diff options
774 files changed, 22639 insertions, 7500 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 2d164f891590..6d74a840525b 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -16,6 +16,7 @@ aconfig_srcjars = [      ":android.app.usage.flags-aconfig-java{.generated_srcjars}",      ":android.companion.flags-aconfig-java{.generated_srcjars}",      ":android.content.pm.flags-aconfig-java{.generated_srcjars}", +    ":android.content.res.flags-aconfig-java{.generated_srcjars}",      ":android.hardware.radio.flags-aconfig-java{.generated_srcjars}",      ":android.nfc.flags-aconfig-java{.generated_srcjars}",      ":android.os.flags-aconfig-java{.generated_srcjars}", @@ -33,10 +34,13 @@ aconfig_srcjars = [      ":android.companion.virtual.flags-aconfig-java{.generated_srcjars}",      ":android.view.inputmethod.flags-aconfig-java{.generated_srcjars}",      ":android.widget.flags-aconfig-java{.generated_srcjars}", +    ":com.android.media.audio.flags-aconfig-java{.generated_srcjars}",      ":com.android.media.flags.bettertogether-aconfig-java{.generated_srcjars}",      ":sdk_sandbox_flags_lib{.generated_srcjars}",      ":android.permission.flags-aconfig-java{.generated_srcjars}", +    ":android.database.sqlite-aconfig-java{.generated_srcjars}",      ":hwui_flags_java_lib{.generated_srcjars}", +    ":framework_graphics_flags_java_lib{.generated_srcjars}",      ":display_flags_lib{.generated_srcjars}",      ":com.android.internal.foldables.flags-aconfig-java{.generated_srcjars}",      ":android.multiuser.flags-aconfig-java{.generated_srcjars}", @@ -44,7 +48,9 @@ aconfig_srcjars = [      ":android.credentials.flags-aconfig-java{.generated_srcjars}",      ":android.view.contentprotection.flags-aconfig-java{.generated_srcjars}",      ":android.service.voice.flags-aconfig-java{.generated_srcjars}", +    ":aconfig_midi_flags_java_lib{.generated_srcjars}",      ":android.service.autofill.flags-aconfig-java{.generated_srcjars}", +    ":com.android.net.flags-aconfig-java{.generated_srcjars}",  ]  filegroup { @@ -301,6 +307,19 @@ java_aconfig_library {      defaults: ["framework-minus-apex-aconfig-java-defaults"],  } +// Resources +aconfig_declarations { +    name: "android.content.res.flags-aconfig", +    package: "android.content.res", +    srcs: ["core/java/android/content/res/*.aconfig"], +} + +java_aconfig_library { +    name: "android.content.res.flags-aconfig-java", +    aconfig_declarations: "android.content.res.flags-aconfig", +    defaults: ["framework-minus-apex-aconfig-java-defaults"], +} +  // Media BetterTogether  aconfig_declarations {      name: "com.android.media.flags.bettertogether-aconfig", @@ -314,6 +333,13 @@ java_aconfig_library {      defaults: ["framework-minus-apex-aconfig-java-defaults"],  } +// Media Audio +java_aconfig_library { +    name: "com.android.media.audio.flags-aconfig-java", +    aconfig_declarations: "aconfig_audio_flags", +    defaults: ["framework-minus-apex-aconfig-java-defaults"], +} +  // Permissions  aconfig_declarations {      name: "android.permission.flags-aconfig", @@ -332,6 +358,19 @@ java_aconfig_library {  } +// SQLite +aconfig_declarations { +    name: "android.database.sqlite-aconfig", +    package: "android.database.sqlite", +    srcs: ["core/java/android/database/sqlite/*.aconfig"], +} + +java_aconfig_library { +    name: "android.database.sqlite-aconfig-java", +    aconfig_declarations: "android.database.sqlite-aconfig", +    defaults: ["framework-minus-apex-aconfig-java-defaults"], +} +  // Biometrics  aconfig_declarations {      name: "android.hardware.biometrics.flags-aconfig", @@ -352,6 +391,12 @@ java_aconfig_library {      defaults: ["framework-minus-apex-aconfig-java-defaults"],  } +java_aconfig_library { +    name: "framework_graphics_flags_java_lib", +    aconfig_declarations: "framework_graphics_flags", +    defaults: ["framework-minus-apex-aconfig-java-defaults"], +} +  // Display  java_aconfig_library {      name: "display_flags_lib", @@ -471,3 +516,10 @@ java_aconfig_library {      aconfig_declarations: "android.companion.flags-aconfig",      defaults: ["framework-minus-apex-aconfig-java-defaults"],  } + +// CoreNetworking +java_aconfig_library { +    name: "com.android.net.flags-aconfig-java", +    aconfig_declarations: "com.android.net.flags-aconfig", +    defaults: ["framework-minus-apex-aconfig-java-defaults"], +} diff --git a/Android.bp b/Android.bp index a507465aa419..0c199a634725 100644 --- a/Android.bp +++ b/Android.bp @@ -227,7 +227,6 @@ java_library {          "android.hardware.radio.messaging-V3-java",          "android.hardware.radio.modem-V3-java",          "android.hardware.radio.network-V3-java", -        "android.hardware.radio.satellite-V1-java",          "android.hardware.radio.sim-V3-java",          "android.hardware.radio.voice-V3-java",          "android.hardware.thermal-V1.0-java-constants", diff --git a/Ravenwood.bp b/Ravenwood.bp index 9218cc9bc3f8..da02298b7415 100644 --- a/Ravenwood.bp +++ b/Ravenwood.bp @@ -59,6 +59,7 @@ java_genrule_host {  // Extract the impl jar from "framework-minus-apex.ravenwood-base" for subsequent build rules.  java_genrule_host {      name: "framework-minus-apex.ravenwood", +    defaults: ["hoststubgen-for-prototype-only-genrule"],      cmd: "cp $(in) $(out)",      srcs: [          ":framework-minus-apex.ravenwood-base{ravenwood.jar}", @@ -66,5 +67,4 @@ java_genrule_host {      out: [          "framework-minus-apex.ravenwood.jar",      ], -    visibility: ["//visibility:public"],  } diff --git a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java index f1403bd51049..e833bb95a302 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java @@ -439,7 +439,7 @@ public class JobParameters implements Parcelable {       * provides an easy way to tell whether the job is being executed due to the deadline       * expiring. Note: If the job is running because its deadline expired, it implies that its       * constraints will not be met. However, -     * {@link android.app.job.JobInfo.Builder#setPeriodic(boolean) periodic jobs} will only ever +     * {@link android.app.job.JobInfo.Builder#setPeriodic(long) periodic jobs} will only ever       * run when their constraints are satisfied, therefore, the constraints will still be satisfied       * for a periodic job even if the deadline has expired.       */ diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig index 4e3cb7d83451..3e835b8ad429 100644 --- a/apex/jobscheduler/service/aconfig/job.aconfig +++ b/apex/jobscheduler/service/aconfig/job.aconfig @@ -2,7 +2,7 @@ package: "com.android.server.job"  flag {      name: "relax_prefetch_connectivity_constraint_only_on_charger" -    namespace: "backstagepower" +    namespace: "backstage_power"      description: "Only relax a prefetch job's connectivity constraint when the device is charging"      bug: "299329948"  }
\ No newline at end of file diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java index 3cbee5d07bb9..384a480af4e9 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -173,8 +173,6 @@ import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;  import dalvik.annotation.optimization.NeverCompile; -import libcore.util.EmptyArray; -  import java.io.FileDescriptor;  import java.io.PrintWriter;  import java.lang.annotation.Retention; @@ -260,7 +258,7 @@ public class AlarmManagerService extends SystemService {      /**       * A map from uid to the last op-mode we have seen for       * {@link AppOpsManager#OP_SCHEDULE_EXACT_ALARM}. Used for evaluating permission state change -     * when the denylist changes. +     * when the app-op changes.       */      @VisibleForTesting      @GuardedBy("mLock") @@ -671,9 +669,6 @@ public class AlarmManagerService extends SystemService {      @VisibleForTesting      final class Constants implements DeviceConfig.OnPropertiesChangedListener,              EconomyManagerInternal.TareStateChangeListener { -        @VisibleForTesting -        static final int MAX_EXACT_ALARM_DENY_LIST_SIZE = 250; -          // Key names stored in the settings value.          @VisibleForTesting          static final String KEY_MIN_FUTURITY = "min_futurity"; @@ -727,8 +722,6 @@ public class AlarmManagerService extends SystemService {          @VisibleForTesting          static final String KEY_PRIORITY_ALARM_DELAY = "priority_alarm_delay";          @VisibleForTesting -        static final String KEY_EXACT_ALARM_DENY_LIST = "exact_alarm_deny_list"; -        @VisibleForTesting          static final String KEY_MIN_DEVICE_IDLE_FUZZ = "min_device_idle_fuzz";          @VisibleForTesting          static final String KEY_MAX_DEVICE_IDLE_FUZZ = "max_device_idle_fuzz"; @@ -835,13 +828,6 @@ public class AlarmManagerService extends SystemService {          public long PRIORITY_ALARM_DELAY = DEFAULT_PRIORITY_ALARM_DELAY;          /** -         * Read-only set of apps that won't get SCHEDULE_EXACT_ALARM when the app-op mode for -         * OP_SCHEDULE_EXACT_ALARM is MODE_DEFAULT. Since this is read-only and volatile, this can -         * be accessed without synchronizing on {@link #mLock}. -         */ -        public volatile Set<String> EXACT_ALARM_DENY_LIST = Collections.emptySet(); - -        /**           * Minimum time interval that an IDLE_UNTIL will be pulled earlier to a subsequent           * WAKE_FROM_IDLE alarm.           */ @@ -1025,21 +1011,6 @@ public class AlarmManagerService extends SystemService {                              PRIORITY_ALARM_DELAY = properties.getLong(KEY_PRIORITY_ALARM_DELAY,                                      DEFAULT_PRIORITY_ALARM_DELAY);                              break; -                        case KEY_EXACT_ALARM_DENY_LIST: -                            final String rawValue = properties.getString(KEY_EXACT_ALARM_DENY_LIST, -                                    ""); -                            final String[] values = rawValue.isEmpty() -                                    ? EmptyArray.STRING -                                    : rawValue.split(",", MAX_EXACT_ALARM_DENY_LIST_SIZE + 1); -                            if (values.length > MAX_EXACT_ALARM_DENY_LIST_SIZE) { -                                Slog.w(TAG, "Deny list too long, truncating to " -                                        + MAX_EXACT_ALARM_DENY_LIST_SIZE + " elements."); -                                updateExactAlarmDenyList( -                                        Arrays.copyOf(values, MAX_EXACT_ALARM_DENY_LIST_SIZE)); -                            } else { -                                updateExactAlarmDenyList(values); -                            } -                            break;                          case KEY_MIN_DEVICE_IDLE_FUZZ:                          case KEY_MAX_DEVICE_IDLE_FUZZ:                              if (!deviceIdleFuzzBoundariesUpdated) { @@ -1110,28 +1081,6 @@ public class AlarmManagerService extends SystemService {              }          } -        private void updateExactAlarmDenyList(String[] newDenyList) { -            final Set<String> newSet = Collections.unmodifiableSet(new ArraySet<>(newDenyList)); -            final Set<String> removed = new ArraySet<>(EXACT_ALARM_DENY_LIST); -            final Set<String> added = new ArraySet<>(newDenyList); - -            added.removeAll(EXACT_ALARM_DENY_LIST); -            removed.removeAll(newSet); -            if (added.size() > 0) { -                mHandler.obtainMessage(AlarmHandler.EXACT_ALARM_DENY_LIST_PACKAGES_ADDED, added) -                        .sendToTarget(); -            } -            if (removed.size() > 0) { -                mHandler.obtainMessage(AlarmHandler.EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED, removed) -                        .sendToTarget(); -            } -            if (newDenyList.length == 0) { -                EXACT_ALARM_DENY_LIST = Collections.emptySet(); -            } else { -                EXACT_ALARM_DENY_LIST = newSet; -            } -        } -          private void updateDeviceIdleFuzzBoundaries() {              final DeviceConfig.Properties properties = DeviceConfig.getProperties(                      DeviceConfig.NAMESPACE_ALARM_MANAGER, @@ -1277,9 +1226,6 @@ public class AlarmManagerService extends SystemService {              TimeUtils.formatDuration(PRIORITY_ALARM_DELAY, pw);              pw.println(); -            pw.print(KEY_EXACT_ALARM_DENY_LIST, EXACT_ALARM_DENY_LIST); -            pw.println(); -              pw.print(KEY_MIN_DEVICE_IDLE_FUZZ);              pw.print("=");              TimeUtils.formatDuration(MIN_DEVICE_IDLE_FUZZ, pw); @@ -2114,14 +2060,10 @@ public class AlarmManagerService extends SystemService {                                              ? permissionState                                              : (newMode == AppOpsManager.MODE_ALLOWED);                                  } else { -                                    final boolean allowedByDefault = -                                            !mConstants.EXACT_ALARM_DENY_LIST.contains(packageName);                                      hadPermission = (oldMode == AppOpsManager.MODE_DEFAULT) -                                            ? allowedByDefault -                                            : (oldMode == AppOpsManager.MODE_ALLOWED); +                                            || (oldMode == AppOpsManager.MODE_ALLOWED);                                      hasPermission = (newMode == AppOpsManager.MODE_DEFAULT) -                                            ? allowedByDefault -                                            : (newMode == AppOpsManager.MODE_ALLOWED); +                                            || (newMode == AppOpsManager.MODE_ALLOWED);                                  }                                  if (hadPermission && !hasPermission) { @@ -2769,11 +2711,8 @@ public class AlarmManagerService extends SystemService {              // Compatibility permission check for older apps.              final int mode = mAppOps.checkOpNoThrow(AppOpsManager.OP_SCHEDULE_EXACT_ALARM, uid,                      packageName); -            if (mode == AppOpsManager.MODE_DEFAULT) { -                hasPermission = !mConstants.EXACT_ALARM_DENY_LIST.contains(packageName); -            } else { -                hasPermission = (mode == AppOpsManager.MODE_ALLOWED); -            } +            hasPermission = (mode == AppOpsManager.MODE_DEFAULT) +                    || (mode == AppOpsManager.MODE_ALLOWED);          }          mStatLogger.logDurationStat(Stats.HAS_SCHEDULE_EXACT_ALARM, start);          return hasPermission; @@ -3993,63 +3932,6 @@ public class AlarmManagerService extends SystemService {      }      /** -     * Called when the {@link Constants#EXACT_ALARM_DENY_LIST}, changes with the packages that -     * either got added or deleted. -     * These packages may lose or gain the SCHEDULE_EXACT_ALARM permission. -     * -     * Note that these packages don't need to be installed on the device, but if they are and they -     * do undergo a permission change, we will handle them appropriately. -     * -     * This should not be called with the lock held as it calls out to other services. -     * This is not expected to get called frequently. -     */ -    void handleChangesToExactAlarmDenyList(ArraySet<String> changedPackages, boolean added) { -        Slog.w(TAG, "Packages " + changedPackages + (added ? " added to" : " removed from") -                + " the exact alarm deny list."); - -        final int[] startedUserIds = mActivityManagerInternal.getStartedUserIds(); - -        for (int i = 0; i < changedPackages.size(); i++) { -            final String changedPackage = changedPackages.valueAt(i); -            for (final int userId : startedUserIds) { -                final int uid = mPackageManagerInternal.getPackageUid(changedPackage, 0, userId); -                if (uid <= 0) { -                    continue; -                } -                if (!isExactAlarmChangeEnabled(changedPackage, userId)) { -                    continue; -                } -                if (isScheduleExactAlarmDeniedByDefault(changedPackage, userId)) { -                    continue; -                } -                if (hasUseExactAlarmInternal(changedPackage, uid)) { -                    continue; -                } -                if (!mExactAlarmCandidates.contains(UserHandle.getAppId(uid))) { -                    // Permission isn't requested, deny list doesn't matter. -                    continue; -                } -                final int appOpMode; -                synchronized (mLock) { -                    appOpMode = mLastOpScheduleExactAlarm.get(uid, -                            AppOpsManager.opToDefaultMode(AppOpsManager.OP_SCHEDULE_EXACT_ALARM)); -                } -                if (appOpMode != AppOpsManager.MODE_DEFAULT) { -                    // Deny list doesn't matter. -                    continue; -                } -                // added: true => package was added to the deny list -                // added: false => package was removed from the deny list -                if (added) { -                    removeExactAlarmsOnPermissionRevoked(uid, changedPackage, /*killUid = */ true); -                } else { -                    sendScheduleExactAlarmPermissionStateChangedBroadcast(changedPackage, userId); -                } -            } -        } -    } - -    /**       * Called when an app loses the permission to use exact alarms. This will happen when the app       * no longer has either {@link Manifest.permission#SCHEDULE_EXACT_ALARM} or       * {@link Manifest.permission#USE_EXACT_ALARM}. @@ -4931,8 +4813,8 @@ public class AlarmManagerService extends SystemService {          public static final int CHARGING_STATUS_CHANGED = 6;          public static final int REMOVE_FOR_CANCELED = 7;          public static final int REMOVE_EXACT_ALARMS = 8; -        public static final int EXACT_ALARM_DENY_LIST_PACKAGES_ADDED = 9; -        public static final int EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED = 10; +        // Unused id 9 +        // Unused id 10          public static final int REFRESH_EXACT_ALARM_CANDIDATES = 11;          public static final int TARE_AFFORDABILITY_CHANGED = 12;          public static final int CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE = 13; @@ -5041,12 +4923,6 @@ public class AlarmManagerService extends SystemService {                      String packageName = (String) msg.obj;                      removeExactAlarmsOnPermissionRevoked(uid, packageName, /*killUid = */true);                      break; -                case EXACT_ALARM_DENY_LIST_PACKAGES_ADDED: -                    handleChangesToExactAlarmDenyList((ArraySet<String>) msg.obj, true); -                    break; -                case EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED: -                    handleChangesToExactAlarmDenyList((ArraySet<String>) msg.obj, false); -                    break;                  case REFRESH_EXACT_ALARM_CANDIDATES:                      refreshExactAlarmCandidates();                      break; diff --git a/api/ApiDocs.bp b/api/ApiDocs.bp index 30b442336148..e1621008cc33 100644 --- a/api/ApiDocs.bp +++ b/api/ApiDocs.bp @@ -124,7 +124,6 @@ droidstubs {              "packages/modules/Media/apex/aidl/stable",          ],      }, -    extensions_info_file: ":sdk-extensions-info",  }  droidstubs { @@ -132,13 +131,7 @@ droidstubs {      defaults: ["framework-doc-stubs-sources-default"],      args: metalava_framework_docs_args +          " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\) ", -    api_levels_annotations_enabled: true, -    api_levels_annotations_dirs: [ -        "sdk-dir", -        "api-versions-jars-dir", -    ], -    api_levels_sdk_type: "system", -    extensions_info_file: ":sdk-extensions-info", +    api_levels_module: "api_versions_system",  }  ///////////////////////////////////////////////////////////////////// diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp index fa4bc0f98fa5..7e41660cf1a2 100644 --- a/api/StubLibraries.bp +++ b/api/StubLibraries.bp @@ -695,6 +695,7 @@ java_api_library {          "api-stubs-docs-non-updatable.api.contribution",      ],      visibility: ["//visibility:public"], +    enable_validation: false,  }  java_api_library { @@ -710,6 +711,7 @@ java_api_library {          "system-api-stubs-docs-non-updatable.api.contribution",      ],      visibility: ["//visibility:public"], +    enable_validation: false,  }  java_api_library { @@ -727,6 +729,7 @@ java_api_library {          "test-api-stubs-docs-non-updatable.api.contribution",      ],      visibility: ["//visibility:public"], +    enable_validation: false,  }  java_api_library { @@ -742,6 +745,7 @@ java_api_library {          "api-stubs-docs-non-updatable.api.contribution",          "system-api-stubs-docs-non-updatable.api.contribution",      ], +    enable_validation: false,  }  java_api_library { @@ -761,6 +765,7 @@ java_api_library {          "module-lib-api-stubs-docs-non-updatable.api.contribution",      ],      visibility: ["//visibility:public"], +    enable_validation: false,  }  java_api_library { @@ -774,6 +779,7 @@ java_api_library {          "stub-annotations",      ],      visibility: ["//visibility:public"], +    enable_validation: false,  }  java_api_library { @@ -798,6 +804,7 @@ java_api_library {      visibility: [          "//visibility:private",      ], +    enable_validation: false,  }  java_api_library { @@ -814,6 +821,7 @@ java_api_library {          "android_module_lib_stubs_current.from-text",      ],      visibility: ["//visibility:public"], +    enable_validation: false,  }  //////////////////////////////////////////////////////////////////////// diff --git a/api/javadoc-lint-baseline b/api/javadoc-lint-baseline index d1821d91ff12..d9e72b83e46e 100644 --- a/api/javadoc-lint-baseline +++ b/api/javadoc-lint-baseline @@ -53,16 +53,6 @@ android/adservices/ondevicepersonalization/WebViewEventInput.java:21: lint: Unre  android/adservices/ondevicepersonalization/WebViewEventInput.java:30: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onExecute() IsolatedWorker#onExecute()" in android.adservices.ondevicepersonalization.WebViewEventInput [101]  android/adservices/ondevicepersonalization/WebViewEventInput.java:41: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.EventUrlProvider#createEventTrackingUrlWithResponse() EventUrlProvider#createEventTrackingUrlWithResponse()" in android.adservices.ondevicepersonalization.WebViewEventInput [101]  android/adservices/ondevicepersonalization/WebViewEventOutput.java:21: lint: Unresolved link/see tag "android.adservices.ondevicepersonalization.IsolatedWorker#onWebViewEvent() IsolatedWorker#onWebViewEvent()" in android.adservices.ondevicepersonalization.WebViewEventOutput [101] -android/app/ActivityOptions.java:366: lint: Unresolved link/see tag "android.app.ComponentOptions.BackgroundActivityStartMode" in android.app.ActivityOptions [101] -android/app/ActivityOptions.java:370: lint: Unresolved link/see tag "android.app.ComponentOptions.BackgroundActivityStartMode" in android.app.ActivityOptions [101] -android/app/ActivityOptions.java:384: lint: Unresolved link/see tag "android.app.ComponentOptions.BackgroundActivityStartMode" in android.app.ActivityOptions [101] -android/app/ApplicationStartInfo.java:96: lint: Unresolved link/see tag "#START_TIMESTAMP_JAVA_CLASSLOADING_COMPLETE" in android.app.ApplicationStartInfo [101] -android/app/BroadcastOptions.java:132: lint: Unresolved link/see tag "#setDeliveryGroupMatchingFilter(android.content.IntentFilter)" in android.app.BroadcastOptions [101] -android/app/GrammaticalInflectionManager.java:60: lint: Unresolved link/see tag "android.os.Environment#getDataSystemCeDirectory(int)" in android.app.GrammaticalInflectionManager [101] -android/app/Notification.java:509: lint: Unresolved link/see tag "android.annotation.ColorInt ColorInt" in android.app.Notification [101] -android/app/Notification.java:650: lint: Unresolved link/see tag "android.annotation.ColorInt ColorInt" in android.app.Notification [101] -android/app/Notification.java:1866: lint: Unresolved link/see tag "/*missing*/" in android.app.Notification.Action [101] -android/app/Notification.java:4796: lint: Unresolved link/see tag "android.content.pm.ShortcutInfo#setLongLived() ShortcutInfo#setLongLived()" in android.app.Notification.MessagingStyle [101]  android/app/admin/DevicePolicyManager.java:2670: lint: Unresolved link/see tag "android.os.UserManager#DISALLOW_CAMERA UserManager#DISALLOW_CAMERA" in android.app.admin.DevicePolicyManager [101]  android/app/admin/DevicePolicyManager.java:7257: lint: Unresolved link/see tag "android.app.admin.DevicePolicyIdentifiers#USB_DATA_SIGNALING_POLICY DevicePolicyIdentifiers#USB_DATA_SIGNALING_POLICY" in android.app.admin.DevicePolicyManager [101]  android/app/admin/DevicePolicyManager.java:7425: lint: Unresolved link/see tag "ACTION_DEVICE_FINANCING_STATE_CHANGED" in android.app.admin.DevicePolicyManager [101] @@ -83,17 +73,6 @@ android/app/appsearch/SearchSpec.java:244: lint: Unresolved link/see tag "Featur  android/app/appsearch/SearchSpec.java:913: lint: Unresolved link/see tag "Features#NUMERIC_SEARCH" in android.app.appsearch.SearchSpec.Builder [101]  android/app/appsearch/SearchSpec.java:925: lint: Unresolved link/see tag "Features#VERBATIM_SEARCH" in android.app.appsearch.SearchSpec.Builder [101]  android/app/appsearch/SearchSpec.java:929: lint: Unresolved link/see tag "Features#LIST_FILTER_QUERY_LANGUAGE" in android.app.appsearch.SearchSpec.Builder [101] -android/app/job/JobParameters.java:128: lint: Unresolved link/see tag "android.app.job.JobInfo.Builder#setPeriodic(boolean) periodic jobs" in android.app.job.JobParameters [101] -android/app/sdksandbox/AppOwnedSdkSandboxInterface.java:9: lint: Unresolved link/see tag "SdkSandboxController#getAppOwnedSdkSandboxInterfaces" in android.app.sdksandbox.AppOwnedSdkSandboxInterface [101] -android/app/sdksandbox/SdkSandboxManager.java:112: lint: Unresolved link/see tag "AppOwnedSdkSandboxInterfaces" in android.app.sdksandbox.SdkSandboxManager [101] -android/content/AttributionSource.java:291: lint: Unresolved link/see tag "setNextAttributionSource" in android.content.AttributionSource.Builder [101] -android/content/Context.java:2872: lint: Unresolved link/see tag "android.telephony.MmsManager" in android.content.Context [101] -android/content/Intent.java:4734: lint: Unresolved link/see tag "android.content.pm.UserInfo#isProfile()" in android.content.Intent [101] -android/content/Intent.java:4760: lint: Unresolved link/see tag "android.content.pm.UserInfo#isProfile()" in android.content.Intent [101] -android/content/Intent.java:4778: lint: Unresolved link/see tag "android.content.pm.UserInfo#isProfile()" in android.content.Intent [101] -android/content/Intent.java:4802: lint: Unresolved link/see tag "android.content.pm.UserInfo#isProfile()" in android.content.Intent [101] -android/graphics/Paint.java:838: lint: Unresolved link/see tag "android.annotation.ColorLong ColorLong" in android.graphics.Paint [101] -android/graphics/text/LineBreaker.java:246: lint: Unresolved link/see tag "StaticLayout.Builder#setUseBoundsForWidth(boolean)" in android.graphics.text.LineBreaker.Builder [101]  android/hardware/camera2/CameraCharacteristics.java:2169: lint: Unresolved link/see tag "android.hardware.camera2.CameraDevice#stream-use-case-capability-additional-guaranteed-configurations guideline" in android.hardware.camera2.CameraCharacteristics [101]  android/hardware/camera2/CameraCharacteristics.java:2344: lint: Unresolved link/see tag "android.hardware.camera2.CameraDevice#concurrent-stream-guaranteed-configurations guideline" in android.hardware.camera2.CameraCharacteristics [101]  android/hardware/camera2/CameraCharacteristics.java:2344: lint: Unresolved link/see tag "android.hardware.camera2.CameraDevice#concurrent-stream-guaranteed-configurations tables" in android.hardware.camera2.CameraCharacteristics [101] @@ -119,11 +98,6 @@ android/hardware/camera2/CaptureRequest.java:704: lint: Unresolved link/see tag  android/hardware/camera2/CaptureRequest.java:1501: lint: Unresolved link/see tag "SessionConfiguration#setSessionParameters" in android.hardware.camera2.CaptureRequest [101]  android/hardware/camera2/CaptureResult.java:923: lint: Unresolved link/see tag "SessionConfiguration#setSessionParameters" in android.hardware.camera2.CaptureResult [101]  android/hardware/camera2/CaptureResult.java:2337: lint: Unresolved link/see tag "SessionConfiguration#setSessionParameters" in android.hardware.camera2.CaptureResult [101] -android/hardware/input/InputManager.java:215: lint: Unresolved link/see tag "android.hardware.input.InputManagerGlobal#getInputDevice InputManagerGlobal#getInputDevice" in android.hardware.input.InputManager.InputDeviceListener [101] -android/inputmethodservice/AbstractInputMethodService.java:155: lint: Unresolved link/see tag "android.app.ActivityThread ActivityThread" in android.inputmethodservice.AbstractInputMethodService [101] -android/inputmethodservice/InputMethodService.java:1078: lint: Unresolved link/see tag "android.widget.Editor" in android.inputmethodservice.InputMethodService [101] -android/location/GnssSignalType.java:14: lint: Unresolved link/see tag "android.location.GnssStatus.ConstellationType GnssStatus.ConstellationType" in android.location.GnssSignalType [101] -android/location/GnssSignalType.java:48: lint: Unresolved link/see tag "android.location.GnssStatus.ConstellationType GnssStatus.ConstellationType" in android.location.GnssSignalType [101]  android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_ALARM AttributeSdkUsage#USAGE_ALARM" in android.media.AudioAttributes.Builder [101]  android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_ASSISTANCE_ACCESSIBILITY AttributeSdkUsage#USAGE_ASSISTANCE_ACCESSIBILITY" in android.media.AudioAttributes.Builder [101]  android/media/AudioAttributes.java:443: lint: Unresolved link/see tag "android.media.AudioAttributes.AttributeSdkUsage#USAGE_ASSISTANCE_NAVIGATION_GUIDANCE AttributeSdkUsage#USAGE_ASSISTANCE_NAVIGATION_GUIDANCE" in android.media.AudioAttributes.Builder [101] @@ -142,8 +116,6 @@ android/media/AudioManager.java:287: lint: Unresolved link/see tag "android.medi  android/media/AudioManager.java:311: lint: Unresolved link/see tag "android.media.audiopolicy.AudioVolumeGroup" in android.media.AudioManager [101]  android/media/AudioManager.java:313: lint: Unresolved link/see tag "android.media.audiopolicy.AudioVolumeGroup" in android.media.AudioManager [101]  android/media/AudioMetadata.java:118: lint: Unresolved link/see tag "android.media.AudioPresentation.ContentClassifier One of {@link android.media.AudioPresentation#CONTENT_UNKNOWN AudioPresentation#CONTENT_UNKNOWN},     {@link android.media.AudioPresentation#CONTENT_MAIN AudioPresentation#CONTENT_MAIN},     {@link android.media.AudioPresentation#CONTENT_MUSIC_AND_EFFECTS AudioPresentation#CONTENT_MUSIC_AND_EFFECTS},     {@link android.media.AudioPresentation#CONTENT_VISUALLY_IMPAIRED AudioPresentation#CONTENT_VISUALLY_IMPAIRED},     {@link android.media.AudioPresentation#CONTENT_HEARING_IMPAIRED AudioPresentation#CONTENT_HEARING_IMPAIRED},     {@link android.media.AudioPresentation#CONTENT_DIALOG AudioPresentation#CONTENT_DIALOG},     {@link android.media.AudioPresentation#CONTENT_COMMENTARY AudioPresentation#CONTENT_COMMENTARY},     {@link android.media.AudioPresentation#CONTENT_EMERGENCY AudioPresentation#CONTENT_EMERGENCY},     {@link android.media.AudioPresentation#CONTENT_VOICEOVER AudioPresentation#CONTENT_VOICEOVER}." in android.media.AudioMetadata.Format [101] -android/media/MediaRouter2.java:162: lint: Unresolved link/see tag "#getInstance(android.content.Context,java.lang.String)" in android.media.MediaRouter2 [101] -android/media/midi/MidiUmpDeviceService.java:-1: lint: Unresolved link/see tag "#MidiDeviceService" in android.media.midi.MidiUmpDeviceService [101]  android/media/tv/SectionRequest.java:44: lint: Unresolved link/see tag "android.media.tv.BroadcastInfoRequest.RequestOption BroadcastInfoRequest.RequestOption" in android.media.tv.SectionRequest [101]  android/media/tv/SectionResponse.java:39: lint: Unresolved link/see tag "android.media.tv.BroadcastInfoRequest.RequestOption BroadcastInfoRequest.RequestOption" in android.media.tv.SectionResponse [101]  android/media/tv/TableRequest.java:48: lint: Unresolved link/see tag "android.media.tv.BroadcastInfoRequest.RequestOption BroadcastInfoRequest.RequestOption" in android.media.tv.TableRequest [101] @@ -179,52 +151,6 @@ android/net/wifi/aware/SubscribeConfig.java:51: lint: Unresolved link/see tag "a  android/net/wifi/aware/SubscribeConfig.java:51: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_5_GHZ WifiScanner#WIFI_BAND_5_GHZ" in android.net.wifi.aware.SubscribeConfig [101]  android/net/wifi/aware/SubscribeConfig.java:276: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_24_GHZ WifiScanner#WIFI_BAND_24_GHZ" in android.net.wifi.aware.SubscribeConfig.Builder [101]  android/net/wifi/aware/SubscribeConfig.java:276: lint: Unresolved link/see tag "android.net.wifi.WifiScanner#WIFI_BAND_5_GHZ WifiScanner#WIFI_BAND_5_GHZ" in android.net.wifi.aware.SubscribeConfig.Builder [101] -android/os/BugreportManager.java:146: lint: Unresolved link/see tag "android.os.BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT" in android.os.BugreportManager.BugreportCallback [101] -android/os/PowerManager.java:796: lint: Unresolved link/see tag "android.os.Temperature" in android.os.PowerManager.OnThermalStatusChangedListener [101] -android/os/RemoteException.java:49: lint: Unresolved link/see tag "android.os.DeadSystemRuntimeException DeadSystemRuntimeException" in android.os.RemoteException [101] -android/provider/Settings.java:374: lint: Unresolved link/see tag "android.credentials.CredentialManager#isEnabledCredentialProviderService()" in android.provider.Settings [101] -android/provider/Settings.java:908: lint: Unresolved link/see tag "android.service.notification.NotificationAssistantService" in android.provider.Settings [101] -android/provider/Settings.java:2181: lint: Unresolved link/see tag "android.app.time.TimeManager" in android.provider.Settings.Global [101] -android/provider/Settings.java:2195: lint: Unresolved link/see tag "android.app.time.TimeManager" in android.provider.Settings.Global [101] -android/security/KeyStoreException.java:27: lint: Unresolved link/see tag "android.security.KeyStoreException.PublicErrorCode PublicErrorCode" in android.security.KeyStoreException [101] -android/service/autofill/FillResponse.java:86: lint: Unresolved link/see tag "setFieldClassificationIds" in android.service.autofill.FillResponse.Builder [101] -android/service/autofill/SaveInfo.java:623: lint: Unresolved link/see tag "FillRequest.getHints()" in android.service.autofill.SaveInfo.Builder [101] -android/service/notification/NotificationListenerService.java:417: lint: Unresolved link/see tag "android.service.notification.NotificationAssistantService notification assistant" in android.service.notification.NotificationListenerService [101] -android/service/notification/NotificationListenerService.java:435: lint: Unresolved link/see tag "android.service.notification.NotificationAssistantService notification assistant" in android.service.notification.NotificationListenerService [101] -android/service/notification/NotificationListenerService.java:1155: lint: Unresolved link/see tag "android.service.notification.NotificationAssistantService NotificationAssistantService" in android.service.notification.NotificationListenerService.Ranking [101] -android/service/notification/NotificationListenerService.java:1166: lint: Unresolved link/see tag "android.service.notification.NotificationAssistantService NotificationAssistantService" in android.service.notification.NotificationListenerService.Ranking [101] -android/service/quickaccesswallet/WalletCard.java:285: lint: Unresolved link/see tag "PackageManager.FEATURE_WALLET_LOCATION_BASED_SUGGESTIONS" in android.service.quickaccesswallet.WalletCard.Builder [101] -android/service/voice/VoiceInteractionSession.java:293: lint: Unresolved link/see tag "android.service.voice.VoiceInteractionService#KEY_SHOW_SESSION_ID VoiceInteractionService#KEY_SHOW_SESSION_ID" in android.service.voice.VoiceInteractionSession [101] -android/text/DynamicLayout.java:141: lint: Unresolved link/see tag "LineBreakconfig" in android.text.DynamicLayout [101] -android/text/WordSegmentFinder.java:13: lint: Unresolved link/see tag "android.text.method.WordIterator WordIterator" in android.text.WordSegmentFinder [101] -android/view/InputDevice.java:71: lint: Unresolved link/see tag "InputManagerGlobal.InputDeviceListener" in android.view.InputDevice [101] -android/view/PixelCopy.java:468: lint: Unresolved link/see tag "android.view.PixelCopy.CopyResultStatus CopyResultStatus" in android.view.PixelCopy.Result [101] -android/view/ScrollFeedbackProvider.java:-25: lint: Unresolved link/see tag "InputManager" in android.view.ScrollFeedbackProvider [101] -android/view/ScrollFeedbackProvider.java:-25: lint: Unresolved link/see tag "InputManager#getInputDeviceIds()" in android.view.ScrollFeedbackProvider [101] -android/view/SurfaceControl.java:823: lint: Unresolved link/see tag "android.view.SurfaceControl.TrustedPresentationCallback TrustedPresentationCallback" in android.view.SurfaceControl.Transaction [101] -android/view/SurfaceControl.java:900: lint: Unresolved link/see tag "android.view.SurfaceControl.TrustedPresentationCallback TrustedPresentationCallback" in android.view.SurfaceControl.Transaction [101] -android/view/SurfaceControl.java:908: lint: Unresolved link/see tag "android.view.SurfaceControl.TrustedPresentationCallback TrustedPresentationCallback" in android.view.SurfaceControl.Transaction [101] -android/view/View.java:1647: lint: Unresolved link/see tag "androidx.core.view.ViewCompat#setAccessibilityPaneTitle(View, CharSequence)" in android.view.View [101] -android/view/View.java:4669: lint: Unresolved link/see tag "androidx.core.view.ViewCompat#setScreenReaderFocusable(View, boolean)" in android.view.View [101] -android/view/View.java:4712: lint: Unresolved link/see tag "androidx.core.view.ViewCompat#setAccessibilityHeading(View, boolean)" in android.view.View [101] -android/view/WindowManager.java:230: lint: Unresolved link/see tag "android.annotation.UiContext" in android.view.WindowManager [101] -android/view/WindowManager.java:247: lint: Unresolved link/see tag "android.annotation.UiContext" in android.view.WindowManager [101] -android/view/WindowManager.java:822: lint: @attr must be a field: android.R.attr#Window_windowNoMoveAnimation [106] -android/view/WindowManager.java:832: lint: @attr must be a field: android.R.attr#Window_windowNoMoveAnimation [106] -android/view/WindowMetrics.java:22: lint: Unresolved link/see tag "android.annotation.UiContext" in android.view.WindowMetrics [101] -android/view/WindowMetrics.java:57: lint: Unresolved link/see tag "android.annotation.UiContext" in android.view.WindowMetrics [101] -android/view/WindowMetrics.java:114: lint: Unresolved link/see tag "android.annotation.UiContext" in android.view.WindowMetrics [101] -android/view/WindowMetrics.java:127: lint: Unresolved link/see tag "android.annotation.UiContext" in android.view.WindowMetrics [101] -android/view/accessibility/AccessibilityNodeInfo.java:368: lint: Unresolved link/see tag "androidx.core.view.ViewCompat#addAccessibilityAction(View, AccessibilityNodeInfoCompat.AccessibilityActionCompat)" in android.view.accessibility.AccessibilityNodeInfo [101] -android/view/accessibility/AccessibilityNodeInfo.java:3246: lint: Unresolved link/see tag "androidx.core.view.ViewCompat#addAccessibilityAction(View, AccessibilityNodeInfoCompat.AccessibilityActionCompat)" in android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction [101] -android/view/displayhash/DisplayHashResultCallback.java:38: lint: Unresolved link/see tag "android.view.displayhash.DisplayHashResultCallback.DisplayHashErrorCode DisplayHashErrorCode" in android.view.displayhash.DisplayHashResultCallback [101] -android/view/inputmethod/EditorInfo.java:107: lint: Unresolved link/see tag "android.widget.Editor Editor" in android.view.inputmethod.EditorInfo [101] -android/view/inputmethod/EditorInfo.java:122: lint: Unresolved link/see tag "android.widget.Editor Editor" in android.view.inputmethod.EditorInfo [101] -android/view/inputmethod/InputMethodManager.java:423: lint: Unresolved link/see tag "android.widget.Editor Editor" in android.view.inputmethod.InputMethodManager [101] -android/view/inputmethod/InputMethodManager.java:447: lint: Unresolved link/see tag "android.widget.Editor Editor" in android.view.inputmethod.InputMethodManager [101] -android/view/inputmethod/InputMethodManager.java:456: lint: Unresolved link/see tag "android.widget.Editor Editor" in android.view.inputmethod.InputMethodManager [101] -android/view/inspector/PropertyReader.java:141: lint: Unresolved link/see tag "android.annotation.ColorInt ColorInt" in android.view.inspector.PropertyReader [101] -android/window/BackEvent.java:24: lint: Unresolved link/see tag "android.window.BackMotionEvent BackMotionEvent" in android.window.BackEvent [101]  android/net/wifi/SoftApConfiguration.java:173: lint: Unresolved link/see tag "android.net.wifi.SoftApConfiguration.Builder#setShutdownTimeoutMillis(long)" in android.net.wifi.SoftApConfiguration [101]  android/os/UserManager.java:2384: lint: Unresolved link/see tag "android.annotation.UserHandleAware @UserHandleAware" in android.os.UserManager [101] @@ -234,19 +160,11 @@ android/service/voice/AlwaysOnHotwordDetector.java:269: lint: Unresolved link/se  android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "#STATE_ERROR" in android [101]  android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "Callback#onFailure" in android [101]  android/service/voice/AlwaysOnHotwordDetector.java:278: lint: Unresolved link/see tag "Callback#onUnknownFailure" in android [101] -android/view/animation/AnimationUtils.java:64: lint: Unresolved link/see tag "Build.VERSION_CODES#VANILLA_ICE_CREAM" in android.view.animation.AnimationUtils [101] -android/view/contentcapture/ContentCaptureSession.java:188: lint: Unresolved link/see tag "UPSIDE_DOWN_CAKE" in android.view.contentcapture.ContentCaptureSession [101]  com/android/internal/policy/PhoneWindow.java:172: lint: Unresolved link/see tag "Build.VERSION_CODES#VANILLA_ICE_CREAM" in com.android.internal.policy.PhoneWindow [101]  com/android/server/broadcastradio/aidl/ConversionUtils.java:70: lint: Unresolved link/see tag "IdentifierType#DAB_SID_EXT" in android [101]  com/android/server/broadcastradio/aidl/ConversionUtils.java:70: lint: Unresolved link/see tag "ProgramSelector#IDENTIFIER_TYPE_DAB_DMB_SID_EXT" in android [101]  com/android/server/broadcastradio/aidl/ConversionUtils.java:70: lint: Unresolved link/see tag "RadioTuner" in android [101] -com/android/server/media/MediaSessionRecord.java:104: lint: Unresolved link/see tag "ComponentName" in android [101] -com/android/server/media/MediaSessionRecord.java:104: lint: Unresolved link/see tag "IllegalArgumentException" in android [101] -com/android/server/media/MediaSessionRecord.java:104: lint: Unresolved link/see tag "MediaSession#setMediaButtonBroadcastReceiver(ComponentName)" in android [101] -com/android/server/media/MediaSessionRecord.java:114: lint: Unresolved link/see tag "IllegalArgumentException" in android [101] -com/android/server/media/MediaSessionRecord.java:114: lint: Unresolved link/see tag "MediaSession#setMediaButtonReceiver(PendingIntent)" in android [101] -com/android/server/media/MediaSessionRecord.java:114: lint: Unresolved link/see tag "PendingIntent" in android [101]  com/android/server/pm/PackageInstallerSession.java:313: lint: Unresolved link/see tag "Build.VERSION_CODES#S API 31" in android [101]  com/android/server/pm/PackageInstallerSession.java:313: lint: Unresolved link/see tag "PackageInstaller.SessionParams#setRequireUserAction" in android [101]  com/android/server/pm/PackageInstallerSession.java:327: lint: Unresolved link/see tag "#requestUserPreapproval(PreapprovalDetails, IntentSender)" in android [101] @@ -254,10 +172,3 @@ com/android/server/pm/PackageInstallerSession.java:327: lint: Unresolved link/se  com/android/server/pm/PackageInstallerSession.java:327: lint: Unresolved link/see tag "PackageInstaller.SessionParams#setRequestUpdateOwnership(boolean)" in android [101]  com/android/server/pm/PackageInstallerSession.java:358: lint: Unresolved link/see tag "IntentSender" in android [101]  com/android/server/devicepolicy/DevicePolicyManagerService.java:860: lint: Unresolved link/see tag "android.security.IKeyChainService#setGrant" in android [101] - -android/os/BatteryStatsManager.java:260: lint: Invalid tag: @Deprecated [131] -android/os/BatteryStatsManager.java:275: lint: Invalid tag: @Deprecated [131] -android/view/WindowManager.java:906: lint: @attr must be a field: android.R.attr#Window_windowNoMoveAnimation [106] -android/view/WindowManager.java:916: lint: @attr must be a field: android.R.attr#Window_windowNoMoveAnimation [106] - -java/lang/ClassLoader.java:853: lint: Unknown tag: @systemProperty [103] diff --git a/core/api/current.txt b/core/api/current.txt index 9c7dc17f763b..66aeb0f7cbaf 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -1,6 +1,4 @@  // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704  package android {    public final class Manifest { @@ -3288,10 +3286,10 @@ package android.accessibilityservice {    public abstract class AccessibilityService extends android.app.Service {      ctor public AccessibilityService(); -    method @Deprecated public void attachAccessibilityOverlayToDisplay(int, @NonNull android.view.SurfaceControl); -    method public void attachAccessibilityOverlayToDisplay(int, @NonNull android.view.SurfaceControl, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer); -    method @Deprecated public void attachAccessibilityOverlayToWindow(int, @NonNull android.view.SurfaceControl); -    method public void attachAccessibilityOverlayToWindow(int, @NonNull android.view.SurfaceControl, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer); +    method public void attachAccessibilityOverlayToDisplay(int, @NonNull android.view.SurfaceControl); +    method @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public void attachAccessibilityOverlayToDisplay(int, @NonNull android.view.SurfaceControl, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer); +    method public void attachAccessibilityOverlayToWindow(int, @NonNull android.view.SurfaceControl); +    method @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public void attachAccessibilityOverlayToWindow(int, @NonNull android.view.SurfaceControl, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);      method public boolean clearCache();      method public boolean clearCachedSubtree(@NonNull android.view.accessibility.AccessibilityNodeInfo);      method public final void disableSelf(); @@ -3403,9 +3401,9 @@ package android.accessibilityservice {      field public static final int GLOBAL_ACTION_RECENTS = 3; // 0x3      field public static final int GLOBAL_ACTION_TAKE_SCREENSHOT = 9; // 0x9      field public static final int GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN = 7; // 0x7 -    field public static final int OVERLAY_RESULT_INTERNAL_ERROR = 1; // 0x1 -    field public static final int OVERLAY_RESULT_INVALID = 2; // 0x2 -    field public static final int OVERLAY_RESULT_SUCCESS = 0; // 0x0 +    field @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public static final int OVERLAY_RESULT_INTERNAL_ERROR = 1; // 0x1 +    field @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public static final int OVERLAY_RESULT_INVALID = 2; // 0x2 +    field @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public static final int OVERLAY_RESULT_SUCCESS = 0; // 0x0      field public static final String SERVICE_INTERFACE = "android.accessibilityservice.AccessibilityService";      field public static final String SERVICE_META_DATA = "android.accessibilityservice";      field public static final int SHOW_MODE_AUTO = 0; // 0x0 @@ -5741,6 +5739,7 @@ package android.app {      ctor @Deprecated public FragmentBreadCrumbs(android.content.Context, android.util.AttributeSet);      ctor @Deprecated public FragmentBreadCrumbs(android.content.Context, android.util.AttributeSet, int);      method @Deprecated public void onBackStackChanged(); +    method @Deprecated protected void onLayout(boolean, int, int, int, int);      method @Deprecated public void setActivity(android.app.Activity);      method @Deprecated public void setMaxVisible(int);      method @Deprecated public void setOnBreadCrumbClickListener(android.app.FragmentBreadCrumbs.OnBreadCrumbClickListener); @@ -9789,7 +9788,7 @@ package android.content {      method @NonNull public android.content.AttributionSource.Builder setAttributionTag(@Nullable String);      method @FlaggedApi("android.permission.flags.device_aware_permission_apis") @NonNull public android.content.AttributionSource.Builder setDeviceId(int);      method @Deprecated @NonNull public android.content.AttributionSource.Builder setNext(@Nullable android.content.AttributionSource); -    method @NonNull public android.content.AttributionSource.Builder setNextAttributionSource(@NonNull android.content.AttributionSource); +    method @FlaggedApi("android.permission.flags.set_next_attribution_source") @NonNull public android.content.AttributionSource.Builder setNextAttributionSource(@NonNull android.content.AttributionSource);      method @NonNull public android.content.AttributionSource.Builder setPackageName(@Nullable String);      method @NonNull public android.content.AttributionSource.Builder setPid(int);    } @@ -10572,7 +10571,7 @@ package android.content {    public final class ContextParams {      method @Nullable public String getAttributionTag();      method @Nullable public android.content.AttributionSource getNextAttributionSource(); -    method @NonNull public boolean shouldRegisterAttributionSource(); +    method @FlaggedApi("android.permission.flags.should_register_attribution_source") @NonNull public boolean shouldRegisterAttributionSource();    }    public static final class ContextParams.Builder { @@ -10581,7 +10580,7 @@ package android.content {      method @NonNull public android.content.ContextParams build();      method @NonNull public android.content.ContextParams.Builder setAttributionTag(@Nullable String);      method @NonNull public android.content.ContextParams.Builder setNextAttributionSource(@Nullable android.content.AttributionSource); -    method @NonNull public android.content.ContextParams.Builder setShouldRegisterAttributionSource(boolean); +    method @FlaggedApi("android.permission.flags.should_register_attribution_source") @NonNull public android.content.ContextParams.Builder setShouldRegisterAttributionSource(boolean);    }    public class ContextWrapper extends android.content.Context { @@ -13653,6 +13652,7 @@ package android.content.res {    public interface XmlResourceParser extends org.xmlpull.v1.XmlPullParser android.util.AttributeSet java.lang.AutoCloseable {      method public void close(); +    method public String getAttributeNamespace(int);    }  } @@ -14352,14 +14352,14 @@ package android.database.sqlite {    public final class SQLiteDatabase extends android.database.sqlite.SQLiteClosable {      method public void beginTransaction();      method public void beginTransactionNonExclusive(); -    method public void beginTransactionReadOnly(); +    method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public void beginTransactionReadOnly();      method public void beginTransactionWithListener(@Nullable android.database.sqlite.SQLiteTransactionListener);      method public void beginTransactionWithListenerNonExclusive(@Nullable android.database.sqlite.SQLiteTransactionListener); -    method public void beginTransactionWithListenerReadOnly(@Nullable android.database.sqlite.SQLiteTransactionListener); +    method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public void beginTransactionWithListenerReadOnly(@Nullable android.database.sqlite.SQLiteTransactionListener);      method public android.database.sqlite.SQLiteStatement compileStatement(String) throws android.database.SQLException;      method @NonNull public static android.database.sqlite.SQLiteDatabase create(@Nullable android.database.sqlite.SQLiteDatabase.CursorFactory);      method @NonNull public static android.database.sqlite.SQLiteDatabase createInMemory(@NonNull android.database.sqlite.SQLiteDatabase.OpenParams); -    method @NonNull public android.database.sqlite.SQLiteRawStatement createRawStatement(@NonNull String); +    method @FlaggedApi("android.database.sqlite.sqlite_apis_15") @NonNull public android.database.sqlite.SQLiteRawStatement createRawStatement(@NonNull String);      method public int delete(@NonNull String, @Nullable String, @Nullable String[]);      method public static boolean deleteDatabase(@NonNull java.io.File);      method public void disableWriteAheadLogging(); @@ -14370,13 +14370,13 @@ package android.database.sqlite {      method public void execSQL(@NonNull String, @NonNull Object[]) throws android.database.SQLException;      method public static String findEditTable(String);      method public java.util.List<android.util.Pair<java.lang.String,java.lang.String>> getAttachedDbs(); -    method public long getLastChangedRowCount(); -    method public long getLastInsertRowId(); +    method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public long getLastChangedRowCount(); +    method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public long getLastInsertRowId();      method public long getMaximumSize();      method public long getPageSize();      method public String getPath();      method @Deprecated public java.util.Map<java.lang.String,java.lang.String> getSyncedTables(); -    method public long getTotalChangedRowCount(); +    method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public long getTotalChangedRowCount();      method public int getVersion();      method public boolean inTransaction();      method public long insert(@NonNull String, @Nullable String, @Nullable android.content.ContentValues); @@ -14598,7 +14598,7 @@ package android.database.sqlite {      method public int update(@NonNull android.database.sqlite.SQLiteDatabase, @NonNull android.content.ContentValues, @Nullable String, @Nullable String[]);    } -  public final class SQLiteRawStatement implements java.io.Closeable { +  @FlaggedApi("android.database.sqlite.sqlite_apis_15") public final class SQLiteRawStatement implements java.io.Closeable {      method public void bindBlob(int, @NonNull byte[]) throws android.database.sqlite.SQLiteException;      method public void bindBlob(int, @NonNull byte[], int, int) throws android.database.sqlite.SQLiteException;      method public void bindDouble(int, double) throws android.database.sqlite.SQLiteException; @@ -15671,7 +15671,7 @@ package android.graphics {    public final class Gainmap implements android.os.Parcelable {      ctor public Gainmap(@NonNull android.graphics.Bitmap); -    ctor public Gainmap(@NonNull android.graphics.Gainmap, @NonNull android.graphics.Bitmap); +    ctor @FlaggedApi("com.android.graphics.hwui.flags.gainmap_constructor_with_metadata") public Gainmap(@NonNull android.graphics.Gainmap, @NonNull android.graphics.Bitmap);      method public int describeContents();      method @NonNull public float getDisplayRatioForFullHdr();      method @NonNull public float[] getEpsilonHdr(); @@ -16309,7 +16309,7 @@ package android.graphics {      method public void arcTo(float, float, float, float, float, float, boolean);      method public void close();      method @Deprecated public void computeBounds(@NonNull android.graphics.RectF, boolean); -    method public void computeBounds(@NonNull android.graphics.RectF); +    method @FlaggedApi("com.android.graphics.flags.exact_compute_bounds") public void computeBounds(@NonNull android.graphics.RectF);      method public void conicTo(float, float, float, float, float);      method public void cubicTo(float, float, float, float, float, float);      method @NonNull public android.graphics.Path.FillType getFillType(); @@ -20405,7 +20405,7 @@ package android.inputmethodservice {      method @Deprecated public boolean isPreviewEnabled();      method @Deprecated public boolean isProximityCorrectionEnabled();      method @Deprecated public boolean isShifted(); -    method public void onClick(android.view.View); +    method @Deprecated public void onClick(android.view.View);      method @Deprecated public void onDetachedFromWindow();      method @Deprecated public void onDraw(android.graphics.Canvas);      method @Deprecated protected boolean onLongPress(android.inputmethodservice.Keyboard.Key); @@ -24232,7 +24232,7 @@ package android.media {    @Deprecated public class RemoteControlClient.MetadataEditor extends android.media.MediaMetadataEditor {      method @Deprecated public void apply(); -    method public Object clone() throws java.lang.CloneNotSupportedException; +    method @Deprecated public Object clone() throws java.lang.CloneNotSupportedException;      method @Deprecated public android.media.RemoteControlClient.MetadataEditor putBitmap(int, android.graphics.Bitmap) throws java.lang.IllegalArgumentException;      method @Deprecated public android.media.RemoteControlClient.MetadataEditor putLong(int, long) throws java.lang.IllegalArgumentException;      method @Deprecated public android.media.RemoteControlClient.MetadataEditor putObject(int, Object) throws java.lang.IllegalArgumentException; @@ -25787,15 +25787,15 @@ package android.media.midi {      method public abstract void onDisconnect(android.media.midi.MidiReceiver);    } -  public abstract class MidiUmpDeviceService extends android.app.Service { +  @FlaggedApi("com.android.media.midi.flags.virtual_ump") public abstract class MidiUmpDeviceService extends android.app.Service {      ctor public MidiUmpDeviceService(); -    method @Nullable public final android.media.midi.MidiDeviceInfo getDeviceInfo(); -    method @NonNull public final java.util.List<android.media.midi.MidiReceiver> getOutputPortReceivers(); -    method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent); -    method public void onClose(); -    method public void onDeviceStatusChanged(@NonNull android.media.midi.MidiDeviceStatus); -    method @NonNull public abstract java.util.List<android.media.midi.MidiReceiver> onGetInputPortReceivers(); -    field public static final String SERVICE_INTERFACE = "android.media.midi.MidiUmpDeviceService"; +    method @FlaggedApi("com.android.media.midi.flags.virtual_ump") @Nullable public final android.media.midi.MidiDeviceInfo getDeviceInfo(); +    method @FlaggedApi("com.android.media.midi.flags.virtual_ump") @NonNull public final java.util.List<android.media.midi.MidiReceiver> getOutputPortReceivers(); +    method @FlaggedApi("com.android.media.midi.flags.virtual_ump") @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent); +    method @FlaggedApi("com.android.media.midi.flags.virtual_ump") public void onClose(); +    method @FlaggedApi("com.android.media.midi.flags.virtual_ump") public void onDeviceStatusChanged(@NonNull android.media.midi.MidiDeviceStatus); +    method @FlaggedApi("com.android.media.midi.flags.virtual_ump") @NonNull public abstract java.util.List<android.media.midi.MidiReceiver> onGetInputPortReceivers(); +    field @FlaggedApi("com.android.media.midi.flags.virtual_ump") public static final String SERVICE_INTERFACE = "android.media.midi.MidiUmpDeviceService";    }  } @@ -27096,6 +27096,7 @@ package android.media.tv {      method @NonNull public java.util.List<android.media.AudioPresentation> getAudioPresentations();      method public String getSelectedTrack(int);      method public java.util.List<android.media.tv.TvTrackInfo> getTracks(int); +    method protected void onLayout(boolean, int, int, int, int);      method public boolean onUnhandledInputEvent(android.view.InputEvent);      method public void overrideTvAppAttributionSource(@NonNull android.content.AttributionSource);      method public void reset(); @@ -32392,7 +32393,7 @@ package android.os {      method public void addData(@NonNull String, @Nullable byte[], int);      method public void addFile(@NonNull String, @NonNull java.io.File, int) throws java.io.IOException;      method public void addText(@NonNull String, @NonNull String); -    method @Nullable @RequiresPermission(allOf={"android.permission.READ_DROPBOX_DATA", android.Manifest.permission.PACKAGE_USAGE_STATS}) public android.os.DropBoxManager.Entry getNextEntry(String, long); +    method @Nullable @RequiresPermission(allOf={android.Manifest.permission.READ_LOGS, android.Manifest.permission.PACKAGE_USAGE_STATS}) public android.os.DropBoxManager.Entry getNextEntry(String, long);      method public boolean isTagEnabled(String);      field public static final String ACTION_DROPBOX_ENTRY_ADDED = "android.intent.action.DROPBOX_ENTRY_ADDED";      field public static final String EXTRA_DROPPED_COUNT = "android.os.extra.DROPPED_COUNT"; @@ -33094,6 +33095,22 @@ package android.os {      method public void onStateChanged(boolean);    } +  @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public final class PowerMonitor implements android.os.Parcelable { +    method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public int describeContents(); +    method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") @NonNull public String getName(); +    method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public int getType(); +    method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public void writeToParcel(@NonNull android.os.Parcel, int); +    field @FlaggedApi("com.android.server.power.optimization.power_monitor_api") @NonNull public static final android.os.Parcelable.Creator<android.os.PowerMonitor> CREATOR; +    field @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public static final int POWER_MONITOR_TYPE_CONSUMER = 0; // 0x0 +    field @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public static final int POWER_MONITOR_TYPE_MEASUREMENT = 1; // 0x1 +  } + +  @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public final class PowerMonitorReadings { +    method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public long getConsumedEnergy(@NonNull android.os.PowerMonitor); +    method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public long getTimestamp(@NonNull android.os.PowerMonitor); +    field @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public static final int ENERGY_UNAVAILABLE = -1; // 0xffffffff +  } +    public class Process {      ctor public Process();      method public static final long getElapsedCpuTime(); @@ -33656,6 +33673,8 @@ package android.os.health {    }    public class SystemHealthManager { +    method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public void getPowerMonitorReadings(@NonNull java.util.List<android.os.PowerMonitor>, @Nullable android.os.Handler, @NonNull java.util.function.Consumer<android.os.PowerMonitorReadings>, @NonNull java.util.function.Consumer<java.lang.RuntimeException>); +    method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public void getSupportedPowerMonitors(@Nullable android.os.Handler, @NonNull java.util.function.Consumer<java.util.List<android.os.PowerMonitor>>);      method public android.os.health.HealthStats takeMyUidSnapshot();      method public android.os.health.HealthStats takeUidSnapshot(int);      method public android.os.health.HealthStats[] takeUidSnapshots(int[]); @@ -43531,7 +43550,6 @@ package android.telephony {      method public int getLongitude();      method public int getNetworkId();      method public int getSystemId(); -    method public void writeToParcel(android.os.Parcel, int);      field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityCdma> CREATOR;    } @@ -43547,7 +43565,6 @@ package android.telephony {      method @Nullable public String getMncString();      method @Nullable public String getMobileNetworkOperator();      method @Deprecated public int getPsc(); -    method public void writeToParcel(android.os.Parcel, int);      field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityGsm> CREATOR;    } @@ -43565,7 +43582,6 @@ package android.telephony {      method @Nullable public String getMobileNetworkOperator();      method public int getPci();      method public int getTac(); -    method public void writeToParcel(android.os.Parcel, int);      field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityLte> CREATOR;    } @@ -43578,7 +43594,6 @@ package android.telephony {      method @IntRange(from=0, to=3279165) public int getNrarfcn();      method @IntRange(from=0, to=1007) public int getPci();      method @IntRange(from=0, to=16777215) public int getTac(); -    method public void writeToParcel(android.os.Parcel, int);      field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityNr> CREATOR;    } @@ -43592,7 +43607,6 @@ package android.telephony {      method @Nullable public String getMncString();      method @Nullable public String getMobileNetworkOperator();      method public int getUarfcn(); -    method public void writeToParcel(android.os.Parcel, int);      field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityTdscdma> CREATOR;    } @@ -43608,7 +43622,6 @@ package android.telephony {      method @Nullable public String getMobileNetworkOperator();      method public int getPsc();      method public int getUarfcn(); -    method public void writeToParcel(android.os.Parcel, int);      field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellIdentityWcdma> CREATOR;    } @@ -43692,6 +43705,7 @@ package android.telephony {    public final class CellSignalStrengthCdma extends android.telephony.CellSignalStrength implements android.os.Parcelable {      method public int describeContents(); +    method public boolean equals(Object);      method public int getAsuLevel();      method public int getCdmaDbm();      method public int getCdmaEcio(); @@ -43702,24 +43716,28 @@ package android.telephony {      method public int getEvdoLevel();      method public int getEvdoSnr();      method @IntRange(from=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN, to=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_GREAT) public int getLevel(); +    method public int hashCode();      method public void writeToParcel(android.os.Parcel, int);      field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthCdma> CREATOR;    }    public final class CellSignalStrengthGsm extends android.telephony.CellSignalStrength implements android.os.Parcelable {      method public int describeContents(); +    method public boolean equals(Object);      method public int getAsuLevel();      method public int getBitErrorRate();      method public int getDbm();      method @IntRange(from=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN, to=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_GREAT) public int getLevel();      method public int getRssi();      method public int getTimingAdvance(); +    method public int hashCode();      method public void writeToParcel(android.os.Parcel, int);      field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthGsm> CREATOR;    }    public final class CellSignalStrengthLte extends android.telephony.CellSignalStrength implements android.os.Parcelable {      method public int describeContents(); +    method public boolean equals(Object);      method public int getAsuLevel();      method @IntRange(from=0, to=15) public int getCqi();      method @IntRange(from=1, to=6) public int getCqiTableIndex(); @@ -43730,12 +43748,14 @@ package android.telephony {      method public int getRssi();      method public int getRssnr();      method public int getTimingAdvance(); +    method public int hashCode();      method public void writeToParcel(android.os.Parcel, int);      field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthLte> CREATOR;    }    public final class CellSignalStrengthNr extends android.telephony.CellSignalStrength implements android.os.Parcelable {      method public int describeContents(); +    method public boolean equals(Object);      method public int getAsuLevel();      method @IntRange(from=0, to=15) @NonNull public java.util.List<java.lang.Integer> getCsiCqiReport();      method @IntRange(from=1, to=3) public int getCsiCqiTableIndex(); @@ -43748,26 +43768,31 @@ package android.telephony {      method public int getSsRsrq();      method public int getSsSinr();      method @IntRange(from=0, to=1282) public int getTimingAdvanceMicros(); +    method public int hashCode();      method public void writeToParcel(android.os.Parcel, int);      field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthNr> CREATOR;    }    public final class CellSignalStrengthTdscdma extends android.telephony.CellSignalStrength implements android.os.Parcelable {      method public int describeContents(); +    method public boolean equals(Object);      method public int getAsuLevel();      method public int getDbm();      method @IntRange(from=0, to=4) public int getLevel();      method public int getRscp(); +    method public int hashCode();      method public void writeToParcel(android.os.Parcel, int);      field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthTdscdma> CREATOR;    }    public final class CellSignalStrengthWcdma extends android.telephony.CellSignalStrength implements android.os.Parcelable {      method public int describeContents(); +    method public boolean equals(Object);      method public int getAsuLevel();      method public int getDbm();      method public int getEcNo();      method @IntRange(from=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN, to=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_GREAT) public int getLevel(); +    method public int hashCode();      method public void writeToParcel(android.os.Parcel, int);      field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthWcdma> CREATOR;    } @@ -46555,6 +46580,7 @@ package android.text {      method @Deprecated public int length();      method @Deprecated public static android.text.AlteredCharSequence make(CharSequence, char[], int, int);      method @Deprecated public CharSequence subSequence(int, int); +    method @Deprecated public String toString();    }    @Deprecated public class AndroidCharacter { @@ -47006,6 +47032,7 @@ package android.text {      method public void removeSpan(Object);      method public void setSpan(Object, int, int, int);      method public CharSequence subSequence(int, int); +    method public String toString();    }    public static final class PrecomputedText.Params { @@ -47135,6 +47162,7 @@ package android.text {      method public void setFilters(android.text.InputFilter[]);      method public void setSpan(Object, int, int, int);      method public CharSequence subSequence(int, int); +    method public String toString();      method public static android.text.SpannableStringBuilder valueOf(CharSequence);    } @@ -48775,7 +48803,9 @@ package android.util {      method public boolean containsValue(Object);      method public void ensureCapacity(int);      method public java.util.Set<java.util.Map.Entry<K,V>> entrySet(); +    method public boolean equals(@Nullable Object);      method public V get(Object); +    method public int hashCode();      method public int indexOfKey(Object);      method public int indexOfValue(Object);      method public boolean isEmpty(); @@ -48807,7 +48837,9 @@ package android.util {      method public boolean contains(Object);      method public boolean containsAll(java.util.Collection<?>);      method public void ensureCapacity(int); +    method public boolean equals(@Nullable Object);      method public void forEach(java.util.function.Consumer<? super E>); +    method public int hashCode();      method public int indexOf(Object);      method public boolean isEmpty();      method public java.util.Iterator<E> iterator(); @@ -57546,6 +57578,7 @@ package android.widget {      ctor @Deprecated public AbsoluteLayout(android.content.Context, android.util.AttributeSet);      ctor @Deprecated public AbsoluteLayout(android.content.Context, android.util.AttributeSet, int);      ctor @Deprecated public AbsoluteLayout(android.content.Context, android.util.AttributeSet, int, int); +    method @Deprecated protected void onLayout(boolean, int, int, int, int);    }    @Deprecated public static class AbsoluteLayout.LayoutParams extends android.view.ViewGroup.LayoutParams { @@ -57624,6 +57657,7 @@ package android.widget {      method public long getSelectedItemId();      method public int getSelectedItemPosition();      method public abstract android.view.View getSelectedView(); +    method protected void onLayout(boolean, int, int, int, int);      method public boolean performItemClick(android.view.View, int, long);      method public abstract void setAdapter(T);      method public void setEmptyView(android.view.View); @@ -58270,6 +58304,7 @@ package android.widget {      method public android.widget.FrameLayout.LayoutParams generateLayoutParams(android.util.AttributeSet);      method @Deprecated public boolean getConsiderGoneChildrenWhenMeasuring();      method public boolean getMeasureAllChildren(); +    method protected void onLayout(boolean, int, int, int, int);      method public void setMeasureAllChildren(boolean);    } @@ -58323,6 +58358,7 @@ package android.widget {      method public boolean getUseDefaultMargins();      method public boolean isColumnOrderPreserved();      method public boolean isRowOrderPreserved(); +    method protected void onLayout(boolean, int, int, int, int);      method public void setAlignmentMode(int);      method public void setColumnCount(int);      method public void setColumnOrderPreserved(boolean); @@ -58545,6 +58581,7 @@ package android.widget {      method public float getWeightSum();      method public boolean isBaselineAligned();      method public boolean isMeasureWithLargestChildEnabled(); +    method protected void onLayout(boolean, int, int, int, int);      method public void setBaselineAligned(boolean);      method public void setBaselineAlignedChildIndex(int);      method public void setDividerDrawable(android.graphics.drawable.Drawable); @@ -59093,6 +59130,7 @@ package android.widget {      method public android.widget.RelativeLayout.LayoutParams generateLayoutParams(android.util.AttributeSet);      method public int getGravity();      method public int getIgnoreGravity(); +    method protected void onLayout(boolean, int, int, int, int);      method public void setGravity(int);      method public void setHorizontalGravity(int);      method public void setIgnoreGravity(int); @@ -59547,6 +59585,7 @@ package android.widget {      method @Deprecated public boolean isMoving();      method @Deprecated public boolean isOpened();      method @Deprecated public void lock(); +    method @Deprecated protected void onLayout(boolean, int, int, int, int);      method @Deprecated public void open();      method @Deprecated public void setOnDrawerCloseListener(android.widget.SlidingDrawer.OnDrawerCloseListener);      method @Deprecated public void setOnDrawerOpenListener(android.widget.SlidingDrawer.OnDrawerOpenListener); @@ -60193,6 +60232,7 @@ package android.widget {      method public boolean hideOverflowMenu();      method public void inflateMenu(@MenuRes int);      method public boolean isOverflowMenuShowing(); +    method protected void onLayout(boolean, int, int, int, int);      method public void setCollapseContentDescription(@StringRes int);      method public void setCollapseContentDescription(@Nullable CharSequence);      method public void setCollapseIcon(@DrawableRes int); @@ -60347,7 +60387,7 @@ package android.widget {      method @Deprecated public android.view.View getZoomControls();      method @Deprecated public boolean isAutoDismissed();      method @Deprecated public boolean isVisible(); -    method public boolean onTouch(android.view.View, android.view.MotionEvent); +    method @Deprecated public boolean onTouch(android.view.View, android.view.MotionEvent);      method @Deprecated public void setAutoDismissed(boolean);      method @Deprecated public void setFocusable(boolean);      method @Deprecated public void setOnZoomListener(android.widget.ZoomButtonsController.OnZoomListener); diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index 500a12cacc3b..b5d3ed7c8a7e 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -1,6 +1,4 @@  // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704  package android {    public static final class Manifest.permission { diff --git a/core/api/module-lib-removed.txt b/core/api/module-lib-removed.txt index 14191ebcb080..d802177e249b 100644 --- a/core/api/module-lib-removed.txt +++ b/core/api/module-lib-removed.txt @@ -1,3 +1 @@  // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704 diff --git a/core/api/removed.txt b/core/api/removed.txt index e2b4e4dfc6c4..5a4be65ef559 100644 --- a/core/api/removed.txt +++ b/core/api/removed.txt @@ -1,6 +1,4 @@  // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704  package android.app {    public class Notification implements android.os.Parcelable { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 7dcc7b2cab13..5d1f6dc3e675 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -1,6 +1,4 @@  // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704  package android {    public static final class Manifest.permission { @@ -299,6 +297,7 @@ 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 public static final String RECEIVE_WIFI_CREDENTIAL_CHANGE = "android.permission.RECEIVE_WIFI_CREDENTIAL_CHANGE";      field public static final String RECORD_BACKGROUND_AUDIO = "android.permission.RECORD_BACKGROUND_AUDIO"; @@ -313,7 +312,7 @@ package android {      field public static final String REMOVE_DRM_CERTIFICATES = "android.permission.REMOVE_DRM_CERTIFICATES";      field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS";      field public static final String RENOUNCE_PERMISSIONS = "android.permission.RENOUNCE_PERMISSIONS"; -    field public static final String REPORT_USAGE_STATS = "android.permission.REPORT_USAGE_STATS"; +    field @FlaggedApi("android.app.usage.report_usage_stats_permission") public static final String REPORT_USAGE_STATS = "android.permission.REPORT_USAGE_STATS";      field @Deprecated public static final String REQUEST_NETWORK_SCORES = "android.permission.REQUEST_NETWORK_SCORES";      field public static final String REQUEST_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE";      field public static final String RESET_PASSWORD = "android.permission.RESET_PASSWORD"; @@ -3556,7 +3555,7 @@ package android.content {      field public static final String ACTION_SHOW_SUSPENDED_APP_DETAILS = "android.intent.action.SHOW_SUSPENDED_APP_DETAILS";      field @Deprecated public static final String ACTION_SIM_STATE_CHANGED = "android.intent.action.SIM_STATE_CHANGED";      field public static final String ACTION_SPLIT_CONFIGURATION_CHANGED = "android.intent.action.SPLIT_CONFIGURATION_CHANGED"; -    field public static final String ACTION_UNARCHIVE_PACKAGE = "android.intent.action.UNARCHIVE_PACKAGE"; +    field @FlaggedApi("android.content.pm.archiving") public static final String ACTION_UNARCHIVE_PACKAGE = "android.intent.action.UNARCHIVE_PACKAGE";      field public static final String ACTION_UPGRADE_SETUP = "android.intent.action.UPGRADE_SETUP";      field public static final String ACTION_USER_ADDED = "android.intent.action.USER_ADDED";      field public static final String ACTION_USER_REMOVED = "android.intent.action.USER_REMOVED"; @@ -4029,7 +4028,7 @@ package android.content.pm {      field @Deprecated public static final int INTENT_FILTER_VERIFICATION_SUCCESS = 1; // 0x1      field @Deprecated public static final int MASK_PERMISSION_FLAGS = 255; // 0xff      field public static final int MATCH_ANY_USER = 4194304; // 0x400000 -    field public static final long MATCH_ARCHIVED_PACKAGES = 4294967296L; // 0x100000000L +    field @FlaggedApi("android.content.pm.archiving") public static final long MATCH_ARCHIVED_PACKAGES = 4294967296L; // 0x100000000L      field public static final int MATCH_CLONE_PROFILE = 536870912; // 0x20000000      field public static final int MATCH_FACTORY_ONLY = 2097152; // 0x200000      field public static final int MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS = 536870912; // 0x20000000 @@ -4060,7 +4059,9 @@ package android.content.pm {    }    public static final class PackageManager.UninstallCompleteCallback implements android.os.Parcelable { +    method public int describeContents();      method public void onUninstallComplete(@NonNull String, int, @Nullable String); +    method public void writeToParcel(@NonNull android.os.Parcel, int);      field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.PackageManager.UninstallCompleteCallback> CREATOR;    } @@ -5960,6 +5961,7 @@ package android.hardware.soundtrigger {    public static final class SoundTrigger.Keyphrase implements android.os.Parcelable {      ctor public SoundTrigger.Keyphrase(int, int, @NonNull java.util.Locale, @NonNull String, @Nullable int[]); +    method public int describeContents();      method public int getId();      method @NonNull public java.util.Locale getLocale();      method public int getRecognitionModes(); @@ -5982,6 +5984,7 @@ package android.hardware.soundtrigger {    public static final class SoundTrigger.KeyphraseSoundModel extends android.hardware.soundtrigger.SoundTrigger.SoundModel implements android.os.Parcelable {      ctor public SoundTrigger.KeyphraseSoundModel(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[], @Nullable android.hardware.soundtrigger.SoundTrigger.Keyphrase[], int);      ctor public SoundTrigger.KeyphraseSoundModel(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[], @Nullable android.hardware.soundtrigger.SoundTrigger.Keyphrase[]); +    method public int describeContents();      method @NonNull public android.hardware.soundtrigger.SoundTrigger.Keyphrase[] getKeyphrases();      method @NonNull public static android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel readFromParcel(@NonNull android.os.Parcel);      method public void writeToParcel(@NonNull android.os.Parcel, int); @@ -5989,6 +5992,7 @@ package android.hardware.soundtrigger {    }    public static final class SoundTrigger.ModelParamRange implements android.os.Parcelable { +    method public int describeContents();      method public int getEnd();      method public int getStart();      method public void writeToParcel(@NonNull android.os.Parcel, int); @@ -6716,7 +6720,7 @@ package android.media.audiopolicy {      method public boolean setUidDeviceAffinity(int, @NonNull java.util.List<android.media.AudioDeviceInfo>);      method public boolean setUserIdDeviceAffinity(int, @NonNull java.util.List<android.media.AudioDeviceInfo>);      method public String toLogFriendlyString(); -    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int updateMixingRules(@NonNull java.util.List<android.util.Pair<android.media.audiopolicy.AudioMix,android.media.audiopolicy.AudioMixingRule>>); +    method @FlaggedApi("com.android.media.audio.flags.audio_policy_update_mixing_rules_api") @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int updateMixingRules(@NonNull java.util.List<android.util.Pair<android.media.audiopolicy.AudioMix,android.media.audiopolicy.AudioMixingRule>>);      field public static final int FOCUS_POLICY_DUCKING_DEFAULT = 0; // 0x0      field public static final int FOCUS_POLICY_DUCKING_IN_APP = 0; // 0x0      field public static final int FOCUS_POLICY_DUCKING_IN_POLICY = 1; // 0x1 @@ -6799,6 +6803,7 @@ package android.media.musicrecognition {    public abstract class MusicRecognitionService extends android.app.Service {      ctor public MusicRecognitionService(); +    method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);      method public abstract void onRecognize(@NonNull android.os.ParcelFileDescriptor, @NonNull android.media.AudioFormat, @NonNull android.media.musicrecognition.MusicRecognitionService.Callback);    } @@ -6857,6 +6862,7 @@ package android.media.soundtrigger {    public abstract class SoundTriggerDetectionService extends android.app.Service {      ctor public SoundTriggerDetectionService(); +    method public final android.os.IBinder onBind(android.content.Intent);      method @MainThread public void onConnected(@NonNull java.util.UUID, @Nullable android.os.Bundle);      method @MainThread public void onDisconnected(@NonNull java.util.UUID, @Nullable android.os.Bundle);      method @MainThread public void onError(@NonNull java.util.UUID, @Nullable android.os.Bundle, int, int); @@ -7570,6 +7576,7 @@ package android.media.tv.tuner.filter {    }    public class MediaEvent extends android.media.tv.tuner.filter.FilterEvent { +    method protected void finalize();      method public long getAudioHandle();      method @NonNull public java.util.List<android.media.AudioPresentation> getAudioPresentations();      method public long getAvDataId(); @@ -8964,6 +8971,8 @@ package android.net {  package android.net.metrics {    @Deprecated public final class ApfProgramEvent implements android.net.metrics.IpConnectivityLog.Event { +    method @Deprecated public int describeContents(); +    method @Deprecated public void writeToParcel(android.os.Parcel, int);    }    @Deprecated public static final class ApfProgramEvent.Builder { @@ -8978,6 +8987,8 @@ package android.net.metrics {    }    @Deprecated public final class ApfStats implements android.net.metrics.IpConnectivityLog.Event { +    method @Deprecated public int describeContents(); +    method @Deprecated public void writeToParcel(android.os.Parcel, int);    }    @Deprecated public static final class ApfStats.Builder { @@ -8996,6 +9007,8 @@ package android.net.metrics {    }    @Deprecated public final class DhcpClientEvent implements android.net.metrics.IpConnectivityLog.Event { +    method @Deprecated public int describeContents(); +    method @Deprecated public void writeToParcel(android.os.Parcel, int);    }    @Deprecated public static final class DhcpClientEvent.Builder { @@ -9007,7 +9020,9 @@ package android.net.metrics {    @Deprecated public final class DhcpErrorEvent implements android.net.metrics.IpConnectivityLog.Event {      ctor @Deprecated public DhcpErrorEvent(int); +    method @Deprecated public int describeContents();      method @Deprecated public static int errorCodeWithOption(int, int); +    method @Deprecated public void writeToParcel(android.os.Parcel, int);      field @Deprecated public static final int BOOTP_TOO_SHORT = 67174400; // 0x4010000      field @Deprecated public static final int BUFFER_UNDERFLOW = 83951616; // 0x5010000      field @Deprecated public static final int DHCP_BAD_MAGIC_COOKIE = 67239936; // 0x4020000 @@ -9045,6 +9060,8 @@ package android.net.metrics {    @Deprecated public final class IpManagerEvent implements android.net.metrics.IpConnectivityLog.Event {      ctor @Deprecated public IpManagerEvent(int, long); +    method @Deprecated public int describeContents(); +    method @Deprecated public void writeToParcel(android.os.Parcel, int);      field @Deprecated public static final int COMPLETE_LIFECYCLE = 3; // 0x3      field @Deprecated public static final int ERROR_INTERFACE_NOT_FOUND = 8; // 0x8      field @Deprecated public static final int ERROR_INVALID_PROVISIONING = 7; // 0x7 @@ -9057,6 +9074,8 @@ package android.net.metrics {    @Deprecated public final class IpReachabilityEvent implements android.net.metrics.IpConnectivityLog.Event {      ctor @Deprecated public IpReachabilityEvent(int); +    method @Deprecated public int describeContents(); +    method @Deprecated public void writeToParcel(android.os.Parcel, int);      field @Deprecated public static final int NUD_FAILED = 512; // 0x200      field @Deprecated public static final int NUD_FAILED_ORGANIC = 1024; // 0x400      field @Deprecated public static final int PROBE = 256; // 0x100 @@ -9067,6 +9086,8 @@ package android.net.metrics {    @Deprecated public final class NetworkEvent implements android.net.metrics.IpConnectivityLog.Event {      ctor @Deprecated public NetworkEvent(int, long);      ctor @Deprecated public NetworkEvent(int); +    method @Deprecated public int describeContents(); +    method @Deprecated public void writeToParcel(android.os.Parcel, int);      field @Deprecated public static final int NETWORK_CAPTIVE_PORTAL_FOUND = 4; // 0x4      field @Deprecated public static final int NETWORK_CONNECTED = 1; // 0x1      field @Deprecated public static final int NETWORK_CONSECUTIVE_DNS_TIMEOUT_FOUND = 12; // 0xc @@ -9083,6 +9104,8 @@ package android.net.metrics {    }    @Deprecated public final class RaEvent implements android.net.metrics.IpConnectivityLog.Event { +    method @Deprecated public int describeContents(); +    method @Deprecated public void writeToParcel(android.os.Parcel, int);    }    @Deprecated public static final class RaEvent.Builder { @@ -9097,7 +9120,9 @@ package android.net.metrics {    }    @Deprecated public final class ValidationProbeEvent implements android.net.metrics.IpConnectivityLog.Event { +    method @Deprecated public int describeContents();      method @Deprecated @NonNull public static String getProbeName(int); +    method @Deprecated public void writeToParcel(android.os.Parcel, int);      field @Deprecated public static final int DNS_FAILURE = 0; // 0x0      field @Deprecated public static final int DNS_SUCCESS = 1; // 0x1      field @Deprecated public static final int PROBE_DNS = 0; // 0x0 @@ -11502,6 +11527,7 @@ package android.service.assist.classification {    public abstract class FieldClassificationService extends android.app.Service {      ctor public FieldClassificationService(); +    method public final android.os.IBinder onBind(android.content.Intent);      method public abstract void onClassificationRequest(@NonNull android.service.assist.classification.FieldClassificationRequest, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.service.assist.classification.FieldClassificationResponse,java.lang.Exception>);      method public void onConnected();      method public void onDisconnected(); @@ -11580,6 +11606,7 @@ package android.service.autofill.augmented {      method protected final void dump(java.io.FileDescriptor, java.io.PrintWriter, String[]);      method protected void dump(@NonNull java.io.PrintWriter, @NonNull String[]);      method @Nullable public final android.service.autofill.FillEventHistory getFillEventHistory(); +    method public final android.os.IBinder onBind(android.content.Intent);      method public void onConnected();      method public void onDisconnected();      method public void onFillRequest(@NonNull android.service.autofill.augmented.FillRequest, @NonNull android.os.CancellationSignal, @NonNull android.service.autofill.augmented.FillController, @NonNull android.service.autofill.augmented.FillCallback); @@ -11618,6 +11645,7 @@ package android.service.autofill.augmented {    public final class FillWindow implements java.lang.AutoCloseable {      ctor public FillWindow(); +    method public void close();      method public void destroy();      method public boolean update(@NonNull android.service.autofill.augmented.PresentationParams.Area, @NonNull android.view.View, long);    } @@ -11643,6 +11671,7 @@ package android.service.carrier {    public final class CarrierMessagingServiceWrapper implements java.lang.AutoCloseable {      ctor public CarrierMessagingServiceWrapper();      method public boolean bindToCarrierMessagingService(@NonNull android.content.Context, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull Runnable); +    method public void close();      method public void disconnect();      method public void downloadMms(@NonNull android.net.Uri, int, @NonNull android.net.Uri, @NonNull java.util.concurrent.Executor, @NonNull android.service.carrier.CarrierMessagingServiceWrapper.CarrierMessagingCallback);      method public void receiveSms(@NonNull android.service.carrier.MessagePdu, @NonNull String, int, int, @NonNull java.util.concurrent.Executor, @NonNull android.service.carrier.CarrierMessagingServiceWrapper.CarrierMessagingCallback); @@ -11693,6 +11722,7 @@ package android.service.contentcapture {      method public final void disableSelf();      method public void onActivityEvent(@NonNull android.service.contentcapture.ActivityEvent);      method public void onActivitySnapshot(@NonNull android.view.contentcapture.ContentCaptureSessionId, @NonNull android.service.contentcapture.SnapshotData); +    method public final android.os.IBinder onBind(android.content.Intent);      method public void onConnected();      method public void onContentCaptureEvent(@NonNull android.view.contentcapture.ContentCaptureSessionId, @NonNull android.view.contentcapture.ContentCaptureEvent);      method public void onCreateContentCaptureSession(@NonNull android.view.contentcapture.ContentCaptureContext, @NonNull android.view.contentcapture.ContentCaptureSessionId); @@ -11731,6 +11761,7 @@ package android.service.contentsuggestions {    public abstract class ContentSuggestionsService extends android.app.Service {      ctor public ContentSuggestionsService(); +    method public final android.os.IBinder onBind(android.content.Intent);      method public abstract void onClassifyContentSelections(@NonNull android.app.contentsuggestions.ClassificationsRequest, @NonNull android.app.contentsuggestions.ContentSuggestionsManager.ClassificationsCallback);      method public abstract void onNotifyInteraction(@NonNull String, @NonNull android.os.Bundle);      method public abstract void onProcessContextImage(int, @Nullable android.graphics.Bitmap, @NonNull android.os.Bundle); @@ -11744,6 +11775,7 @@ package android.service.dataloader {    public abstract class DataLoaderService extends android.app.Service {      ctor public DataLoaderService(); +    method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);      method @Nullable public android.service.dataloader.DataLoaderService.DataLoader onCreateDataLoader(@NonNull android.content.pm.DataLoaderParams);    } @@ -12416,6 +12448,7 @@ package android.service.tracing {    public class TraceReportService extends android.app.Service {      ctor public TraceReportService(); +    method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);      method public void onReportTrace(@NonNull android.service.tracing.TraceReportService.TraceParams);    } @@ -13041,6 +13074,7 @@ package android.telecom {      method @Nullable public android.content.ComponentName getCallScreeningComponent();      method public boolean isBlocked();      method public boolean isInContacts(); +    method public void writeToParcel(android.os.Parcel, int);      field @NonNull public static final android.os.Parcelable.Creator<android.telecom.Connection.CallFilteringCompletionInfo> CREATOR;    } @@ -13350,6 +13384,7 @@ package android.telephony {      method public int getReason();      method public int getTimeoutSeconds();      method public boolean isEnabled(); +    method public void writeToParcel(android.os.Parcel, int);      field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallForwardingInfo> CREATOR;      field public static final int REASON_ALL = 4; // 0x4      field public static final int REASON_ALL_CONDITIONAL = 5; // 0x5 @@ -13634,6 +13669,7 @@ package android.telephony {      method public int getDownlinkCapacityKbps();      method public int getType();      method public int getUplinkCapacityKbps(); +    method public void writeToParcel(@NonNull android.os.Parcel, int);      field @NonNull public static final android.os.Parcelable.Creator<android.telephony.LinkCapacityEstimate> CREATOR;      field public static final int INVALID = -1; // 0xffffffff      field public static final int LCE_TYPE_COMBINED = 2; // 0x2 @@ -13643,8 +13679,10 @@ package android.telephony {    public final class LteVopsSupportInfo extends android.telephony.VopsSupportInfo {      ctor public LteVopsSupportInfo(int, int); +    method public boolean equals(@Nullable Object);      method public int getEmcBearerSupport();      method public int getVopsSupport(); +    method public int hashCode();      method public boolean isEmergencyServiceFallbackSupported();      method public boolean isEmergencyServiceSupported();      method public boolean isVopsSupported(); @@ -13745,9 +13783,11 @@ package android.telephony {    public final class NrVopsSupportInfo extends android.telephony.VopsSupportInfo {      ctor public NrVopsSupportInfo(int, int, int); +    method public boolean equals(@Nullable Object);      method public int getEmcSupport();      method public int getEmfSupport();      method public int getVopsSupport(); +    method public int hashCode();      method public boolean isEmergencyServiceFallbackSupported();      method public boolean isEmergencyServiceSupported();      method public boolean isVopsSupported(); @@ -14860,6 +14900,7 @@ package android.telephony.data {    public abstract class QualifiedNetworksService extends android.app.Service {      ctor public QualifiedNetworksService(); +    method public android.os.IBinder onBind(android.content.Intent);      method @NonNull public abstract android.telephony.data.QualifiedNetworksService.NetworkAvailabilityProvider onCreateNetworkAvailabilityProvider(int);      field public static final String QUALIFIED_NETWORKS_SERVICE_INTERFACE = "android.telephony.data.QualifiedNetworksService";    } @@ -15051,6 +15092,7 @@ package android.telephony.gba {    public class GbaService extends android.app.Service {      ctor public GbaService();      method public void onAuthenticationRequest(int, int, int, @NonNull android.net.Uri, @NonNull byte[], boolean); +    method public android.os.IBinder onBind(android.content.Intent);      method public final void reportAuthenticationFailure(int, int) throws java.lang.RuntimeException;      method public final void reportKeysAvailable(int, @NonNull byte[], @NonNull String) throws java.lang.RuntimeException;      field public static final String SERVICE_INTERFACE = "android.telephony.gba.GbaService"; @@ -15553,6 +15595,7 @@ package android.telephony.ims {      method @Deprecated public android.telephony.ims.stub.ImsRegistrationImplBase getRegistration(int);      method @NonNull public android.telephony.ims.stub.ImsRegistrationImplBase getRegistrationForSubscription(int, int);      method @Nullable public android.telephony.ims.stub.SipTransportImplBase getSipTransport(int); +    method public android.os.IBinder onBind(android.content.Intent);      method public final void onUpdateSupportedImsFeatures(android.telephony.ims.stub.ImsFeatureConfiguration) throws android.os.RemoteException;      method public android.telephony.ims.stub.ImsFeatureConfiguration querySupportedImsFeatures();      method public void readyForFeatureCreation(); @@ -16698,6 +16741,23 @@ package android.telephony.satellite {      field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.AntennaPosition> CREATOR;    } +  @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class NtnSignalStrength implements android.os.Parcelable { +    ctor @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public NtnSignalStrength(@Nullable android.telephony.satellite.NtnSignalStrength); +    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int describeContents(); +    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int getLevel(); +    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void writeToParcel(@NonNull android.os.Parcel, int); +    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.NtnSignalStrength> CREATOR; +    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NTN_SIGNAL_STRENGTH_GOOD = 3; // 0x3 +    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NTN_SIGNAL_STRENGTH_GREAT = 4; // 0x4 +    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NTN_SIGNAL_STRENGTH_MODERATE = 2; // 0x2 +    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NTN_SIGNAL_STRENGTH_NONE = 0; // 0x0 +    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NTN_SIGNAL_STRENGTH_POOR = 1; // 0x1 +  } + +  @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public interface NtnSignalStrengthCallback { +    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onNtnSignalStrengthChanged(@NonNull android.telephony.satellite.NtnSignalStrength); +  } +    @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class PointingInfo implements android.os.Parcelable {      method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int describeContents();      method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public float getSatelliteAzimuthDegrees(); @@ -16733,6 +16793,7 @@ package android.telephony.satellite {      method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.Set<java.lang.Integer> getSatelliteAttachRestrictionReasonsForCarrier(int);      method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void pollPendingSatelliteDatagrams(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);      method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void provisionSatelliteService(@NonNull String, @NonNull byte[], @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); +    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForNtnSignalStrengthChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.NtnSignalStrengthCallback);      method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteDatagram(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteDatagramCallback);      method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteModemStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteStateCallback);      method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteProvisionStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteProvisionStateCallback); @@ -16743,6 +16804,7 @@ package android.telephony.satellite {      method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteEnabled(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);      method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteProvisioned(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);      method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void requestIsSatelliteSupported(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); +    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestNtnSignalStrength(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.satellite.NtnSignalStrength,android.telephony.satellite.SatelliteManager.SatelliteException>);      method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteAttachEnabledForCarrier(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);      method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteCapabilities(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.satellite.SatelliteCapabilities,android.telephony.satellite.SatelliteManager.SatelliteException>);      method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteEnabled(boolean, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); @@ -16751,6 +16813,7 @@ package android.telephony.satellite {      method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void setDeviceAlignedWithSatellite(boolean);      method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void startSatelliteTransmissionUpdates(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>, @NonNull android.telephony.satellite.SatelliteTransmissionUpdateCallback);      method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void stopSatelliteTransmissionUpdates(@NonNull android.telephony.satellite.SatelliteTransmissionUpdateCallback, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); +    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForNtnSignalStrengthChanged(@NonNull android.telephony.satellite.NtnSignalStrengthCallback);      method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteDatagram(@NonNull android.telephony.satellite.SatelliteDatagramCallback);      method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteModemStateChanged(@NonNull android.telephony.satellite.SatelliteStateCallback);      method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteProvisionStateChanged(@NonNull android.telephony.satellite.SatelliteProvisionStateCallback); diff --git a/core/api/system-removed.txt b/core/api/system-removed.txt index 1fa2718dc6d2..aa17df3471d7 100644 --- a/core/api/system-removed.txt +++ b/core/api/system-removed.txt @@ -1,6 +1,4 @@  // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704  package android.app {    public class AppOpsManager { diff --git a/core/api/test-current.txt b/core/api/test-current.txt index eeddeb21aa9d..b9052873243e 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1,6 +1,4 @@  // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704  package android {    public static final class Manifest.permission { @@ -3187,7 +3185,6 @@ package android.telephony {      field public static final int HAL_SERVICE_MESSAGING = 2; // 0x2      field public static final int HAL_SERVICE_MODEM = 3; // 0x3      field public static final int HAL_SERVICE_NETWORK = 4; // 0x4 -    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int HAL_SERVICE_SATELLITE = 8; // 0x8      field public static final int HAL_SERVICE_SIM = 5; // 0x5      field public static final int HAL_SERVICE_VOICE = 6; // 0x6      field public static final android.util.Pair HAL_VERSION_UNKNOWN; @@ -3587,8 +3584,8 @@ package android.view {      field public static final int ACCESSIBILITY_TITLE_CHANGED = 33554432; // 0x2000000      field public static final int FLAG_SLIPPERY = 536870912; // 0x20000000      field public CharSequence accessibilityTitle; -    field public float preferredMaxDisplayRefreshRate; -    field public float preferredMinDisplayRefreshRate; +    field @FlaggedApi("android.view.flags.wm_display_refresh_rate_test") public float preferredMaxDisplayRefreshRate; +    field @FlaggedApi("android.view.flags.wm_display_refresh_rate_test") public float preferredMinDisplayRefreshRate;      field public int privateFlags;    } @@ -3618,7 +3615,6 @@ package android.view.accessibility {    public final class AccessibilityWindowInfo implements android.os.Parcelable {      method public static void setNumInstancesInUseCounter(java.util.concurrent.atomic.AtomicInteger); -    field public static final int UNDEFINED_WINDOW_ID = -1; // 0xffffffff    }  } @@ -3946,7 +3942,9 @@ package android.widget.inline {  package android.window {    public final class BackNavigationInfo implements android.os.Parcelable { +    method public int describeContents();      method @NonNull public static String typeToString(int); +    method public void writeToParcel(@NonNull android.os.Parcel, int);      field @NonNull public static final android.os.Parcelable.Creator<android.window.BackNavigationInfo> CREATOR;      field public static final String KEY_TRIGGER_BACK = "TriggerBack";      field public static final int TYPE_CALLBACK = 4; // 0x4 @@ -4007,11 +4005,13 @@ package android.window {    }    public final class TaskFragmentCreationParams implements android.os.Parcelable { +    method public int describeContents();      method @NonNull public android.os.IBinder getFragmentToken();      method @NonNull public android.graphics.Rect getInitialRelativeBounds();      method @NonNull public android.window.TaskFragmentOrganizerToken getOrganizer();      method @NonNull public android.os.IBinder getOwnerToken();      method public int getWindowingMode(); +    method public void writeToParcel(@NonNull android.os.Parcel, int);      field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentCreationParams> CREATOR;    } @@ -4023,6 +4023,7 @@ package android.window {    }    public final class TaskFragmentInfo implements android.os.Parcelable { +    method public int describeContents();      method public boolean equalsForTaskFragmentOrganizer(@Nullable android.window.TaskFragmentInfo);      method @NonNull public java.util.List<android.os.IBinder> getActivities();      method @NonNull public java.util.List<android.os.IBinder> getActivitiesRequestedInTaskFragment(); @@ -4036,6 +4037,7 @@ package android.window {      method public boolean isEmpty();      method public boolean isTaskClearedForReuse();      method public boolean isVisible(); +    method public void writeToParcel(@NonNull android.os.Parcel, int);      field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentInfo> CREATOR;    } @@ -4058,6 +4060,8 @@ package android.window {    }    public final class TaskFragmentOrganizerToken implements android.os.Parcelable { +    method public int describeContents(); +    method public void writeToParcel(android.os.Parcel, int);      field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentOrganizerToken> CREATOR;    } diff --git a/core/api/test-removed.txt b/core/api/test-removed.txt index 14191ebcb080..d802177e249b 100644 --- a/core/api/test-removed.txt +++ b/core/api/test-removed.txt @@ -1,3 +1 @@  // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704 diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index 3370c121acfe..1000612ee0e2 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -23,6 +23,7 @@ import android.accessibilityservice.GestureDescription.MotionEventGenerator;  import android.annotation.CallbackExecutor;  import android.annotation.CheckResult;  import android.annotation.ColorInt; +import android.annotation.FlaggedApi;  import android.annotation.IntDef;  import android.annotation.NonNull;  import android.annotation.Nullable; @@ -793,6 +794,7 @@ public abstract class AccessibilityService extends Service {       * @hide       */      @Retention(RetentionPolicy.SOURCE) +    @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks")      @IntDef(              prefix = {"OVERLAY_RESULT_"},              value = { @@ -803,6 +805,7 @@ public abstract class AccessibilityService extends Service {      public @interface AttachOverlayResult {}      /** Result code indicating the overlay was successfully attached. */ +    @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks")      public static final int OVERLAY_RESULT_SUCCESS = 0;      /** @@ -810,6 +813,7 @@ public abstract class AccessibilityService extends Service {       * error and not       * because of problems with the input.       */ +    @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks")      public static final int OVERLAY_RESULT_INTERNAL_ERROR = 1;      /** @@ -817,6 +821,7 @@ public abstract class AccessibilityService extends Service {       * specified display or       * window id was invalid.       */ +    @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks")      public static final int OVERLAY_RESULT_INVALID = 2;      private int mConnectionId = AccessibilityInteractionClient.NO_ID; @@ -3506,11 +3511,7 @@ public abstract class AccessibilityService extends Service {       * @param displayId the display to which the SurfaceControl should be attached.       * @param sc the SurfaceControl containing the overlay content       * -     * @deprecated Use -     * {@link #attachAccessibilityOverlayToDisplay(int, SurfaceControl, Executor, IntConsumer)} -     * instead.       */ -    @Deprecated      public void attachAccessibilityOverlayToDisplay(int displayId, @NonNull SurfaceControl sc) {          Preconditions.checkNotNull(sc, "SurfaceControl cannot be null");          AccessibilityInteractionClient.getInstance(this) @@ -3547,6 +3548,7 @@ public abstract class AccessibilityService extends Service {       * @see #OVERLAY_RESULT_INVALID       * @see #OVERLAY_RESULT_INTERNAL_ERROR       */ +    @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks")      public void attachAccessibilityOverlayToDisplay(              int displayId,              @NonNull SurfaceControl sc, @@ -3581,11 +3583,7 @@ public abstract class AccessibilityService extends Service {       * @param accessibilityWindowId The window id, from {@link AccessibilityWindowInfo#getId()}.       * @param sc the SurfaceControl containing the overlay content       * -     * @deprecated Use -     * {@link #attachAccessibilityOverlayToWindow(int, SurfaceControl, Executor,IntConsumer)} -     * instead.       */ -    @Deprecated      public void attachAccessibilityOverlayToWindow(              int accessibilityWindowId, @NonNull SurfaceControl sc) {          Preconditions.checkNotNull(sc, "SurfaceControl cannot be null"); @@ -3623,6 +3621,7 @@ public abstract class AccessibilityService extends Service {       * @see #OVERLAY_RESULT_INVALID       * @see #OVERLAY_RESULT_INTERNAL_ERROR       */ +    @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks")      public void attachAccessibilityOverlayToWindow(              int accessibilityWindowId,              @NonNull SurfaceControl sc, diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index 895dde760058..f2c00517ad16 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -2073,9 +2073,7 @@ public class ActivityOptions extends ComponentOptions {       * Allow a {@link PendingIntent} to use the privilege of its creator to start background       * activities.       * -     * @param mode the {@link android.app.ComponentOptions.BackgroundActivityStartMode} being set -     * @throws IllegalArgumentException is the value is not a valid -     * {@link android.app.ComponentOptions.BackgroundActivityStartMode} +     * @param mode the mode being set       */      @NonNull      public ActivityOptions setPendingIntentCreatorBackgroundActivityStartMode( @@ -2088,7 +2086,7 @@ public class ActivityOptions extends ComponentOptions {       * Returns the mode to start background activities granted by the creator of the       * {@link PendingIntent}.       * -     * @return the {@link android.app.ComponentOptions.BackgroundActivityStartMode} currently set +     * @return the mode currently set       */      public @BackgroundActivityStartMode int getPendingIntentCreatorBackgroundActivityStartMode() {          return mPendingIntentCreatorBackgroundActivityStartMode; diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 17637df90b99..ecbc9b1d52c2 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -1487,13 +1487,13 @@ public class AppOpsManager {              AppProtoEnums.APP_OP_RECEIVE_SANDBOX_TRIGGER_AUDIO;      /** -     * Allows the assistant app to get the training data from the trusted process to improve the -     * hotword training model. +     * Allows the privileged assistant app to receive the training data from the sandboxed hotword +     * detection service.       *       * @hide       */ -    public static final int OP_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA = -            AppProtoEnums.APP_OP_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA; +    public static final int OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA = +            AppProtoEnums.APP_OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA;      /** @hide */      @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @@ -1641,7 +1641,7 @@ public class AppOpsManager {              OPSTR_CAMERA_SANDBOXED,              OPSTR_RECORD_AUDIO_SANDBOXED,              OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO, -            OPSTR_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA +            OPSTR_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA      })      public @interface AppOpString {} @@ -2262,13 +2262,13 @@ public class AppOpsManager {              "android:receive_sandbox_trigger_audio";      /** -     * Allows the assistant app to get the training data from the trusted process to improve -     * the hotword training model. +     * Allows the privileged assistant app to receive training data from the sandboxed hotword +     * detection service.       *       * @hide       */ -    public static final String OPSTR_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA = -            "android:receive_trusted_process_training_data"; +    public static final String OPSTR_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA = +            "android:RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA";      /** {@link #sAppOpsToNote} not initialized yet for this op */      private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0; @@ -2381,7 +2381,8 @@ public class AppOpsManager {              OP_FOREGROUND_SERVICE_SPECIAL_USE,              OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD,              OP_USE_FULL_SCREEN_INTENT, -            OP_RECEIVE_SANDBOX_TRIGGER_AUDIO +            OP_RECEIVE_SANDBOX_TRIGGER_AUDIO, +            OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA      };      static final AppOpInfo[] sAppOpInfos = new AppOpInfo[]{ @@ -2814,9 +2815,11 @@ public class AppOpsManager {                  "RECEIVE_SANDBOX_TRIGGER_AUDIO")                  .setPermission(Manifest.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO)                  .setDefaultMode(AppOpsManager.MODE_DEFAULT).build(), -        new AppOpInfo.Builder(OP_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA, -                OPSTR_RECEIVE_TRUSTED_PROCESS_TRAINING_DATA, -                "RECEIVE_TRUSTED_PROCESS_TRAINING_DATA").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()      };      // The number of longs needed to form a full bitmask of app ops diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java index 9549ebf698a8..41b400459526 100644 --- a/core/java/android/app/BroadcastOptions.java +++ b/core/java/android/app/BroadcastOptions.java @@ -843,8 +843,7 @@ public class BroadcastOptions extends ComponentOptions {       * considered to be in the same delivery group as this iff it has the same {@code namespace}       * and {@code key}.       * -     * <p> If neither matching key using this API nor matching filter using -     * {@link #setDeliveryGroupMatchingFilter(IntentFilter)} is specified, then by default +     * <p> If not matching key using this API then by default       * {@link Intent#filterEquals(Intent)} will be used to identify the delivery group.       */      @NonNull diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index 8fea03ba47f5..ebf183ff18c3 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -50,7 +50,6 @@ import android.os.Trace;  import android.os.UserHandle;  import android.provider.Settings;  import android.security.net.config.NetworkSecurityConfigProvider; -import android.sysprop.VndkProperties;  import android.text.TextUtils;  import android.util.AndroidRuntimeException;  import android.util.ArrayMap; @@ -901,14 +900,10 @@ public final class LoadedApk {          }          // Similar to vendor apks, we should add /product/lib for apks from product partition -        // when product apps are marked as unbundled. We cannot use the same way from vendor -        // to check if lib path exists because there is possibility that /product/lib would not -        // exist from legacy device while product apks are bundled. To make this clear, we use -        // "ro.product.vndk.version" property. If the property is defined, we regard all product -        // apks as unbundled. +        // when product apps are marked as unbundled. Product is separated as long as the +        // partition exists, so it can be handled with same approach from the vendor partition.          if (mApplicationInfo.getCodePath() != null -                && mApplicationInfo.isProduct() -                && VndkProperties.product_vndk_version().isPresent()) { +                && mApplicationInfo.isProduct()) {              isBundledApp = false;          } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index dd7db23d668b..94e1292a7554 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -1514,13 +1514,13 @@ public class Notification implements Parcelable      /**       * {@link #extras} key: the color used as a hint for the Answer action button of a -     * {@link android.app.Notification.CallStyle} notification. This extra is a {@link ColorInt}. +     * {@link android.app.Notification.CallStyle} notification. This extra is a {@code ColorInt}.       */      public static final String EXTRA_ANSWER_COLOR = "android.answerColor";      /**       * {@link #extras} key: the color used as a hint for the Decline or Hang Up action button of a -     * {@link android.app.Notification.CallStyle} notification. This extra is a {@link ColorInt}. +     * {@link android.app.Notification.CallStyle} notification. This extra is a {@code ColorInt}.       */      public static final String EXTRA_DECLINE_COLOR = "android.declineColor"; @@ -1704,7 +1704,7 @@ public class Notification implements Parcelable          private static final String EXTRA_DATA_ONLY_INPUTS = "android.extra.DATA_ONLY_INPUTS";          /** -         * {@link }: No semantic action defined. +         * No semantic action defined.           */          public static final int SEMANTIC_ACTION_NONE = 0; @@ -7923,7 +7923,7 @@ public class Notification implements Parcelable       * (via {@link Notification.Builder#setShortcutId(String)}) are displayed in a dedicated       * conversation section in the shade above non-conversation alerting and silence notifications.       * To be a valid conversation shortcut, the shortcut must be a -     * {@link ShortcutInfo#setLongLived()} dynamic or cached sharing shortcut. +     * {@link ShortcutInfo.Builder#setLongLived(boolean)} dynamic or cached sharing shortcut.       *       * <p>       * This class is a "rebuilder": It attaches to a Builder object and modifies its behavior. diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java index 15bd1dcc3980..e2689687f388 100644 --- a/core/java/android/app/assist/AssistStructure.java +++ b/core/java/android/app/assist/AssistStructure.java @@ -651,6 +651,7 @@ public class AssistStructure implements Parcelable {          // POJO used to override some autofill-related values when the node is parcelized.          // Not written to parcel.          AutofillOverlay mAutofillOverlay; +        boolean mIsCredential;          int mX;          int mY; @@ -799,6 +800,7 @@ public class AssistStructure implements Parcelable {              if (autofillFlags != 0) {                  mSanitized = in.readInt() == 1; +                mIsCredential = in.readInt() == 1;                  mImportantForAutofill = in.readInt();                  if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_VIEW_ID) != 0) { @@ -1033,6 +1035,7 @@ public class AssistStructure implements Parcelable {              if (autofillFlags != 0) {                  out.writeInt(mSanitized ? 1 : 0); +                out.writeInt(mIsCredential ? 1 : 0);                  out.writeInt(mImportantForAutofill);                  writeSensitive = mSanitized || !sanitizeOnWrite;                  if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_VIEW_ID) != 0) { @@ -1246,6 +1249,19 @@ public class AssistStructure implements Parcelable {          }          /** +         * @return whether the node is a credential. +         * +         * <p>It's only relevant when the {@link AssistStructure} is used for autofill purposes, +         * not for assist purposes. +         * TODO(b/303677885): add TestApi +         * +         * @hide +         */ +        public boolean isCredential() { +            return mIsCredential; +        } + +        /**           * Gets the {@link android.text.InputType} bits of this structure.           *           * @return bits as defined by {@link android.text.InputType}. @@ -2183,6 +2199,11 @@ public class AssistStructure implements Parcelable {          }          @Override +        public void setIsCredential(boolean isCredential) { +            mNode.mIsCredential = isCredential; +        } + +        @Override          public void setReceiveContentMimeTypes(@Nullable String[] mimeTypes) {              mNode.mReceiveContentMimeTypes = mimeTypes;          } @@ -2498,7 +2519,9 @@ public class AssistStructure implements Parcelable {                      + ", value=" + node.getAutofillValue()                      + ", sanitized=" + node.isSanitized()                      + ", important=" + node.getImportantForAutofill() -                    + ", visibility=" + node.getVisibility()); +                    + ", visibility=" + node.getVisibility() +                    + ", isCredential=" + node.isCredential() +            );          }          final int NCHILDREN = node.getChildCount(); diff --git a/core/java/android/app/usage/flags.aconfig b/core/java/android/app/usage/flags.aconfig index afe87de1dbf5..f401a7607364 100644 --- a/core/java/android/app/usage/flags.aconfig +++ b/core/java/android/app/usage/flags.aconfig @@ -2,8 +2,14 @@ package: "android.app.usage"  flag {      name: "user_interaction_type_api" -    namespace: "power_optimization" +    namespace: "backstage_power"      description: "Feature flag for user interaction event report/query API"      bug: "296061232"  } +flag { +    name: "report_usage_stats_permission" +    namespace: "backstage_power" +    description: "Feature flag for the new REPORT_USAGE_STATS permission." +    bug: "296056771" +} diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java index bfc1eec829e8..62fbcaff79e3 100644 --- a/core/java/android/content/AttributionSource.java +++ b/core/java/android/content/AttributionSource.java @@ -730,7 +730,7 @@ public final class AttributionSource implements Parcelable {          /**           * The next app to receive the permission protected data.           * -         * @deprecated Use {@link setNextAttributionSource} instead. +         * @deprecated Use {@link #setNextAttributionSource} instead.           */          @Deprecated          public @NonNull Builder setNext(@Nullable AttributionSource value) { @@ -744,6 +744,7 @@ public final class AttributionSource implements Parcelable {          /**           * The next app to receive the permission protected data.           */ +        @FlaggedApi(Flags.FLAG_SET_NEXT_ATTRIBUTION_SOURCE)          public @NonNull Builder setNextAttributionSource(@NonNull AttributionSource value) {              checkNotUsed();              mBuilderFieldsSet |= 0x20; diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index b6a98a5b8f83..59bb73b5916d 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -323,7 +323,7 @@ public abstract class Context {              // Make sure no flag uses the sign bit (most significant bit) of the long integer,              // to avoid future confusion.              BIND_BYPASS_USER_NETWORK_RESTRICTIONS, -            BIND_FILTER_OUT_QUARANTINED_COMPONENTS, +            BIND_MATCH_QUARANTINED_COMPONENTS,      })      @Retention(RetentionPolicy.SOURCE)      public @interface BindServiceFlagsLongBits {} @@ -703,7 +703,7 @@ public abstract class Context {       *       * @hide       */ -    public static final long BIND_FILTER_OUT_QUARANTINED_COMPONENTS = 0x2_0000_0000L; +    public static final long BIND_MATCH_QUARANTINED_COMPONENTS = 0x2_0000_0000L;      /** @@ -4357,7 +4357,6 @@ public abstract class Context {       * @see android.telephony.CarrierConfigManager       * @see #EUICC_SERVICE       * @see android.telephony.euicc.EuiccManager -     * @see android.telephony.MmsManager       * @see #INPUT_METHOD_SERVICE       * @see android.view.inputmethod.InputMethodManager       * @see #UI_MODE_SERVICE diff --git a/core/java/android/content/ContextParams.java b/core/java/android/content/ContextParams.java index 988a9c0ab9b3..b844d358a523 100644 --- a/core/java/android/content/ContextParams.java +++ b/core/java/android/content/ContextParams.java @@ -16,7 +16,10 @@  package android.content; +import static android.permission.flags.Flags.FLAG_SHOULD_REGISTER_ATTRIBUTION_SOURCE; +  import android.Manifest; +import android.annotation.FlaggedApi;  import android.annotation.NonNull;  import android.annotation.Nullable;  import android.annotation.RequiresPermission; @@ -102,6 +105,7 @@ public final class ContextParams {       * registered.       */      @NonNull +    @FlaggedApi(FLAG_SHOULD_REGISTER_ATTRIBUTION_SOURCE)      public boolean shouldRegisterAttributionSource() {          return mShouldRegisterAttributionSource;      } @@ -179,6 +183,7 @@ public final class ContextParams {           *                       created should be registered.           */          @NonNull +        @FlaggedApi(FLAG_SHOULD_REGISTER_ATTRIBUTION_SOURCE)          public Builder setShouldRegisterAttributionSource(boolean shouldRegister) {              mShouldRegisterAttributionSource = shouldRegister;              return this; diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 5f4c05f001ff..b765562ab587 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -3929,6 +3929,8 @@ public class Intent implements Parcelable, Cloneable {       * {@link #ACTION_BOOT_COMPLETED} is sent.  This is sent as a foreground       * broadcast, since it is part of a visible user interaction; be as quick       * as possible when handling it. +     * +     * <p><b>Note:</b> This broadcast is not sent to the system user.       */      public static final String ACTION_USER_INITIALIZE =              "android.intent.action.USER_INITIALIZE"; @@ -4180,7 +4182,7 @@ public class Intent implements Parcelable, Cloneable {       * new state of quiet mode. This is only sent to registered receivers, not manifest receivers.       *       * <p>This broadcast is similar to {@link #ACTION_MANAGED_PROFILE_AVAILABLE} but functions as a -     * generic broadcast for all users of type {@link android.content.pm.UserInfo#isProfile()}}. +     * generic broadcast for all profile users.       */      @FlaggedApi(FLAG_ALLOW_PRIVATE_PROFILE)      public static final String ACTION_PROFILE_AVAILABLE = @@ -4194,7 +4196,7 @@ public class Intent implements Parcelable, Cloneable {       * new state of quiet mode. This is only sent to registered receivers, not manifest receivers.       *       * <p>This broadcast is similar to {@link #ACTION_MANAGED_PROFILE_UNAVAILABLE} but functions as -     * a generic broadcast for all users of type {@link android.content.pm.UserInfo#isProfile()}}. +     * a generic broadcast for all profile users.       */      @FlaggedApi(FLAG_ALLOW_PRIVATE_PROFILE)      public static final String ACTION_PROFILE_UNAVAILABLE = @@ -4222,7 +4224,7 @@ public class Intent implements Parcelable, Cloneable {       * that was removed.       *       * <p>This broadcast is similar to {@link #ACTION_MANAGED_PROFILE_REMOVED} but functions as a -     * generic broadcast for all users of type {@link android.content.pm.UserInfo#isProfile()}}. +     * generic broadcast for all profile users.       * It is sent in addition to the {@link #ACTION_MANAGED_PROFILE_REMOVED} broadcast when a       * managed user is removed.       * @@ -4242,7 +4244,7 @@ public class Intent implements Parcelable, Cloneable {       * that was added.       *       * <p>This broadcast is similar to {@link #ACTION_MANAGED_PROFILE_ADDED} but functions as a -     * generic broadcast for all users of type {@link android.content.pm.UserInfo#isProfile()}}. +     * generic broadcast for all profile users.       * It is sent in addition to the {@link #ACTION_MANAGED_PROFILE_ADDED} broadcast when a       * managed user is added.       * @@ -5323,6 +5325,7 @@ public class Intent implements Parcelable, Cloneable {       * @hide       */      @SystemApi +    @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING)      public static final String ACTION_UNARCHIVE_PACKAGE = "android.intent.action.UNARCHIVE_PACKAGE";      // --------------------------------------------------------------------- diff --git a/core/java/android/content/pm/ArchivedActivityParcel.aidl b/core/java/android/content/pm/ArchivedActivityParcel.aidl index 7ab7ed1cc5df..74953fff40d8 100644 --- a/core/java/android/content/pm/ArchivedActivityParcel.aidl +++ b/core/java/android/content/pm/ArchivedActivityParcel.aidl @@ -16,9 +16,12 @@  package android.content.pm; +import android.content.ComponentName; +  /** @hide */  parcelable ArchivedActivityParcel {      String title; +    ComponentName originalComponentName;      // PNG compressed bitmaps.      byte[] iconBitmap;      byte[] monochromeIconBitmap; diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 45338bb2c0a2..1b60f8ed904f 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -838,7 +838,7 @@ public abstract class PackageManager {              GET_DISABLED_COMPONENTS,              GET_DISABLED_UNTIL_USED_COMPONENTS,              GET_UNINSTALLED_PACKAGES, -            FILTER_OUT_QUARANTINED_COMPONENTS, +            MATCH_QUARANTINED_COMPONENTS,      })      @Retention(RetentionPolicy.SOURCE)      public @interface ComponentInfoFlagsBits {} @@ -863,7 +863,7 @@ public abstract class PackageManager {              GET_DISABLED_UNTIL_USED_COMPONENTS,              GET_UNINSTALLED_PACKAGES,              MATCH_CLONE_PROFILE, -            FILTER_OUT_QUARANTINED_COMPONENTS, +            MATCH_QUARANTINED_COMPONENTS,      })      @Retention(RetentionPolicy.SOURCE)      public @interface ResolveInfoFlagsBits {} @@ -1252,12 +1252,13 @@ public abstract class PackageManager {       */      // TODO(b/278553670) Unhide and update @links before launch.      @SystemApi +    @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING)      public static final long MATCH_ARCHIVED_PACKAGES = 1L << 32;      /**       * @hide       */ -    public static final long FILTER_OUT_QUARANTINED_COMPONENTS = 0x100000000L; +    public static final long MATCH_QUARANTINED_COMPONENTS = 0x100000000L;      /**       * Flag for {@link #addCrossProfileIntentFilter}: if this flag is set: when @@ -4605,6 +4606,16 @@ public abstract class PackageManager {      public static final String FEATURE_WALLET_LOCATION_BASED_SUGGESTIONS =              "android.software.wallet_location_based_suggestions"; +    /** +     * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has +     * the rotary encoder hardware to support rotating bezel on watch. +     * +     * @hide +     */ +    @SdkConstant(SdkConstantType.FEATURE) +    public static final String FEATURE_ROTARY_ENCODER_LOW_RES = +            "android.hardware.rotaryencoder.lowres"; +      /** @hide */      public static final boolean APP_ENUMERATION_ENABLED_BY_DEFAULT = true; diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig index b2cc070228b7..db12728cfb98 100644 --- a/core/java/android/content/pm/flags.aconfig +++ b/core/java/android/content/pm/flags.aconfig @@ -50,3 +50,11 @@ flag {      description: "Feature flag to enable the features that rely on new ART Service APIs that are in the VIC version of the ART module."      bug: "304741685"  } + +flag { +    name: "sdk_lib_independence" +    namespace: "package_manager_service" +    description: "Feature flag to keep app working even if its declared sdk-library dependency is unavailable." +    bug: "295827951" +    is_fixed_read_only: true +} diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig new file mode 100644 index 000000000000..0c2c0f494257 --- /dev/null +++ b/core/java/android/content/res/flags.aconfig @@ -0,0 +1,10 @@ +package: "android.content.res" + +flag { +    name: "default_locale" +    namespace: "resource_manager" +    description: "Feature flag for default locale in LocaleConfig" +    bug: "117306409" +    # fixed_read_only or device wont boot because of permission issues accessing flags during boot +    is_fixed_read_only: true +} diff --git a/core/java/android/credentials/CredentialProviderInfo.java b/core/java/android/credentials/CredentialProviderInfo.java index 8503072838d1..38fbd726a4fb 100644 --- a/core/java/android/credentials/CredentialProviderInfo.java +++ b/core/java/android/credentials/CredentialProviderInfo.java @@ -128,6 +128,11 @@ public final class CredentialProviderInfo implements Parcelable {      @TestApi      @FlaggedApi(Flags.FLAG_SETTINGS_ACTIVITY_ENABLED)      public CharSequence getSettingsActivity() { +        // Add a manual check to make sure this returns null if +        // the flag is not enabled. +        if (!Flags.settingsActivityEnabled()) { +            return null; +        }          return mSettingsActivity;      } diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java index 746f2f23fd5c..b003e75b4e50 100644 --- a/core/java/android/database/sqlite/SQLiteDatabase.java +++ b/core/java/android/database/sqlite/SQLiteDatabase.java @@ -16,6 +16,7 @@  package android.database.sqlite; +import android.annotation.FlaggedApi;  import android.annotation.IntDef;  import android.annotation.IntRange;  import android.annotation.NonNull; @@ -700,6 +701,7 @@ public final class SQLiteDatabase extends SQLiteClosable {       *   }       * </pre>       */ +    @FlaggedApi(Flags.FLAG_SQLITE_APIS_15)      public void beginTransactionReadOnly() {          beginTransactionWithListenerReadOnly(null);      } @@ -783,6 +785,7 @@ public final class SQLiteDatabase extends SQLiteClosable {       *   }       * </pre>       */ +    @FlaggedApi(Flags.FLAG_SQLITE_APIS_15)      public void beginTransactionWithListenerReadOnly(              @Nullable SQLiteTransactionListener transactionListener) {          beginTransaction(transactionListener, SQLiteSession.TRANSACTION_MODE_DEFERRED); @@ -2221,6 +2224,7 @@ public final class SQLiteDatabase extends SQLiteClosable {       * @throws IllegalStateException if a transaction is not in progress.       * @throws SQLiteException if the SQL cannot be compiled.       */ +    @FlaggedApi(Flags.FLAG_SQLITE_APIS_15)      @NonNull      public SQLiteRawStatement createRawStatement(@NonNull String sql) {          Objects.requireNonNull(sql); @@ -2240,6 +2244,7 @@ public final class SQLiteDatabase extends SQLiteClosable {       * @return The ROWID of the last row to be inserted under this connection.       * @throws IllegalStateException if there is no current transaction.       */ +    @FlaggedApi(Flags.FLAG_SQLITE_APIS_15)      public long getLastInsertRowId() {          return getThreadSession().getLastInsertRowId();      } @@ -2253,6 +2258,7 @@ public final class SQLiteDatabase extends SQLiteClosable {       * @return The number of rows changed by the most recent sql statement       * @throws IllegalStateException if there is no current transaction.       */ +    @FlaggedApi(Flags.FLAG_SQLITE_APIS_15)      public long getLastChangedRowCount() {          return getThreadSession().getLastChangedRowCount();      } @@ -2280,6 +2286,7 @@ public final class SQLiteDatabase extends SQLiteClosable {       * @return The number of rows changed on the current connection.       * @throws IllegalStateException if there is no current transaction.       */ +    @FlaggedApi(Flags.FLAG_SQLITE_APIS_15)      public long getTotalChangedRowCount() {          return getThreadSession().getTotalChangedRowCount();      } diff --git a/core/java/android/database/sqlite/SQLiteRawStatement.java b/core/java/android/database/sqlite/SQLiteRawStatement.java index 165f1810c894..827420f96fed 100644 --- a/core/java/android/database/sqlite/SQLiteRawStatement.java +++ b/core/java/android/database/sqlite/SQLiteRawStatement.java @@ -16,6 +16,7 @@  package android.database.sqlite; +import android.annotation.FlaggedApi;  import android.annotation.IntDef;  import android.annotation.NonNull;  import android.annotation.Nullable; @@ -70,6 +71,7 @@ import java.util.Objects;   *   * @see <a href="http://sqlite.org/c3ref/stmt.html">sqlite3_stmt</a>   */ +@FlaggedApi(Flags.FLAG_SQLITE_APIS_15)  public final class SQLiteRawStatement implements Closeable {      private static final String TAG = "SQLiteRawStatement"; diff --git a/core/java/android/database/sqlite/flags.aconfig b/core/java/android/database/sqlite/flags.aconfig new file mode 100644 index 000000000000..564df039456b --- /dev/null +++ b/core/java/android/database/sqlite/flags.aconfig @@ -0,0 +1,9 @@ +package: "android.database.sqlite" + +flag { +     name: "sqlite_apis_15" +     namespace: "system_performance" +     is_fixed_read_only: true +     description: "SQLite APIs held back for Android 15" +     bug: "279043253" +} diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java index f033f9740b3d..bcf447b4267d 100644 --- a/core/java/android/hardware/SensorManager.java +++ b/core/java/android/hardware/SensorManager.java @@ -16,6 +16,7 @@  package android.hardware; +import android.annotation.IntDef;  import android.annotation.Nullable;  import android.annotation.SystemApi;  import android.annotation.SystemService; @@ -27,6 +28,8 @@ import android.os.MemoryFile;  import android.util.Log;  import android.util.SparseArray; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy;  import java.util.ArrayList;  import java.util.Collections;  import java.util.List; @@ -1809,6 +1812,41 @@ public abstract class SensorManager {      protected abstract boolean cancelTriggerSensorImpl(TriggerEventListener listener,              Sensor sensor, boolean disable); +    /** +     * @hide +     */ +    @Retention(RetentionPolicy.SOURCE) +    @IntDef({DATA_INJECTION, REPLAY_DATA_INJECTION, HAL_BYPASS_REPLAY_DATA_INJECTION}) +    public @interface DataInjectionMode {} +    /** +     * This mode is only used for testing purposes. Not all HALs support this mode. In this mode, +     * the HAL ignores the sensor data provided by physical sensors and accepts the data that is +     * injected from the SensorService as if it were the real sensor data. This mode is primarily +     * used for testing various algorithms like vendor provided SensorFusion, Step Counter and +     * Step Detector etc. Typically, in this mode, there is a client app which injects +     * sensor data into the HAL. Normal apps can register and unregister for any sensor +     * that supports injection. Registering to sensors that do not support injection will +     * give an error. +     * This is the default data injection mode. +     * @hide +     */ +    public static final int DATA_INJECTION = 1; +    /** +     * Mostly equivalent to DATA_INJECTION with the difference being that the injected data is +     * delivered to all requesting apps rather than just the package allowed to inject data. +     * This mode is only allowed to be used on development builds. +     * @hide +     */ +    public static final int REPLAY_DATA_INJECTION = 3; +    /** +     * Like REPLAY_DATA_INJECTION but injected data is not sent into the HAL. It is stored in a +     * buffer in the platform and played back to all requesting apps. +     * This is useful for playing back sensor data to test platform components without +     * relying on the HAL to support data injection. +     * @hide +     */ +    public static final int HAL_BYPASS_REPLAY_DATA_INJECTION = 4; +      /**       * For testing purposes only. Not for third party applications. @@ -1833,13 +1871,47 @@ public abstract class SensorManager {       */      @SystemApi      public boolean initDataInjection(boolean enable) { -        return initDataInjectionImpl(enable); +        return initDataInjectionImpl(enable, DATA_INJECTION); +    } + +    /** +     * For testing purposes only. Not for third party applications. +     * +     * Initialize data injection mode and create a client for data injection. SensorService should +     * already be operating in one of DATA_INJECTION, REPLAY_DATA_INJECTION or +     * HAL_BYPASS_REPLAY_DATA_INJECTION modes for this call succeed. To set SensorService in +     * a Data Injection mode, use one of: +     * +     * <ul> +     *      <li>adb shell dumpsys sensorservice data_injection</li> +     *      <li>adb shell dumpsys sensorservice replay_data_injection package_name</li> +     *      <li>adb shell dumpsys sensorservice hal_bypass_replay_data_injection package_name</li> +     * </ul> +     * +     * Typically this is done using a host side test.  This mode is expected to be used +     * only for testing purposes. See {@link DataInjectionMode} for details of each data injection +     * mode. Once this method succeeds, the test can call +     * {@link #injectSensorData(Sensor, float[], int, long)} to inject sensor data into the HAL. +     * To put SensorService back into normal mode, use "adb shell dumpsys sensorservice enable" +     * +     * @param enable True to initialize a client in a data injection mode. +     *               False to clean up the native resources. +     * +     * @param mode One of DATA_INJECTION, REPLAY_DATA_INJECTION or HAL_BYPASS_DATA_INJECTION. +     *             See {@link DataInjectionMode} for details. +     * +     * @return true if the HAL supports data injection and false +     *         otherwise. +     * @hide +     */ +    public boolean initDataInjection(boolean enable, @DataInjectionMode int mode) { +        return initDataInjectionImpl(enable, mode);      }      /**       * @hide       */ -    protected abstract boolean initDataInjectionImpl(boolean enable); +    protected abstract boolean initDataInjectionImpl(boolean enable, @DataInjectionMode int mode);      /**       * For testing purposes only. Not for third party applications. @@ -1871,9 +1943,6 @@ public abstract class SensorManager {          if (sensor == null) {              throw new IllegalArgumentException("sensor cannot be null");          } -        if (!sensor.isDataInjectionSupported()) { -            throw new IllegalArgumentException("sensor does not support data injection"); -        }          if (values == null) {              throw new IllegalArgumentException("sensor data cannot be null");          } diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java index dfd380233662..40e03dbb8b34 100644 --- a/core/java/android/hardware/SystemSensorManager.java +++ b/core/java/android/hardware/SystemSensorManager.java @@ -90,6 +90,8 @@ public class SystemSensorManager extends SensorManager {      private static native void nativeGetRuntimeSensors(              long nativeInstance, int deviceId, List<Sensor> list);      private static native boolean nativeIsDataInjectionEnabled(long nativeInstance); +    private static native boolean nativeIsReplayDataInjectionEnabled(long nativeInstance); +    private static native boolean nativeIsHalBypassReplayDataInjectionEnabled(long nativeInstance);      private static native int nativeCreateDirectChannel(              long nativeInstance, int deviceId, long size, int channelType, int fd, @@ -384,20 +386,41 @@ public class SystemSensorManager extends SensorManager {          }      } -    protected boolean initDataInjectionImpl(boolean enable) { +    protected boolean initDataInjectionImpl(boolean enable, @DataInjectionMode int mode) {          synchronized (sLock) { +            boolean isDataInjectionModeEnabled = false;              if (enable) { -                boolean isDataInjectionModeEnabled = nativeIsDataInjectionEnabled(mNativeInstance); +                switch (mode) { +                    case DATA_INJECTION: +                        isDataInjectionModeEnabled = nativeIsDataInjectionEnabled(mNativeInstance); +                        break; +                    case REPLAY_DATA_INJECTION: +                        isDataInjectionModeEnabled = nativeIsReplayDataInjectionEnabled( +                                mNativeInstance); +                        break; +                    case HAL_BYPASS_REPLAY_DATA_INJECTION: +                        isDataInjectionModeEnabled = nativeIsHalBypassReplayDataInjectionEnabled( +                                mNativeInstance); +                        break; +                    default: +                        break; +                }                  // The HAL does not support injection OR SensorService hasn't been set in DI mode.                  if (!isDataInjectionModeEnabled) { -                    Log.e(TAG, "Data Injection mode not enabled"); +                    Log.e(TAG, "The correct Data Injection mode has not been enabled");                      return false;                  } +                if (sInjectEventQueue != null && sInjectEventQueue.getDataInjectionMode() != mode) { +                    // The inject event queue has been initialized for a different type of DI +                    // close it and create a new one +                    sInjectEventQueue.dispose(); +                    sInjectEventQueue = null; +                }                  // Initialize a client for data_injection.                  if (sInjectEventQueue == null) {                      try {                          sInjectEventQueue = new InjectEventQueue( -                                mMainLooper, this, mContext.getPackageName()); +                                mMainLooper, this, mode, mContext.getPackageName());                      } catch (RuntimeException e) {                          Log.e(TAG, "Cannot create InjectEventQueue: " + e);                      } @@ -421,6 +444,12 @@ public class SystemSensorManager extends SensorManager {                  Log.e(TAG, "Data injection mode not activated before calling injectSensorData");                  return false;              } +            if (sInjectEventQueue.getDataInjectionMode() != HAL_BYPASS_REPLAY_DATA_INJECTION +                    && !sensor.isDataInjectionSupported()) { +                // DI mode and Replay DI mode require support from the sensor HAL +                // HAL Bypass mode doesn't require this. +                throw new IllegalArgumentException("sensor does not support data injection"); +            }              int ret = sInjectEventQueue.injectSensorData(sensor.getHandle(), values, accuracy,                                                           timestamp);              // If there are any errors in data injection clean up the native resources. @@ -825,6 +854,8 @@ public class SystemSensorManager extends SensorManager {          protected static final int OPERATING_MODE_NORMAL = 0;          protected static final int OPERATING_MODE_DATA_INJECTION = 1; +        protected static final int OPERATING_MODE_REPLAY_DATA_INJECTION = 3; +        protected static final int OPERATING_MODE_HAL_BYPASS_REPLAY_DATA_INJECTION = 4;          BaseEventQueue(Looper looper, SystemSensorManager manager, int mode, String packageName) {              if (packageName == null) packageName = ""; @@ -1134,8 +1165,12 @@ public class SystemSensorManager extends SensorManager {      }      final class InjectEventQueue extends BaseEventQueue { -        public InjectEventQueue(Looper looper, SystemSensorManager manager, String packageName) { -            super(looper, manager, OPERATING_MODE_DATA_INJECTION, packageName); + +        private int mMode; +        public InjectEventQueue(Looper looper, SystemSensorManager manager, +                @DataInjectionMode int mode, String packageName) { +            super(looper, manager, mode, packageName); +            mMode = mode;          }          int injectSensorData(int handle, float[] values, int accuracy, long timestamp) { @@ -1161,6 +1196,10 @@ public class SystemSensorManager extends SensorManager {          protected void removeSensorEvent(Sensor sensor) {          } + +        int getDataInjectionMode() { +            return mMode; +        }      }      protected boolean setOperationParameterImpl(SensorAdditionalInfo parameter) { diff --git a/core/java/android/hardware/display/BrightnessInfo.java b/core/java/android/hardware/display/BrightnessInfo.java index 99f3d156cfa9..53a9a75fbca0 100644 --- a/core/java/android/hardware/display/BrightnessInfo.java +++ b/core/java/android/hardware/display/BrightnessInfo.java @@ -59,7 +59,8 @@ public final class BrightnessInfo implements Parcelable {      @IntDef(prefix = {"BRIGHTNESS_MAX_REASON_"}, value = {              BRIGHTNESS_MAX_REASON_NONE, -            BRIGHTNESS_MAX_REASON_THERMAL +            BRIGHTNESS_MAX_REASON_THERMAL, +            BRIGHTNESS_MAX_REASON_POWER_IC      })      @Retention(RetentionPolicy.SOURCE)      public @interface BrightnessMaxReason {} @@ -74,6 +75,11 @@ public final class BrightnessInfo implements Parcelable {       */      public static final int BRIGHTNESS_MAX_REASON_THERMAL = 1; +    /** +     * Maximum brightness is restricted due to power throttling. +     */ +    public static final int BRIGHTNESS_MAX_REASON_POWER_IC = 2; +      /** Brightness */      public final float brightness; @@ -144,6 +150,8 @@ public final class BrightnessInfo implements Parcelable {                  return "none";              case BRIGHTNESS_MAX_REASON_THERMAL:                  return "thermal"; +            case BRIGHTNESS_MAX_REASON_POWER_IC: +                return "power IC";          }          return "invalid";      } diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index f347909348c8..aeddd0c8d4b1 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -31,6 +31,7 @@ import android.annotation.RequiresPermission;  import android.annotation.SystemApi;  import android.annotation.SystemService;  import android.annotation.TestApi; +import android.app.ActivityThread;  import android.app.KeyguardManager;  import android.compat.annotation.UnsupportedAppUsage;  import android.content.Context; @@ -775,7 +776,8 @@ public final class DisplayManager {       */      public void registerDisplayListener(@NonNull DisplayListener listener,              @Nullable Handler handler, @EventsMask long eventsMask) { -        mGlobal.registerDisplayListener(listener, handler, eventsMask); +        mGlobal.registerDisplayListener(listener, handler, eventsMask, +                ActivityThread.currentPackageName());      }      /** @@ -1819,6 +1821,12 @@ public final class DisplayManager {          String KEY_BRIGHTNESS_THROTTLING_DATA = "brightness_throttling_data";          /** +         * Key for the power throttling data as a String formatted, from the display +         * device config. +         */ +        String KEY_POWER_THROTTLING_DATA = "power_throttling_data"; + +        /**           * Key for new power controller feature flag. If enabled new DisplayPowerController will           * be used.           * Read value via {@link android.provider.DeviceConfig#getBoolean(String, String, boolean)} diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index 6d6085b471d9..8decd50664b3 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -25,6 +25,7 @@ import android.annotation.IntDef;  import android.annotation.NonNull;  import android.annotation.Nullable;  import android.annotation.RequiresPermission; +import android.app.ActivityThread;  import android.app.PropertyInvalidatedCache;  import android.compat.annotation.UnsupportedAppUsage;  import android.content.Context; @@ -45,8 +46,11 @@ import android.os.Message;  import android.os.RemoteException;  import android.os.ServiceManager;  import android.os.Trace; +import android.sysprop.DisplayProperties; +import android.text.TextUtils;  import android.util.Log;  import android.util.Pair; +import android.util.Slog;  import android.util.SparseArray;  import android.view.Display;  import android.view.DisplayAdjustments; @@ -72,7 +76,13 @@ import java.util.concurrent.atomic.AtomicLong;   */  public final class DisplayManagerGlobal {      private static final String TAG = "DisplayManager"; -    private static final boolean DEBUG = false; + +    private static final String EXTRA_LOGGING_PACKAGE_NAME = +            DisplayProperties.debug_vri_package().orElse(null); +    private static String sCurrentPackageName = ActivityThread.currentPackageName(); +    private static boolean sExtraDisplayListenerLogging = initExtraLogging(); + +    private static final boolean DEBUG = false || sExtraDisplayListenerLogging;      // True if display info and display ids should be cached.      // @@ -130,6 +140,8 @@ public final class DisplayManagerGlobal {      @VisibleForTesting      public DisplayManagerGlobal(IDisplayManager dm) {          mDm = dm; +        initExtraLogging(); +          try {              mWideColorSpace =                      ColorSpace.get( @@ -208,7 +220,7 @@ public final class DisplayManagerGlobal {          registerCallbackIfNeededLocked(); -        if (DEBUG) { +        if (DEBUG || extraLogging()) {              Log.d(TAG, "getDisplayInfo: displayId=" + displayId + ", info=" + info);          }          return info; @@ -321,12 +333,14 @@ public final class DisplayManagerGlobal {       * If null, listener will use the handler for the current thread, and if still null,       * the handler for the main thread.       * If that is still null, a runtime exception will be thrown. +     * @param packageName of the calling package.       */      public void registerDisplayListener(@NonNull DisplayListener listener, -            @Nullable Handler handler, @EventsMask long eventsMask) { +            @Nullable Handler handler, @EventsMask long eventsMask, String packageName) {          Looper looper = getLooperForHandler(handler);          Handler springBoard = new Handler(looper); -        registerDisplayListener(listener, new HandlerExecutor(springBoard), eventsMask); +        registerDisplayListener(listener, new HandlerExecutor(springBoard), eventsMask, +                packageName);      }      /** @@ -334,9 +348,11 @@ public final class DisplayManagerGlobal {       *       * @param listener The listener that will be called when display changes occur.       * @param executor Executor for the thread that will be receiving the callbacks. Cannot be null. +     * @param eventsMask Mask of events to be listened to. +     * @param packageName of the calling package.       */      public void registerDisplayListener(@NonNull DisplayListener listener, -            @NonNull Executor executor, @EventsMask long eventsMask) { +            @NonNull Executor executor, @EventsMask long eventsMask, String packageName) {          if (listener == null) {              throw new IllegalArgumentException("listener must not be null");          } @@ -345,15 +361,22 @@ public final class DisplayManagerGlobal {              throw new IllegalArgumentException("The set of events to listen to must not be empty.");          } +        if (extraLogging()) { +            Slog.i(TAG, "Registering Display Listener: " +                    + Long.toBinaryString(eventsMask) + ", packageName: " + packageName); +        } +          synchronized (mLock) {              int index = findDisplayListenerLocked(listener);              if (index < 0) { -                mDisplayListeners.add(new DisplayListenerDelegate(listener, executor, eventsMask)); +                mDisplayListeners.add(new DisplayListenerDelegate(listener, executor, eventsMask, +                        packageName));                  registerCallbackIfNeededLocked();              } else {                  mDisplayListeners.get(index).setEventsMask(eventsMask);              }              updateCallbackIfNeededLocked(); +            maybeLogAllDisplayListeners();          }      } @@ -362,6 +385,10 @@ public final class DisplayManagerGlobal {              throw new IllegalArgumentException("listener must not be null");          } +        if (extraLogging()) { +            Slog.i(TAG, "Unregistering Display Listener: " + listener); +        } +          synchronized (mLock) {              int index = findDisplayListenerLocked(listener);              if (index >= 0) { @@ -371,6 +398,18 @@ public final class DisplayManagerGlobal {                  updateCallbackIfNeededLocked();              }          } +        maybeLogAllDisplayListeners(); +    } + +    private void maybeLogAllDisplayListeners() { +        if (!sExtraDisplayListenerLogging) { +            return; +        } + +        Slog.i(TAG, "Currently Registered Display Listeners:"); +        for (int i = 0; i < mDisplayListeners.size(); i++) { +            Slog.i(TAG, i + ": " + mDisplayListeners.get(i)); +        }      }      private static Looper getLooperForHandler(@Nullable Handler handler) { @@ -1148,15 +1187,20 @@ public final class DisplayManagerGlobal {          private final DisplayInfo mDisplayInfo = new DisplayInfo();          private final Executor mExecutor;          private AtomicLong mGenerationId = new AtomicLong(1); +        private final String mPackageName;          DisplayListenerDelegate(DisplayListener listener, @NonNull Executor executor, -                @EventsMask long eventsMask) { +                @EventsMask long eventsMask, String packageName) {              mExecutor = executor;              mListener = listener;              mEventsMask = eventsMask; +            mPackageName = packageName;          }          public void sendDisplayEvent(int displayId, @DisplayEvent int event, DisplayInfo info) { +            if (extraLogging()) { +                Slog.i(TAG, "Sending Display Event: " + eventToString(event)); +            }              long generationId = mGenerationId.get();              Message msg = Message.obtain(null, event, displayId, 0, info);              mExecutor.execute(() -> { @@ -1177,6 +1221,14 @@ public final class DisplayManagerGlobal {          }          private void handleMessage(Message msg) { +            if (extraLogging()) { +                Slog.i(TAG, "DisplayListenerDelegate(" + eventToString(msg.what) +                        + ", display=" + msg.arg1 +                        + ", mEventsMask=" + Long.toBinaryString(mEventsMask) +                        + ", mPackageName=" + mPackageName +                        + ", msg.obj=" + msg.obj +                        + ", listener=" + mListener.getClass() + ")"); +            }              if (DEBUG) {                  Trace.beginSection(                          "DisplayListenerDelegate(" + eventToString(msg.what) @@ -1193,6 +1245,10 @@ public final class DisplayManagerGlobal {                      if ((mEventsMask & DisplayManager.EVENT_FLAG_DISPLAY_CHANGED) != 0) {                          DisplayInfo newInfo = (DisplayInfo) msg.obj;                          if (newInfo != null && !newInfo.equals(mDisplayInfo)) { +                            if (extraLogging()) { +                                Slog.i(TAG, "Sending onDisplayChanged: Display Changed. Info: " +                                        + newInfo); +                            }                              mDisplayInfo.copyFrom(newInfo);                              mListener.onDisplayChanged(msg.arg1);                          } @@ -1228,6 +1284,11 @@ public final class DisplayManagerGlobal {                  Trace.endSection();              }          } + +        @Override +        public String toString() { +            return "mask: {" + mEventsMask + "}, for " + mListener.getClass(); +        }      }      /** @@ -1353,4 +1414,19 @@ public final class DisplayManagerGlobal {          }          return "UNKNOWN";      } + + +    private static boolean initExtraLogging() { +        if (sCurrentPackageName == null) { +            sCurrentPackageName = ActivityThread.currentPackageName(); +            sExtraDisplayListenerLogging = !TextUtils.isEmpty(EXTRA_LOGGING_PACKAGE_NAME) +                    && EXTRA_LOGGING_PACKAGE_NAME.equals(sCurrentPackageName); +        } +        return sExtraDisplayListenerLogging; +    } + +    private static boolean extraLogging() { +        return sExtraDisplayListenerLogging && EXTRA_LOGGING_PACKAGE_NAME.equals( +                sCurrentPackageName); +    }  } diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java index 02212968cdb0..02304b5ba4f3 100644 --- a/core/java/android/hardware/face/FaceManager.java +++ b/core/java/android/hardware/face/FaceManager.java @@ -1074,6 +1074,14 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan       */      public interface FaceDetectionCallback {          void onFaceDetected(int sensorId, int userId, boolean isStrongBiometric); + +        /** +         * An error has occurred with face detection. +         * +         * This callback signifies that this operation has been completed, and +         * no more callbacks should be expected. +         */ +        default void onDetectionError(int errorMsgId) {}      }      /** @@ -1373,6 +1381,9 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan          } else if (mRemovalCallback != null) {              mRemovalCallback.onRemovalError(mRemovalFace, clientErrMsgId,                      getErrorString(mContext, errMsgId, vendorCode)); +        } else if (mFaceDetectionCallback != null) { +            mFaceDetectionCallback.onDetectionError(errMsgId); +            mFaceDetectionCallback = null;          }      } diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index 5bfda70f03de..935157a48a45 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -462,6 +462,14 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing           * Invoked when a fingerprint has been detected.           */          void onFingerprintDetected(int sensorId, int userId, boolean isStrongBiometric); + +        /** +         * An error has occurred with fingerprint detection. +         * +         * This callback signifies that this operation has been completed, and +         * no more callbacks should be expected. +         */ +        default void onDetectionError(int errorMsgId) {}      }      /** @@ -1484,6 +1492,9 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing                      ? mRemoveTracker.mSingleFingerprint : null;              mRemovalCallback.onRemovalError(fp, clientErrMsgId,                      getErrorString(mContext, errMsgId, vendorCode)); +        } else if (mFingerprintDetectionCallback != null) { +            mFingerprintDetectionCallback.onDetectionError(errMsgId); +            mFingerprintDetectionCallback = null;          }      } diff --git a/core/java/android/hardware/input/InputDeviceSensorManager.java b/core/java/android/hardware/input/InputDeviceSensorManager.java index aa55e543dd48..05024ea95eda 100644 --- a/core/java/android/hardware/input/InputDeviceSensorManager.java +++ b/core/java/android/hardware/input/InputDeviceSensorManager.java @@ -644,7 +644,7 @@ public class InputDeviceSensorManager implements InputManager.InputDeviceListene          }          @Override -        protected boolean initDataInjectionImpl(boolean enable) { +        protected boolean initDataInjectionImpl(boolean enable, int mode) {              return false;          } diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java index ff1a6acd8e4e..7b8419e6c6f7 100644 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -1373,7 +1373,7 @@ public final class InputManager {      public interface InputDeviceListener {          /**           * Called whenever an input device has been added to the system. -         * Use {@link InputManagerGlobal#getInputDevice} to get more information about the device. +         * Use {@link #getInputDevice(int)} to get more information about the device.           *           * @param deviceId The id of the input device that was added.           */ diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 582c5c0c4dd4..4c2bbc18f2db 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -3231,7 +3231,7 @@ public class InputMethodService extends AbstractInputMethodService {      }      /** -     * Called when the user tapped or clicked an {@link android.widget.Editor}. +     * Called when the user tapped or clicked an editor.       * This can be useful when IME makes a decision of showing Virtual keyboard based on what       * {@link MotionEvent#getToolType(int)} was used to click the editor.       * e.g. when toolType is {@link MotionEvent#TOOL_TYPE_STYLUS}, IME may choose to show a diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index 8482945dc1f0..e85b7bf1c91e 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -1854,7 +1854,7 @@ public abstract class BatteryStats {          @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)          public HistoryItem next; -        // The time of this event in milliseconds, as per SystemClock.elapsedRealtime(). +        // The time of this event in milliseconds, as per MonotonicClock.monotonicTime().          @UnsupportedAppUsage          public long time; diff --git a/core/java/android/os/BatteryUsageStatsQuery.java b/core/java/android/os/BatteryUsageStatsQuery.java index 49d7e8bc5632..32840d4b5837 100644 --- a/core/java/android/os/BatteryUsageStatsQuery.java +++ b/core/java/android/os/BatteryUsageStatsQuery.java @@ -300,6 +300,7 @@ public final class BatteryUsageStatsQuery implements Parcelable {           * @param fromTimestamp Exclusive starting timestamp, as per System.currentTimeMillis()           * @param toTimestamp Inclusive ending timestamp, as per System.currentTimeMillis()           */ +        // TODO(b/298459065): switch to monotonic clock          public Builder aggregateSnapshots(long fromTimestamp, long toTimestamp) {              mFromTimestamp = fromTimestamp;              mToTimestamp = toTimestamp; diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java index 086b0e5d4b05..58def6ef7961 100644 --- a/core/java/android/os/BugreportManager.java +++ b/core/java/android/os/BugreportManager.java @@ -143,10 +143,11 @@ public final class BugreportManager {           */          public void onError(@BugreportErrorCode int errorCode) {} -        /** Called when taking bugreport finishes successfully. +        /** +         * Called when taking bugreport finishes successfully.           *           * <p>This callback will be invoked if the -         * {@link BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT} flag is not set. +         * {@code BugreportParams#BUGREPORT_FLAG_DEFER_CONSENT} flag is not set.           */          public void onFinished() {} diff --git a/core/java/android/os/DropBoxManager.java b/core/java/android/os/DropBoxManager.java index 109d6b218c5f..cf3546057549 100644 --- a/core/java/android/os/DropBoxManager.java +++ b/core/java/android/os/DropBoxManager.java @@ -17,7 +17,7 @@  package android.os;  import static android.Manifest.permission.PACKAGE_USAGE_STATS; -import static android.Manifest.permission.READ_DROPBOX_DATA; +import static android.Manifest.permission.READ_LOGS;  import android.annotation.BytesLong;  import android.annotation.CurrentTimeMillisLong; @@ -81,11 +81,9 @@ public class DropBoxManager {      /**       * Broadcast Action: This is broadcast when a new entry is added in the dropbox. -     * For Android V+ (including V), you must hold the -     * {@link android.Manifest.permission#READ_DROPBOX_DATA} permission in order -     * to receive this broadcast. For Android version earlier than -     * Android V, you must hold {@link android.Manifest.permission#READ_LOGS}. -     * This broadcast can be rate limited for low priority entries +     * You must hold the {@link android.Manifest.permission#READ_LOGS} permission +     * in order to receive this broadcast. This broadcast can be rate limited for low priority +     * entries       *       * <p class="note">This is a protected intent that can only be sent       * by the system. @@ -384,16 +382,12 @@ public class DropBoxManager {      /**       * Gets the next entry from the drop box <em>after</em> the specified time.       * You must always call {@link Entry#close()} on the return value! -     * {@link android.Manifest.permission#READ_DROPBOX_DATA} permission is -     * required for Android V or later. -     * {@link android.Manifest.permission#READ_LOGS} permission is -     * required for Android earlier than V.       *       * @param tag of entry to look for, null for all tags       * @param msec time of the last entry seen       * @return the next entry, or null if there are no more entries       */ -    @RequiresPermission(allOf = { READ_DROPBOX_DATA, PACKAGE_USAGE_STATS }) +    @RequiresPermission(allOf = { READ_LOGS, PACKAGE_USAGE_STATS })      public @Nullable Entry getNextEntry(String tag, long msec) {          try {              return mService.getNextEntryWithAttribution(tag, msec, mContext.getOpPackageName(), diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index 4174c1c13c94..fce715ade5af 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -2670,7 +2670,7 @@ public final class PowerManager {          /**           * Called when overall thermal throttling status changed. -         * @param status defined in {@link android.os.Temperature}. +         * @param status the status           */          void onThermalStatusChanged(@ThermalStatus int status);      } diff --git a/core/java/android/os/PowerMonitor.java b/core/java/android/os/PowerMonitor.java index 5fb0df7febc1..8c5f2cd48f61 100644 --- a/core/java/android/os/PowerMonitor.java +++ b/core/java/android/os/PowerMonitor.java @@ -16,6 +16,7 @@  package android.os; +import android.annotation.FlaggedApi;  import android.annotation.IntDef;  import android.annotation.NonNull; @@ -23,12 +24,19 @@ import java.lang.annotation.Retention;  import java.lang.annotation.RetentionPolicy;  /** - * A PowerMonitor represents either a Channel aka ODPM rail (on-device power monitor) or an - * EnergyConsumer, as defined in - * <a href="https://android.googlesource.com/platform/hardware/interfaces/+/refs/heads/main/power/stats/aidl/aidl_api/android.hardware.power.stats/current/android/hardware/power/stats">android.hardware.power.stats</a> - * - * @hide + * A PowerMonitor represents either an ODPM rail (on-device power rail monitor) or a modeled + * energy consumer. + * <p/> + * ODPM rail names are device-specific. No assumptions should be made about the names and + * exact purpose of ODPM rails across different device models. A rail name may be something + * like "S2S_VDD_G3D"; specific knowledge of the device hardware is required to interpret + * the corresponding power monitor data. + * <p/> + * Energy consumer have more human-readable names, e.g. "GPU", "MODEM" etc. However, developers + * must be extra cautious about using energy consumers across different device models, + * as their exact implementations are also hardware dependent and are customized by OEMs.   */ +@FlaggedApi("com.android.server.power.optimization.power_monitor_api")  public final class PowerMonitor implements Parcelable {      /** @@ -36,9 +44,9 @@ public final class PowerMonitor implements Parcelable {       * power rail measurement, or modeled in some fashion.  For example, an energy consumer may       * represent a combination of multiple rails or a portion of a rail shared between subsystems,       * e.g. WiFi and Bluetooth are often handled by the same chip, powered by a shared rail. -     * Some consumer names are standardized (see android.hardware.power.stats.EnergyConsumerType), -     * others are not. +     * Some consumer names are standardized, others are not.       */ +    @FlaggedApi("com.android.server.power.optimization.power_monitor_api")      public static final int POWER_MONITOR_TYPE_CONSUMER = 0;      /** @@ -46,6 +54,7 @@ public final class PowerMonitor implements Parcelable {       * no assumptions can be made about the source of those measurements across different devices,       * even if they have the same name.       */ +    @FlaggedApi("com.android.server.power.optimization.power_monitor_api")      public static final int POWER_MONITOR_TYPE_MEASUREMENT = 1;      /** @hide */ @@ -64,38 +73,60 @@ public final class PowerMonitor implements Parcelable {       * @hide       */      public final int index; +      @PowerMonitorType -    public final int type; +    private final int mType;      @NonNull -    public final String name; +    private final String mName;      /**       * @hide       */      public PowerMonitor(int index, int type, @NonNull String name) {          this.index = index; -        this.type = type; -        this.name = name; +        this.mType = type; +        this.mName = name; +    } + +    /** +     * Returns the type of the power monitor. +     */ +    @FlaggedApi("com.android.server.power.optimization.power_monitor_api") +    @PowerMonitorType +    public int getType() { +        return mType; +    } + +    /** +     * Returns the name of the power monitor, either a power rail or an energy consumer. +     */ +    @FlaggedApi("com.android.server.power.optimization.power_monitor_api") +    @NonNull +    public String getName() { +        return mName;      }      private PowerMonitor(Parcel in) {          index = in.readInt(); -        type = in.readInt(); -        name = in.readString(); +        mType = in.readInt(); +        mName = in.readString8();      } +    @FlaggedApi("com.android.server.power.optimization.power_monitor_api")      @Override      public void writeToParcel(@NonNull Parcel dest, int flags) {          dest.writeInt(index); -        dest.writeInt(type); -        dest.writeString(name); +        dest.writeInt(mType); +        dest.writeString8(mName);      } +    @FlaggedApi("com.android.server.power.optimization.power_monitor_api")      @Override      public int describeContents() {          return 0;      } +    @FlaggedApi("com.android.server.power.optimization.power_monitor_api")      @NonNull      public static final Creator<PowerMonitor> CREATOR = new Creator<>() {          @Override diff --git a/core/java/android/os/PowerMonitorReadings.java b/core/java/android/os/PowerMonitorReadings.java index e76705917c7a..bb677d529507 100644 --- a/core/java/android/os/PowerMonitorReadings.java +++ b/core/java/android/os/PowerMonitorReadings.java @@ -17,6 +17,7 @@  package android.os;  import android.annotation.ElapsedRealtimeLong; +import android.annotation.FlaggedApi;  import android.annotation.NonNull;  import java.util.Arrays; @@ -24,10 +25,10 @@ import java.util.Comparator;  /**   * A collection of energy measurements from Power Monitors. - * - * @hide   */ +@FlaggedApi("com.android.server.power.optimization.power_monitor_api")  public final class PowerMonitorReadings { +    @FlaggedApi("com.android.server.power.optimization.power_monitor_api")      public static final int ENERGY_UNAVAILABLE = -1;      @NonNull @@ -41,7 +42,7 @@ public final class PowerMonitorReadings {              Comparator.comparingInt(pm -> pm.index);      /** -     * @param powerMonitors array of power monitor (ODPM) rails, sorted by PowerMonitor.index +     * @param powerMonitors array of power monitor, sorted by PowerMonitor.index       * @hide       */      public PowerMonitorReadings(@NonNull PowerMonitor[] powerMonitors, @@ -56,7 +57,8 @@ public final class PowerMonitorReadings {       * Does not persist across reboots.       * Represents total energy: both on-battery and plugged-in.       */ -    public long getConsumedEnergyUws(@NonNull PowerMonitor powerMonitor) { +    @FlaggedApi("com.android.server.power.optimization.power_monitor_api") +    public long getConsumedEnergy(@NonNull PowerMonitor powerMonitor) {          int offset = Arrays.binarySearch(mPowerMonitors, powerMonitor, POWER_MONITOR_COMPARATOR);          if (offset >= 0) {              return mEnergyUws[offset]; @@ -67,6 +69,7 @@ public final class PowerMonitorReadings {      /**       * Elapsed realtime, in milliseconds, when the snapshot was taken.       */ +    @FlaggedApi("com.android.server.power.optimization.power_monitor_api")      @ElapsedRealtimeLong      public long getTimestamp(@NonNull PowerMonitor powerMonitor) {          int offset = Arrays.binarySearch(mPowerMonitors, powerMonitor, POWER_MONITOR_COMPARATOR); @@ -84,7 +87,7 @@ public final class PowerMonitorReadings {              if (i != 0) {                  sb.append(", ");              } -            sb.append(mPowerMonitors[i].name) +            sb.append(mPowerMonitors[i].getName())                      .append(" = ").append(mEnergyUws[i])                      .append(" (").append(mTimestampsMs[i]).append(')');          } diff --git a/core/java/android/os/RemoteException.java b/core/java/android/os/RemoteException.java index 970f4192c36c..ace5f7e8393f 100644 --- a/core/java/android/os/RemoteException.java +++ b/core/java/android/os/RemoteException.java @@ -66,7 +66,7 @@ public class RemoteException extends AndroidException {      /**       * Rethrow this exception when we know it came from the system server. This       * gives us an opportunity to throw a nice clean -     * {@link DeadSystemRuntimeException} signal to avoid spamming logs with +     * {@code DeadSystemRuntimeException} signal to avoid spamming logs with       * misleading stack traces.       * <p>       * Apps making calls into the system server may end up persisting internal diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig index 37559b32e841..b7f2e065bac8 100644 --- a/core/java/android/os/flags.aconfig +++ b/core/java/android/os/flags.aconfig @@ -16,7 +16,7 @@ flag {  flag {      name: "remove_app_profiler_pss_collection" -    namespace: "android_platform_power_optimization" +    namespace: "backstage_power"      description: "Replaces background PSS collection in AppProfiler with RSS"      bug: "297542292"  } diff --git a/core/java/android/os/health/SystemHealthManager.java b/core/java/android/os/health/SystemHealthManager.java index dfc43f46c57d..2d53341be654 100644 --- a/core/java/android/os/health/SystemHealthManager.java +++ b/core/java/android/os/health/SystemHealthManager.java @@ -16,6 +16,7 @@  package android.os.health; +import android.annotation.FlaggedApi;  import android.annotation.NonNull;  import android.annotation.Nullable;  import android.annotation.SystemService; @@ -24,7 +25,6 @@ import android.content.Context;  import android.os.BatteryStats;  import android.os.Build;  import android.os.Bundle; -import android.os.ConditionVariable;  import android.os.Handler;  import android.os.IPowerStatsService;  import android.os.PowerMonitor; @@ -157,39 +157,15 @@ public class SystemHealthManager {      }      /** -     * Returns a list of supported power monitors, which include raw ODPM rails and -     * modeled energy consumers.  If ODPM is unsupported by PowerStats HAL, this method returns -     * an empty array. +     * Asynchronously retrieves a list of supported  {@link PowerMonitor}'s, which include raw ODPM +     * (on-device power rail monitor) rails and modeled energy consumers.  If ODPM is unsupported +     * on this device this method delivers an empty list.       * -     * @hide -     */ -    @NonNull -    public List<PowerMonitor> getSupportedPowerMonitors() { -        synchronized (mPowerMonitorsLock) { -            if (mPowerMonitorsInfo != null) { -                return mPowerMonitorsInfo; -            } -        } -        ConditionVariable lock = new ConditionVariable(); -        // Populate mPowerMonitorsInfo by side-effect -        getSupportedPowerMonitors(null, unused -> lock.open()); -        lock.block(); - -        synchronized (mPowerMonitorsLock) { -            return mPowerMonitorsInfo; -        } -    } - -    /** -     * Asynchronously retrieves a list of supported power monitors, see -     * {@link #getSupportedPowerMonitors()} -     * -     * @param handler optional Handler to deliver the callback. If not supplied, the callback -     *                may be invoked on an arbitrary thread. +     * @param handler  optional Handler to deliver the callback. If not supplied, the callback +     *                 may be invoked on an arbitrary thread.       * @param onResult callback for the result -     * -     * @hide       */ +    @FlaggedApi("com.android.server.power.optimization.power_monitor_api")      public void getSupportedPowerMonitors(@Nullable Handler handler,              @NonNull Consumer<List<PowerMonitor>> onResult) {          final List<PowerMonitor> result; @@ -229,35 +205,6 @@ public class SystemHealthManager {          }      } -    /** -     * Retrieves the accumulated power consumption reported by the specified power monitors. -     * -     * @param powerMonitors power monitors to be returned. -     * -     * @hide -     */ -    @NonNull -    public PowerMonitorReadings getPowerMonitorReadings(@NonNull List<PowerMonitor> powerMonitors) { -        PowerMonitorReadings[] outReadings = new PowerMonitorReadings[1]; -        RuntimeException[] outException = new RuntimeException[1]; -        ConditionVariable lock = new ConditionVariable(); -        getPowerMonitorReadings(powerMonitors, null, -                pms -> { -                    outReadings[0] = pms; -                    lock.open(); -                }, -                error -> { -                    outException[0] = error; -                    lock.open(); -                } -        ); -        lock.block(); -        if (outException[0] != null) { -            throw outException[0]; -        } -        return outReadings[0]; -    } -      private static final Comparator<PowerMonitor> POWER_MONITOR_COMPARATOR =              Comparator.comparingInt(pm -> pm.index); @@ -270,9 +217,8 @@ public class SystemHealthManager {       *                      may be invoked on an arbitrary thread.       * @param onSuccess     callback for the result       * @param onError       callback invoked in case of an error -     * -     * @hide       */ +    @FlaggedApi("com.android.server.power.optimization.power_monitor_api")      public void getPowerMonitorReadings(@NonNull List<PowerMonitor> powerMonitors,              @Nullable Handler handler, @NonNull Consumer<PowerMonitorReadings> onSuccess,              @NonNull Consumer<RuntimeException> onError) { diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig index d8534dd5fbc1..3f06a91f6e5b 100644 --- a/core/java/android/permission/flags.aconfig +++ b/core/java/android/permission/flags.aconfig @@ -21,3 +21,17 @@ flag {      description: "enable role controller in system server"      bug: "302562590"  } + +flag { +  name: "set_next_attribution_source" +  namespace: "permissions" +  description: "enable AttributionSource.setNextAttributionSource" +  bug: "304478648" +} + +flag { +    name: "should_register_attribution_source" +    namespace: "permissions" +    description: "enable the shouldRegisterAttributionSource API" +    bug: "305057691" +} diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 7e5999e695ea..f0906b1e3c06 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1800,7 +1800,6 @@ public final class Settings {       * Input: Nothing.       * <p>       * Output: Nothing. -     * @see android.service.notification.NotificationAssistantService       */      @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)      public static final String ACTION_NOTIFICATION_ASSISTANT_SETTINGS = @@ -2507,8 +2506,8 @@ public final class Settings {       * to the caller package.       * <p>       * <b>NOTE: </b> Applications should call -     * {@link android.credentials.CredentialManager#isEnabledCredentialProviderService()} -     * and only use this action to start an activity if they return {@code false}. +     * {@link android.credentials.CredentialManager#isEnabledCredentialProviderService( +     * ComponentName)} and only use this action to start an activity if they return {@code false}.       */      @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)      public static final String ACTION_CREDENTIAL_PROVIDER = @@ -12383,7 +12382,7 @@ public final class Settings {           * Value to specify if the device's UTC system clock should be set automatically, e.g. using           * telephony signals like NITZ, or other sources like GNSS or NTP.           * -         * <p>Prefer {@link android.app.time.TimeManager} API calls to determine the state of +         * <p>Prefer {@code android.app.time.TimeManager} API calls to determine the state of           * automatic time detection instead of directly observing this setting as it may be ignored           * by the time_detector service under various conditions.           * @@ -12396,7 +12395,7 @@ public final class Settings {           * Value to specify if the device's time zone system property should be set automatically,           * e.g. using telephony signals like MCC and NITZ, or other mechanisms like the location.           * -         * <p>Prefer {@link android.app.time.TimeManager} API calls to determine the state of +         * <p>Prefer {@code android.app.time.TimeManager} API calls to determine the state of           * automatic time zone detection instead of directly observing this setting as it may be           * ignored by the time_zone_detector service under various conditions.           * diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java index 3fb94ee93c14..7ea74d375ffb 100644 --- a/core/java/android/service/autofill/FillResponse.java +++ b/core/java/android/service/autofill/FillResponse.java @@ -406,7 +406,7 @@ public final class FillResponse implements Parcelable {           *           * Call this when a field has been detected with a type.           * -         * Altough similiarly named with {@link setFieldClassificationIds}, +         * Altough similiarly named with {@link #setFieldClassificationIds},           * it provides a different functionality - setFieldClassificationIds should           * be used when a field is only suspected to be Autofillable.           * This method should be used when a field is certainly Autofillable diff --git a/core/java/android/service/autofill/SaveInfo.java b/core/java/android/service/autofill/SaveInfo.java index 954cc960665f..53706cd3f887 100644 --- a/core/java/android/service/autofill/SaveInfo.java +++ b/core/java/android/service/autofill/SaveInfo.java @@ -813,7 +813,7 @@ public final class SaveInfo implements Parcelable {           * If no {@link #Builder(int, AutofillId[]) required ids},           * or {@link #setOptionalIds(AutofillId[]) optional ids}, or {@link #FLAG_DELAY_SAVE}           * were set, Save Dialog will only be triggered if platform detection is enabled, which -         * is indicated when {@link FillRequest.getHints()} is not empty. +         * is indicated when {@link FillRequest#getHints()} is not empty.           */          public SaveInfo build() {              throwIfDestroyed(); diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index 1cfff14b2d7c..759953e4aca4 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -896,8 +896,7 @@ public abstract class NotificationListenerService extends Service {       * <p>This method will throw a security exception if you don't have access to notifications       * for the given user.</p>       * <p>The caller must have {@link CompanionDeviceManager#getAssociations() an associated -     * device} or be the {@link NotificationAssistantService notification assistant} in order to -     * use this method. +     * device} or be the notification assistant in order to use this method.       *       * @param pkg The package to retrieve channels for.       */ @@ -920,8 +919,7 @@ public abstract class NotificationListenerService extends Service {       * <p>This method will throw a security exception if you don't have access to notifications       * for the given user.</p>       * <p>The caller must have {@link CompanionDeviceManager#getAssociations() an associated -     * device} or be the {@link NotificationAssistantService notification assistant} in order to -     * use this method. +     * device} or be the notification assistant in order to use this method.       *       * @param pkg The package to retrieve channel groups for.       */ @@ -2001,7 +1999,7 @@ public abstract class NotificationListenerService extends Service {          /**           * Returns a list of smart {@link Notification.Action} that can be added by the -         * {@link NotificationAssistantService} +         * notification assistant.           */          public @NonNull List<Notification.Action> getSmartActions() {              return mSmartActions == null ? Collections.emptyList() : mSmartActions; @@ -2022,8 +2020,7 @@ public abstract class NotificationListenerService extends Service {          }          /** -         * Returns a list of smart replies that can be added by the -         * {@link NotificationAssistantService} +         * Returns a list of smart replies that can be added by the notification assistant.           */          public @NonNull List<CharSequence> getSmartReplies() {              return mSmartReplies == null ? Collections.emptyList() : mSmartReplies; diff --git a/core/java/android/service/quickaccesswallet/WalletCard.java b/core/java/android/service/quickaccesswallet/WalletCard.java index 4a4fd041fb19..bf129c551605 100644 --- a/core/java/android/service/quickaccesswallet/WalletCard.java +++ b/core/java/android/service/quickaccesswallet/WalletCard.java @@ -372,9 +372,9 @@ public final class WalletCard implements Parcelable {          }          /** -         * Set of locations this card might be useful at. If {@link -         * PackageManager.FEATURE_WALLET_LOCATION_BASED_SUGGESTIONS} is enabled, the card might be -         * shown to the user when a user is near one of these locations. +         * Set of locations this card might be useful at. If +         * {@link android.content.pm.PackageManager#FEATURE_WALLET_LOCATION_BASED_SUGGESTIONS} is +         * enabled, the card might be shown to the user when a user is near one of these locations.           */          @NonNull          public Builder setCardLocations(@NonNull List<Location> cardLocations) { diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java index 3f41c56ac7f1..d2806217a276 100644 --- a/core/java/android/service/voice/VoiceInteractionService.java +++ b/core/java/android/service/voice/VoiceInteractionService.java @@ -520,7 +520,7 @@ public class VoiceInteractionService extends Service {              @NonNull String keyphrase, @SuppressLint("UseIcu") @NonNull Locale locale,              @NonNull @CallbackExecutor Executor executor,              @NonNull AlwaysOnHotwordDetector.Callback callback) { -        // TODO (b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning +        // TODO(b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning          Objects.requireNonNull(keyphrase);          Objects.requireNonNull(locale); @@ -546,6 +546,10 @@ public class VoiceInteractionService extends Service {              @NonNull SoundTrigger.ModuleProperties moduleProperties,              @NonNull @CallbackExecutor Executor executor,              @NonNull AlwaysOnHotwordDetector.Callback callback) { +        // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the +        // {@link #createAlwaysOnHotwordDetectorForTest(String, Locale, +        // SoundTrigger.ModuleProperties, AlwaysOnHotwordDetector.Callback)} and replace with the +        // permission RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully launched.          Objects.requireNonNull(keyphrase);          Objects.requireNonNull(locale); @@ -612,6 +616,11 @@ public class VoiceInteractionService extends Service {              @Nullable PersistableBundle options,              @Nullable SharedMemory sharedMemory,              @SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback) { +        // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the +        // {@link #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory, +        // AlwaysOnHotwordDetector.Callback)} and replace with the permission +        // RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully launched. +          return createAlwaysOnHotwordDetectorInternal(keyphrase, locale,                  /* supportHotwordDetectionService= */ true, options, sharedMemory,                  /* modulProperties */ null, /* executor= */ null, callback); @@ -663,7 +672,11 @@ public class VoiceInteractionService extends Service {              @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory,              @NonNull @CallbackExecutor Executor executor,              @NonNull AlwaysOnHotwordDetector.Callback callback) { -        // TODO (b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning +        // TODO(b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning +        // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the +        // {@link #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory, +        // Executor, AlwaysOnHotwordDetector.Callback)} and replace with the permission +        // RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully launched.          Objects.requireNonNull(keyphrase);          Objects.requireNonNull(locale); @@ -690,6 +703,10 @@ public class VoiceInteractionService extends Service {              @NonNull SoundTrigger.ModuleProperties moduleProperties,              @NonNull @CallbackExecutor Executor executor,              @NonNull AlwaysOnHotwordDetector.Callback callback) { +        // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the +        // {@link #createAlwaysOnHotwordDetectorForTest(String, Locale, PersistableBundle, +        // SharedMemory, SoundTrigger.ModuleProperties, Executor, AlwaysOnHotwordDetector.Callback)} +        // and replace with the permission RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully launched.          Objects.requireNonNull(keyphrase);          Objects.requireNonNull(locale); diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java index cabcae30851f..d40b39e18edb 100644 --- a/core/java/android/service/voice/VoiceInteractionSession.java +++ b/core/java/android/service/voice/VoiceInteractionSession.java @@ -1809,7 +1809,7 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall       * in milliseconds of the KeyEvent that triggered Assistant and       * Intent.EXTRA_ASSIST_INPUT_DEVICE_ID (android.intent.extra.ASSIST_INPUT_DEVICE_ID)       *  referring to the device that sent the request. Starting from Android 14, the system will -     * add {@link VoiceInteractionService#KEY_SHOW_SESSION_ID}, the Bundle is not null. But the +     * add {@link #KEY_SHOW_SESSION_ID}, the Bundle is not null. But the       * application should handle null case before Android 14.       * @param showFlags The show flags originally provided to       * {@link VoiceInteractionService#showSession VoiceInteractionService.showSession}. diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java index 8862f1d74ab1..a0cd0748f509 100644 --- a/core/java/android/text/DynamicLayout.java +++ b/core/java/android/text/DynamicLayout.java @@ -1274,7 +1274,7 @@ public class DynamicLayout extends Layout {      }      /** -     * Gets the {@link LineBreakconfig} used in this DynamicLayout. +     * Gets the {@link LineBreakConfig} used in this DynamicLayout.       * Use this only to consult the LineBreakConfig's properties and not       * to change them.       * diff --git a/core/java/android/text/WordSegmentFinder.java b/core/java/android/text/WordSegmentFinder.java index be002f3102d3..b0a70eae902a 100644 --- a/core/java/android/text/WordSegmentFinder.java +++ b/core/java/android/text/WordSegmentFinder.java @@ -24,7 +24,7 @@ import android.text.method.WordIterator;  /**   * Implementation of {@link SegmentFinder} using words as the text segment. Word boundaries are - * found using {@link WordIterator}. Whitespace characters are excluded, so they are not included in + * found using {@code WordIterator}. Whitespace characters are excluded, so they are not included in   * any text segments.   *   * <p>For example, the text "Hello, World!" would be subdivided into four text segments: "Hello", diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 2906d86f803d..766e924c994e 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -200,6 +200,12 @@ public class FeatureFlagUtils {      public static final String SETTINGS_REMOTE_DEVICE_CREDENTIAL_VALIDATION =              "settings_remote_device_credential_validation"; +    /** +     * Flag to enable/disable to start treating any calls to "suspend" an app as "quarantine". +     * @hide +     */ +    public static final String SETTINGS_TREAT_PAUSE_AS_QUARANTINE = +            "settings_treat_pause_as_quarantine";      private static final Map<String, String> DEFAULT_FLAGS; @@ -247,6 +253,7 @@ public class FeatureFlagUtils {          DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_FINGERPRINT_SETTINGS, "false");          // TODO: b/298454866 Replace with Trunk Stable Feature Flag          DEFAULT_FLAGS.put(SETTINGS_REMOTEAUTH_ENROLLMENT_SETTINGS, "false"); +        DEFAULT_FLAGS.put(SETTINGS_TREAT_PAUSE_AS_QUARANTINE, "false");      }      private static final Set<String> PERSISTENT_FLAGS; @@ -264,6 +271,7 @@ public class FeatureFlagUtils {          PERSISTENT_FLAGS.add(SETTINGS_ENABLE_SPA);          PERSISTENT_FLAGS.add(SETTINGS_ENABLE_SPA_PHASE2);          PERSISTENT_FLAGS.add(SETTINGS_PREFER_ACCESSIBILITY_MENU_IN_SYSTEM); +        PERSISTENT_FLAGS.add(SETTINGS_TREAT_PAUSE_AS_QUARANTINE);      }      /** diff --git a/core/java/android/view/ContentRecordingSession.java b/core/java/android/view/ContentRecordingSession.java index a89f79540c65..dc41b70d1683 100644 --- a/core/java/android/view/ContentRecordingSession.java +++ b/core/java/android/view/ContentRecordingSession.java @@ -52,6 +52,12 @@ public final class ContentRecordingSession implements Parcelable {       */      public static final int RECORD_CONTENT_TASK = 1; +    /** Full screen sharing (app is not selected). */ +    public static final int TARGET_UID_FULL_SCREEN = -1; + +    /** Can't report (e.g. side loaded app). */ +    public static final int TARGET_UID_UNKNOWN = -2; +      /**       * Unique logical identifier of the {@link android.hardware.display.VirtualDisplay} that has       * recorded content rendered to its surface. @@ -89,27 +95,36 @@ public final class ContentRecordingSession implements Parcelable {       */      private boolean mWaitingForConsent = false; +    /** UID of the package that is captured if selected. */ +    private int mTargetUid = TARGET_UID_UNKNOWN; +      /**       * Default instance, with recording the display.       */      private ContentRecordingSession() {      } -    /** -     * Returns an instance initialized for recording the indicated display. -     */ +    /** Returns an instance initialized for recording the indicated display. */      public static ContentRecordingSession createDisplaySession(int displayToMirror) { -        return new ContentRecordingSession().setDisplayToRecord(displayToMirror) -                .setContentToRecord(RECORD_CONTENT_DISPLAY); +        return new ContentRecordingSession() +                .setDisplayToRecord(displayToMirror) +                .setContentToRecord(RECORD_CONTENT_DISPLAY) +                .setTargetUid(TARGET_UID_FULL_SCREEN);      } -    /** -     * Returns an instance initialized for task recording. -     */ +    /** Returns an instance initialized for task recording. */      public static ContentRecordingSession createTaskSession(              @NonNull IBinder taskWindowContainerToken) { -        return new ContentRecordingSession().setContentToRecord(RECORD_CONTENT_TASK) -                .setTokenToRecord(taskWindowContainerToken); +        return createTaskSession(taskWindowContainerToken, TARGET_UID_UNKNOWN); +    } + +    /** Returns an instance initialized for task recording. */ +    public static ContentRecordingSession createTaskSession( +            @NonNull IBinder taskWindowContainerToken, int targetUid) { +        return new ContentRecordingSession() +                .setContentToRecord(RECORD_CONTENT_TASK) +                .setTokenToRecord(taskWindowContainerToken) +                .setTargetUid(targetUid);      }      /** @@ -175,13 +190,33 @@ public final class ContentRecordingSession implements Parcelable {          }      } +    @IntDef(prefix = "TARGET_UID_", value = { +        TARGET_UID_FULL_SCREEN, +        TARGET_UID_UNKNOWN +    }) +    @Retention(RetentionPolicy.SOURCE) +    @DataClass.Generated.Member +    public @interface TargetUid {} + +    @DataClass.Generated.Member +    public static String targetUidToString(@TargetUid int value) { +        switch (value) { +            case TARGET_UID_FULL_SCREEN: +                    return "TARGET_UID_FULL_SCREEN"; +            case TARGET_UID_UNKNOWN: +                    return "TARGET_UID_UNKNOWN"; +            default: return Integer.toHexString(value); +        } +    } +      @DataClass.Generated.Member      /* package-private */ ContentRecordingSession(              int virtualDisplayId,              @RecordContent int contentToRecord,              int displayToRecord,              @Nullable IBinder tokenToRecord, -            boolean waitingForConsent) { +            boolean waitingForConsent, +            int targetUid) {          this.mVirtualDisplayId = virtualDisplayId;          this.mContentToRecord = contentToRecord; @@ -196,6 +231,7 @@ public final class ContentRecordingSession implements Parcelable {          this.mDisplayToRecord = displayToRecord;          this.mTokenToRecord = tokenToRecord;          this.mWaitingForConsent = waitingForConsent; +        this.mTargetUid = targetUid;          // onConstructed(); // You can define this method to get a callback      } @@ -251,6 +287,14 @@ public final class ContentRecordingSession implements Parcelable {      }      /** +     * UID of the package that is captured if selected. +     */ +    @DataClass.Generated.Member +    public int getTargetUid() { +        return mTargetUid; +    } + +    /**       * Unique logical identifier of the {@link android.hardware.display.VirtualDisplay} that has       * recorded content rendered to its surface.       */ @@ -314,6 +358,15 @@ public final class ContentRecordingSession implements Parcelable {          return this;      } +    /** +     * UID of the package that is captured if selected. +     */ +    @DataClass.Generated.Member +    public @NonNull ContentRecordingSession setTargetUid( int value) { +        mTargetUid = value; +        return this; +    } +      @Override      @DataClass.Generated.Member      public String toString() { @@ -325,7 +378,8 @@ public final class ContentRecordingSession implements Parcelable {                  "contentToRecord = " + recordContentToString(mContentToRecord) + ", " +                  "displayToRecord = " + mDisplayToRecord + ", " +                  "tokenToRecord = " + mTokenToRecord + ", " + -                "waitingForConsent = " + mWaitingForConsent + +                "waitingForConsent = " + mWaitingForConsent + ", " + +                "targetUid = " + mTargetUid +          " }";      } @@ -346,7 +400,8 @@ public final class ContentRecordingSession implements Parcelable {                  && mContentToRecord == that.mContentToRecord                  && mDisplayToRecord == that.mDisplayToRecord                  && java.util.Objects.equals(mTokenToRecord, that.mTokenToRecord) -                && mWaitingForConsent == that.mWaitingForConsent; +                && mWaitingForConsent == that.mWaitingForConsent +                && mTargetUid == that.mTargetUid;      }      @Override @@ -361,6 +416,7 @@ public final class ContentRecordingSession implements Parcelable {          _hash = 31 * _hash + mDisplayToRecord;          _hash = 31 * _hash + java.util.Objects.hashCode(mTokenToRecord);          _hash = 31 * _hash + Boolean.hashCode(mWaitingForConsent); +        _hash = 31 * _hash + mTargetUid;          return _hash;      } @@ -378,6 +434,7 @@ public final class ContentRecordingSession implements Parcelable {          dest.writeInt(mContentToRecord);          dest.writeInt(mDisplayToRecord);          if (mTokenToRecord != null) dest.writeStrongBinder(mTokenToRecord); +        dest.writeInt(mTargetUid);      }      @Override @@ -397,6 +454,7 @@ public final class ContentRecordingSession implements Parcelable {          int contentToRecord = in.readInt();          int displayToRecord = in.readInt();          IBinder tokenToRecord = (flg & 0x8) == 0 ? null : (IBinder) in.readStrongBinder(); +        int targetUid = in.readInt();          this.mVirtualDisplayId = virtualDisplayId;          this.mContentToRecord = contentToRecord; @@ -412,6 +470,7 @@ public final class ContentRecordingSession implements Parcelable {          this.mDisplayToRecord = displayToRecord;          this.mTokenToRecord = tokenToRecord;          this.mWaitingForConsent = waitingForConsent; +        this.mTargetUid = targetUid;          // onConstructed(); // You can define this method to get a callback      } @@ -442,6 +501,7 @@ public final class ContentRecordingSession implements Parcelable {          private int mDisplayToRecord;          private @Nullable IBinder mTokenToRecord;          private boolean mWaitingForConsent; +        private int mTargetUid;          private long mBuilderFieldsSet = 0L; @@ -513,10 +573,21 @@ public final class ContentRecordingSession implements Parcelable {              return this;          } +        /** +         * UID of the package that is captured if selected. +         */ +        @DataClass.Generated.Member +        public @NonNull Builder setTargetUid(int value) { +            checkNotUsed(); +            mBuilderFieldsSet |= 0x20; +            mTargetUid = value; +            return this; +        } +          /** Builds the instance. This builder should not be touched after calling this! */          public @NonNull ContentRecordingSession build() {              checkNotUsed(); -            mBuilderFieldsSet |= 0x20; // Mark builder used +            mBuilderFieldsSet |= 0x40; // Mark builder used              if ((mBuilderFieldsSet & 0x1) == 0) {                  mVirtualDisplayId = INVALID_DISPLAY; @@ -533,17 +604,21 @@ public final class ContentRecordingSession implements Parcelable {              if ((mBuilderFieldsSet & 0x10) == 0) {                  mWaitingForConsent = false;              } +            if ((mBuilderFieldsSet & 0x20) == 0) { +                mTargetUid = TARGET_UID_UNKNOWN; +            }              ContentRecordingSession o = new ContentRecordingSession(                      mVirtualDisplayId,                      mContentToRecord,                      mDisplayToRecord,                      mTokenToRecord, -                    mWaitingForConsent); +                    mWaitingForConsent, +                    mTargetUid);              return o;          }          private void checkNotUsed() { -            if ((mBuilderFieldsSet & 0x20) != 0) { +            if ((mBuilderFieldsSet & 0x40) != 0) {                  throw new IllegalStateException(                          "This Builder should not be reused. Use a new Builder instance instead");              } @@ -551,10 +626,10 @@ public final class ContentRecordingSession implements Parcelable {      }      @DataClass.Generated( -            time = 1683628463074L, +            time = 1697456140720L,              codegenVersion = "1.0.23",              sourceFile = "frameworks/base/core/java/android/view/ContentRecordingSession.java", -            inputSignatures = "public static final  int RECORD_CONTENT_DISPLAY\npublic static final  int RECORD_CONTENT_TASK\nprivate  int mVirtualDisplayId\nprivate @android.view.ContentRecordingSession.RecordContent int mContentToRecord\nprivate  int mDisplayToRecord\nprivate @android.annotation.Nullable android.os.IBinder mTokenToRecord\nprivate  boolean mWaitingForConsent\npublic static  android.view.ContentRecordingSession createDisplaySession(int)\npublic static  android.view.ContentRecordingSession createTaskSession(android.os.IBinder)\npublic static  boolean isValid(android.view.ContentRecordingSession)\npublic static  boolean isProjectionOnSameDisplay(android.view.ContentRecordingSession,android.view.ContentRecordingSession)\nclass ContentRecordingSession extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genToString=true, genSetters=true, genEqualsHashCode=true)") +            inputSignatures = "public static final  int RECORD_CONTENT_DISPLAY\npublic static final  int RECORD_CONTENT_TASK\npublic static final  int TARGET_UID_FULL_SCREEN\npublic static final  int TARGET_UID_UNKNOWN\nprivate  int mVirtualDisplayId\nprivate @android.view.ContentRecordingSession.RecordContent int mContentToRecord\nprivate  int mDisplayToRecord\nprivate @android.annotation.Nullable android.os.IBinder mTokenToRecord\nprivate  boolean mWaitingForConsent\nprivate  int mTargetUid\npublic static  android.view.ContentRecordingSession createDisplaySession(int)\npublic static  android.view.ContentRecordingSession createTaskSession(android.os.IBinder)\npublic static  android.view.ContentRecordingSession createTaskSession(android.os.IBinder,int)\npublic static  boolean isValid(android.view.ContentRecordingSession)\npublic static  boolean isProjectionOnSameDisplay(android.view.ContentRecordingSession,android.view.ContentRecordingSession)\nclass ContentRecordingSession extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genToString=true, genSetters=true, genEqualsHashCode=true)")      @Deprecated      private void __metadata() {} diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 0b2b6ce33666..506945501609 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -26,6 +26,7 @@ import android.annotation.Nullable;  import android.annotation.RequiresPermission;  import android.annotation.SuppressLint;  import android.annotation.TestApi; +import android.app.ActivityThread;  import android.app.KeyguardManager;  import android.app.WindowConfiguration;  import android.compat.annotation.UnsupportedAppUsage; @@ -1366,7 +1367,8 @@ public final class Display {              // form of the larger DISPLAY_CHANGED event              mGlobal.registerDisplayListener(toRegister, executor,                      DisplayManager.EVENT_FLAG_HDR_SDR_RATIO_CHANGED -                            | DisplayManagerGlobal.EVENT_DISPLAY_CHANGED); +                            | DisplayManagerGlobal.EVENT_DISPLAY_CHANGED, +                    ActivityThread.currentPackageName());          }      } diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java index c35b6907cd9d..9f886c826174 100644 --- a/core/java/android/view/InputDevice.java +++ b/core/java/android/view/InputDevice.java @@ -778,7 +778,8 @@ public final class InputDevice implements Parcelable {       * Each gamepad or joystick is given a unique, positive controller number when initially       * configured by the system. This number may change due to events such as device disconnects /       * reconnects or user initiated reassignment. Any change in number will trigger an event that -     * can be observed by registering an {@link InputManagerGlobal.InputDeviceListener}. +     * can be observed by registering an +     * {@link android.hardware.input.InputManager.InputDeviceListener}.       * </p>       * <p>       * All input devices which are not gamepads or joysticks will be assigned a controller number diff --git a/core/java/android/view/ScrollFeedbackProvider.java b/core/java/android/view/ScrollFeedbackProvider.java index 78716f5646f6..0ba414817247 100644 --- a/core/java/android/view/ScrollFeedbackProvider.java +++ b/core/java/android/view/ScrollFeedbackProvider.java @@ -17,7 +17,6 @@  package android.view;  import android.annotation.FlaggedApi; -import android.annotation.NonNull;  import android.view.flags.Flags;  /** @@ -43,7 +42,8 @@ import android.view.flags.Flags;   * the scroll event. If calling this method in response to a {@link MotionEvent}, use the device ID   * that is reported by the event, which can be obtained using {@link MotionEvent#getDeviceId()}.   * Otherwise, use a valid ID that is obtained from {@link InputDevice#getId()}, or from an - * {@link InputManager} instance ({@link InputManager#getInputDeviceIds()} gives all the valid input + * {@link android.hardware.input.InputManager} instance + * ({@link android.hardware.input.InputManager#getInputDeviceIds()} gives all the valid input   * device IDs).   *   * <li><p><b>source</b>: should always be the {@link InputDevice} source that generated the scroll diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index b080b7115728..2f3d73a36425 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -263,7 +263,7 @@ public final class SurfaceControl implements Parcelable {      private static native void nativeSetDefaultFrameRateCompatibility(long transactionObj,              long nativeObject, int compatibility);      private static native void nativeSetFrameRateCategory( -            long transactionObj, long nativeObject, int category); +            long transactionObj, long nativeObject, int category, boolean smoothSwitchOnly);      private static native void nativeSetFrameRateSelectionStrategy(              long transactionObj, long nativeObject, int strategy);      private static native long nativeGetHandle(long nativeObject); @@ -3709,6 +3709,10 @@ public final class SurfaceControl implements Parcelable {           * @param sc The SurfaceControl to specify the frame rate category of.           * @param category The frame rate category of this surface. The category value may influence           * the system's choice of display frame rate. +         * @param smoothSwitchOnly Set to {@code true} to indicate the display frame rate should not +         * change if changing it would cause jank. Else {@code false}. +         * This parameter is ignored when {@code category} is +         * {@link Surface#FRAME_RATE_CATEGORY_DEFAULT}.           *           * @return This transaction object.           * @@ -3717,10 +3721,10 @@ public final class SurfaceControl implements Parcelable {           * @hide           */          @NonNull -        public Transaction setFrameRateCategory( -                @NonNull SurfaceControl sc, @Surface.FrameRateCategory int category) { +        public Transaction setFrameRateCategory(@NonNull SurfaceControl sc, +                @Surface.FrameRateCategory int category, boolean smoothSwitchOnly) {              checkPreconditions(sc); -            nativeSetFrameRateCategory(mNativeObject, sc.mNativeObject, category); +            nativeSetFrameRateCategory(mNativeObject, sc.mNativeObject, category, smoothSwitchOnly);              return this;          } @@ -4266,8 +4270,7 @@ public final class SurfaceControl implements Parcelable {           * be somewhat arbitrary, and so there are some somewhat arbitrary decisions in           * this API as well.           * <p> -         * @param sc         The {@link SurfaceControl} to set the -         *                   {@link TrustedPresentationCallback} on +         * @param sc         The {@link SurfaceControl} to set the callback on           * @param thresholds The {@link TrustedPresentationThresholds} that will specify when the to           *                   invoke the callback.           * @param executor   The {@link Executor} where the callback will be invoked on. @@ -4300,10 +4303,9 @@ public final class SurfaceControl implements Parcelable {          }          /** -         * Clears the {@link TrustedPresentationCallback} for a specific {@link SurfaceControl} +         * Clears the callback for a specific {@link SurfaceControl}           * -         * @param sc The SurfaceControl that the {@link TrustedPresentationCallback} should be -         *           cleared from +         * @param sc The SurfaceControl that the callback should be cleared from           * @return This transaction           */          @NonNull diff --git a/core/java/android/view/SurfaceControlRegistry.java b/core/java/android/view/SurfaceControlRegistry.java index 67ac811287cb..0f35048cac67 100644 --- a/core/java/android/view/SurfaceControlRegistry.java +++ b/core/java/android/view/SurfaceControlRegistry.java @@ -78,6 +78,11 @@ public class SurfaceControlRegistry {              for (int i = 0; i < size; i++) {                  final Map.Entry<SurfaceControl, Long> entry = entries.get(i);                  final SurfaceControl sc = entry.getKey(); +                if (sc == null) { +                    // Just skip if the key has since been removed from the weak hash map +                    continue; +                } +                  final long timeRegistered = entry.getValue();                  pw.print("  ");                  pw.print(sc.getName()); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 16318e0a3f49..49b16c7697c9 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -19,6 +19,8 @@ package android.view;  import static android.content.res.Resources.ID_NULL;  import static android.os.Trace.TRACE_TAG_APP;  import static android.view.ContentInfo.SOURCE_DRAG_AND_DROP; +import static android.view.Surface.FRAME_RATE_CATEGORY_LOW; +import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;  import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED;  import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_INVALID_BOUNDS;  import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_MISSING_WINDOW; @@ -27,6 +29,7 @@ import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ER  import static android.view.displayhash.DisplayHashResultCallback.EXTRA_DISPLAY_HASH;  import static android.view.displayhash.DisplayHashResultCallback.EXTRA_DISPLAY_HASH_ERROR_CODE;  import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API; +import static android.view.flags.Flags.toolkitSetFrameRate;  import static android.view.flags.Flags.viewVelocityApi;  import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS; @@ -114,6 +117,7 @@ import android.sysprop.DisplayProperties;  import android.text.InputType;  import android.text.TextUtils;  import android.util.AttributeSet; +import android.util.DisplayMetrics;  import android.util.FloatProperty;  import android.util.LayoutDirection;  import android.util.Log; @@ -5509,6 +5513,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,      private ViewTranslationResponse mViewTranslationResponse;      /** +     * A threshold value to determine the frame rate category of the View based on the size. +     */ +    private static final float FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD = 0.07f; + +    /**       * Simple constructor to use when creating a view from code.       *       * @param context The Context the view is running in, through which it can @@ -9308,6 +9317,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,                  structure.setAutofillType(autofillType);                  structure.setAutofillHints(getAutofillHints());                  structure.setAutofillValue(getAutofillValue()); +                structure.setIsCredential(isCredential());              }              structure.setImportantForAutofill(getImportantForAutofill());              structure.setReceiveContentMimeTypes(getReceiveContentMimeTypes()); @@ -20182,6 +20192,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,              return;          } +        // For VRR to vote the preferred frame rate +        votePreferredFrameRate(); +          // Reset content capture caches          mPrivateFlags4 &= ~PFLAG4_CONTENT_CAPTURE_IMPORTANCE_MASK;          mContentCaptureSessionCached = false; @@ -20284,6 +20297,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,       */      protected void damageInParent() {          if (mParent != null && mAttachInfo != null) { +            // For VRR to vote the preferred frame rate +            votePreferredFrameRate();              mParent.onDescendantInvalidated(this, this);          }      } @@ -32980,6 +32995,40 @@ public class View implements Drawable.Callback, KeyEvent.Callback,          return null;      } +    private float getSizePercentage() { +        if (mResources == null || getAlpha() == 0 || getVisibility() != VISIBLE) { +            return 0; +        } + +        DisplayMetrics displayMetrics = mResources.getDisplayMetrics(); +        int screenSize = displayMetrics.widthPixels +                * displayMetrics.heightPixels; +        int viewSize = getWidth() * getHeight(); + +        if (screenSize == 0 || viewSize == 0) { +            return 0f; +        } +        return (float) viewSize / screenSize; +    } + +    private int calculateFrameRateCategory() { +        float sizePercentage = getSizePercentage(); + +        if (sizePercentage <= FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD) { +            return FRAME_RATE_CATEGORY_LOW; +        } else { +            return FRAME_RATE_CATEGORY_NORMAL; +        } +    } + +    private void votePreferredFrameRate() { +        // use toolkitSetFrameRate flag to gate the change +        ViewRootImpl viewRootImpl = getViewRootImpl(); +        if (toolkitSetFrameRate() && viewRootImpl != null && getSizePercentage() > 0) { +            viewRootImpl.votePreferredFrameRateCategory(calculateFrameRateCategory()); +        } +    } +      /**       * Set the current velocity of the View, we only track positive value.       * We will use the velocity information to adjust the frame rate when applicable. diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index b5648cc90dcd..9d15c78a6e9e 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -24,6 +24,8 @@ import static android.view.Display.DEFAULT_DISPLAY;  import static android.view.Display.INVALID_DISPLAY;  import static android.view.InputDevice.SOURCE_CLASS_NONE;  import static android.view.InsetsSource.ID_IME; +import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH; +import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;  import static android.view.View.PFLAG_DRAW_ANIMATION;  import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;  import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; @@ -74,7 +76,10 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_E  import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE;  import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;  import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;  import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; +import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; +import static android.view.WindowManager.LayoutParams.TYPE_DRAWN_APPLICATION;  import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;  import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL;  import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT; @@ -87,6 +92,7 @@ import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;  import static android.view.accessibility.Flags.forceInvertColor;  import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER;  import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER; +import static android.view.flags.Flags.toolkitSetFrameRate;  import android.Manifest;  import android.accessibilityservice.AccessibilityService; @@ -732,6 +738,7 @@ public final class ViewRootImpl implements ViewParent,      private SurfaceControl mBoundsLayer;      private final SurfaceSession mSurfaceSession = new SurfaceSession();      private final Transaction mTransaction = new Transaction(); +    private final Transaction mFrameRateTransaction = new Transaction();      @UnsupportedAppUsage      boolean mAdded; @@ -955,6 +962,34 @@ public final class ViewRootImpl implements ViewParent,      private AccessibilityWindowAttributes mAccessibilityWindowAttributes; +    /* +     * for Variable Refresh Rate project +     */ + +    // The preferred frame rate category of the view that +    // could be updated on a frame-by-frame basis. +    private int mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE; +    // The preferred frame rate category of the last frame that +    // could be used to lower frame rate after touch boost +    private int mLastPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE; +    // The preferred frame rate of the view that is mainly used for +    // touch boosting, view velocity handling, and TextureView. +    private float mPreferredFrameRate = 0; +    // Used to check if there were any view invalidations in +    // the previous time frame (FRAME_RATE_IDLENESS_REEVALUATE_TIME). +    private boolean mHasInvalidation = false; +    // Used to check if it is in the touch boosting period. +    private boolean mIsFrameRateBoosting = false; +    // Used to check if there is a message in the message queue +    // for idleness handling. +    private boolean mHasIdledMessage = false; +    // time for touch boost period. +    private static final int FRAME_RATE_TOUCH_BOOST_TIME = 1500; +    // time for checking idle status periodically. +    private static final int FRAME_RATE_IDLENESS_CHECK_TIME_MILLIS = 500; +    // time for revaluating the idle status before lowering the frame rate. +    private static final int FRAME_RATE_IDLENESS_REEVALUATE_TIME = 500; +      /**       * A temporary object used so relayoutWindow can return the latest SyncSeqId       * system. The SyncSeqId system was designed to work without synchronous relayout @@ -1549,7 +1584,8 @@ public final class ViewRootImpl implements ViewParent,                          mHandler,                          DisplayManager.EVENT_FLAG_DISPLAY_ADDED                          | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED -                        | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED); +                        | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED, +                        mBasePackageName);      }      /** @@ -3917,6 +3953,12 @@ public final class ViewRootImpl implements ViewParent,                  mWmsRequestSyncGroupState = WMS_SYNC_NONE;              }          } + +        // For the variable refresh rate project. +        setPreferredFrameRate(mPreferredFrameRate); +        setPreferredFrameRateCategory(mPreferredFrameRateCategory); +        mLastPreferredFrameRateCategory = mPreferredFrameRateCategory; +        mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;      }      private void createSyncIfNeeded() { @@ -5969,6 +6011,8 @@ public final class ViewRootImpl implements ViewParent,      private static final int MSG_REPORT_KEEP_CLEAR_RECTS = 36;      private static final int MSG_PAUSED_FOR_SYNC_TIMEOUT = 37;      private static final int MSG_DECOR_VIEW_GESTURE_INTERCEPTION = 38; +    private static final int MSG_TOUCH_BOOST_TIMEOUT = 39; +    private static final int MSG_CHECK_INVALIDATION_IDLE = 40;      final class ViewRootHandler extends Handler {          @Override @@ -6264,6 +6308,32 @@ public final class ViewRootImpl implements ViewParent,                      mNumPausedForSync = 0;                      scheduleTraversals();                      break; +                case MSG_TOUCH_BOOST_TIMEOUT: +                    /** +                     * Lower the frame rate after the boosting period (FRAME_RATE_TOUCH_BOOST_TIME). +                     */ +                    mIsFrameRateBoosting = false; +                    setPreferredFrameRateCategory(Math.max(mPreferredFrameRateCategory, +                            mLastPreferredFrameRateCategory)); +                    break; +                case MSG_CHECK_INVALIDATION_IDLE: +                    if (!mHasInvalidation && !mIsFrameRateBoosting) { +                        mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE; +                        setPreferredFrameRateCategory(mPreferredFrameRateCategory); +                        mHasIdledMessage = false; +                    } else { +                        /** +                         * If there is no invalidation within a certain period, +                         * we consider the display is idled. +                         * We then set the frame rate catetogry to NO_PREFERENCE. +                         * Note that SurfaceFlinger also has a mechanism to lower the refresh rate +                         * if there is no updates of the buffer. +                         */ +                        mHasInvalidation = false; +                        mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE, +                                FRAME_RATE_IDLENESS_REEVALUATE_TIME); +                    } +                    break;              }          }      } @@ -7206,6 +7276,7 @@ public final class ViewRootImpl implements ViewParent,          private int processPointerEvent(QueuedInputEvent q) {              final MotionEvent event = (MotionEvent)q.mEvent; +            final int action = event.getAction();              boolean handled = mHandwritingInitiator.onTouchEvent(event);              if (handled) {                  // If handwriting is started, toolkit doesn't receive ACTION_UP. @@ -7226,6 +7297,22 @@ public final class ViewRootImpl implements ViewParent,                      scheduleConsumeBatchedInputImmediately();                  }              } + +            // For the variable refresh rate project +            if (handled && shouldTouchBoost(action, mWindowAttributes.type)) { +                // set the frame rate to the maximum value. +                mIsFrameRateBoosting = true; +                setPreferredFrameRateCategory(mPreferredFrameRateCategory); +            } +            /** +             * We want to lower the refresh rate when MotionEvent.ACTION_UP, +             * MotionEvent.ACTION_CANCEL is detected. +             * Not using ACTION_MOVE to avoid checking and sending messages too frequently. +             */ +            if (mIsFrameRateBoosting && (action == MotionEvent.ACTION_UP +                    || action == MotionEvent.ACTION_CANCEL)) { +                sendDelayedEmptyMessage(MSG_TOUCH_BOOST_TIMEOUT, FRAME_RATE_TOUCH_BOOST_TIME); +            }              return handled ? FINISH_HANDLED : FORWARD;          } @@ -11809,4 +11896,94 @@ public final class ViewRootImpl implements ViewParent,              @NonNull Consumer<Boolean> listener) {          t.clearTrustedPresentationCallback(getSurfaceControl());      } + +    private void setPreferredFrameRateCategory(int preferredFrameRateCategory) { +        if (!shouldSetFrameRateCategory()) { +            return; +        } + +        int frameRateCategory = mIsFrameRateBoosting +                ? FRAME_RATE_CATEGORY_HIGH : preferredFrameRateCategory; + +        try { +            mFrameRateTransaction.setFrameRateCategory(mSurfaceControl, +                    frameRateCategory, false).apply(); +        } catch (Exception e) { +            Log.e(mTag, "Unable to set frame rate category", e); +        } + +        if (mPreferredFrameRateCategory != FRAME_RATE_CATEGORY_NO_PREFERENCE && !mHasIdledMessage) { +            // Check where the display is idled periodically. +            // If so, set the frame rate category to NO_PREFERENCE +            mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE, +                    FRAME_RATE_IDLENESS_CHECK_TIME_MILLIS); +            mHasIdledMessage = true; +        } +    } + +    private void setPreferredFrameRate(float preferredFrameRate) { +        if (!shouldSetFrameRate()) { +            return; +        } + +        try { +            mFrameRateTransaction.setFrameRate(mSurfaceControl, +                    preferredFrameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT).apply(); +        } catch (Exception e) { +            Log.e(mTag, "Unable to set frame rate", e); +        } +    } + +    private void sendDelayedEmptyMessage(int message, int delayedTime) { +        mHandler.removeMessages(message); + +        mHandler.sendEmptyMessageDelayed(message, delayedTime); +    } + +    private boolean shouldSetFrameRateCategory() { +        // use toolkitSetFrameRate flag to gate the change +        return  mSurface.isValid() && toolkitSetFrameRate(); +    } + +    private boolean shouldSetFrameRate() { +        // use toolkitSetFrameRate flag to gate the change +        return mPreferredFrameRate > 0 && toolkitSetFrameRate(); +    } + +    private boolean shouldTouchBoost(int motionEventAction, int windowType) { +        boolean desiredAction = motionEventAction == MotionEvent.ACTION_DOWN +                || motionEventAction == MotionEvent.ACTION_MOVE +                || motionEventAction == MotionEvent.ACTION_UP; +        boolean desiredType = windowType == TYPE_BASE_APPLICATION || windowType == TYPE_APPLICATION +                || windowType == TYPE_APPLICATION_STARTING || windowType == TYPE_DRAWN_APPLICATION; +        // use toolkitSetFrameRate flag to gate the change +        return desiredAction && desiredType && toolkitSetFrameRate(); +    } + +    /** +     * Allow Views to vote for the preferred frame rate category +     * +     * @param frameRateCategory the preferred frame rate category of a View +     */ +    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED) +    public void votePreferredFrameRateCategory(int frameRateCategory) { +        mPreferredFrameRateCategory = Math.max(mPreferredFrameRateCategory, frameRateCategory); +        mHasInvalidation = true; +    } + +    /** +     * Get the value of mPreferredFrameRateCategory +     */ +    @VisibleForTesting +    public int getPreferredFrameRateCategory() { +        return mPreferredFrameRateCategory; +    } + +    /** +     * Get the value of mPreferredFrameRate +     */ +    @VisibleForTesting +    public float getPreferredFrameRate() { +        return mPreferredFrameRate; +    }  } diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java index 2c2ae06e9186..bb2c7c8b198b 100644 --- a/core/java/android/view/ViewStructure.java +++ b/core/java/android/view/ViewStructure.java @@ -397,6 +397,13 @@ public abstract class ViewStructure {      public void setImportantForAutofill(@AutofillImportance int mode) {}      /** +     * Sets whether the node is a credential. See {@link View#isCredential}. +     * +     * @hide +     */ +    public void setIsCredential(boolean isCredential) {} + +    /**       * Sets the MIME types accepted by this view. See {@link View#getReceiveContentMimeTypes()}.       *       * <p>Should only be set when the node is used for Autofill or Content Capture purposes - it diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 6c8f5424f85e..4f03ce98ccac 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -17,6 +17,7 @@  package android.view;  import static android.content.pm.ActivityInfo.COLOR_MODE_DEFAULT; +import static android.view.flags.Flags.FLAG_WM_DISPLAY_REFRESH_RATE_TEST;  import static android.view.View.STATUS_BAR_DISABLE_BACK;  import static android.view.View.STATUS_BAR_DISABLE_CLOCK;  import static android.view.View.STATUS_BAR_DISABLE_EXPAND; @@ -1685,7 +1686,7 @@ public interface WindowManager extends ViewManager {       * orientation (e.g. with {@link android.app.Activity#setRequestedOrientation(int)}). This       * listener gives application an opportunity to selectively react to device orientation changes.       * The newly added listener will be called with current proposed rotation. Note that the context -     * of this window manager instance must be a {@link android.annotation.UiContext}. +     * of this window manager instance must be a {@code UiContext}.       *       * @param executor The executor on which callback method will be invoked.       * @param listener Called when the proposed rotation for the context is being delivered. @@ -1693,7 +1694,7 @@ public interface WindowManager extends ViewManager {       *                 {@link Surface#ROTATION_90}, {@link Surface#ROTATION_180} and       *                 {@link Surface#ROTATION_270}.       * @throws UnsupportedOperationException if this method is called on an instance that is not -     *         associated with a {@link android.annotation.UiContext}. +     *         associated with a {@code UiContext}.       */      default void addProposedRotationListener(@NonNull @CallbackExecutor Executor executor,              @NonNull IntConsumer listener) { @@ -3115,7 +3116,7 @@ public interface WindowManager extends ViewManager {          /**           * Never animate position changes of the window.           * -         * @see android.R.attr#Window_windowNoMoveAnimation +         * @see android.R.styleable#Window_windowNoMoveAnimation           * {@hide}           */          @UnsupportedAppUsage @@ -3905,6 +3906,7 @@ public interface WindowManager extends ViewManager {           * This value is ignored if {@link #preferredDisplayModeId} is set.           * @hide           */ +        @FlaggedApi(FLAG_WM_DISPLAY_REFRESH_RATE_TEST)          @TestApi          public float preferredMinDisplayRefreshRate; @@ -3914,6 +3916,7 @@ public interface WindowManager extends ViewManager {           * This value is ignored if {@link #preferredDisplayModeId} is set.           * @hide           */ +        @FlaggedApi(FLAG_WM_DISPLAY_REFRESH_RATE_TEST)          @TestApi          public float preferredMaxDisplayRefreshRate; @@ -4531,7 +4534,7 @@ public interface WindowManager extends ViewManager {           * Set whether animations can be played for position changes on this window. If disabled,           * the window will move to its new position instantly without animating.           * -         * @attr ref android.R.attr#Window_windowNoMoveAnimation +         * @attr ref android.R.styleable#Window_windowNoMoveAnimation           */          public void setCanPlayMoveAnimation(boolean enable) {              if (enable) { @@ -4546,7 +4549,7 @@ public interface WindowManager extends ViewManager {           * This does not guarantee that an animation will be played in all such situations. For           * example, drag-resizing may move the window but not play an animation.           * -         * @attr ref android.R.attr#Window_windowNoMoveAnimation +         * @attr ref android.R.styleable#Window_windowNoMoveAnimation           */          public boolean canPlayMoveAnimation() {              return (privateFlags & PRIVATE_FLAG_NO_MOVE_ANIMATION) == 0; diff --git a/core/java/android/view/WindowMetrics.java b/core/java/android/view/WindowMetrics.java index 7ad43c76efaa..26298bc645ad 100644 --- a/core/java/android/view/WindowMetrics.java +++ b/core/java/android/view/WindowMetrics.java @@ -47,7 +47,6 @@ import java.util.function.Supplier;   * @see WindowInsets#getInsets(int)   * @see WindowManager#getCurrentWindowMetrics()   * @see WindowManager#getMaximumWindowMetrics() - * @see android.annotation.UiContext   */  public final class WindowMetrics {      @NonNull @@ -99,8 +98,7 @@ public final class WindowMetrics {      }      /** -     * Returns the bounds of the area associated with this window or -     * {@link android.annotation.UiContext}. +     * Returns the bounds of the area associated with this window or {@code UiContext}.       * <p>       * <b>Note that the size of the reported bounds can have different size than       * {@link Display#getSize(Point)}.</b> This method reports the window size including all system @@ -133,7 +131,7 @@ public final class WindowMetrics {      /**       * Returns the {@link WindowInsets} of the area associated with this window or -     * {@link android.annotation.UiContext}. +     * {@code UiContext}.       *       * @return the {@link WindowInsets} of the visual area.       */ @@ -146,9 +144,8 @@ public final class WindowMetrics {      }      /** -     * Returns the density of the area associated with this window or -     * {@link android.annotation.UiContext}, which uses the same units as -     * {@link android.util.DisplayMetrics#density}. +     * Returns the density of the area associated with this window or {@code UiContext}, +     * which uses the same units as {@link android.util.DisplayMetrics#density}.       *       * @see android.util.DisplayMetrics#DENSITY_DEFAULT       * @see android.util.DisplayMetrics#density diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java index 60f46e60ec9e..12ce0f47c460 100644 --- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java +++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java @@ -1731,6 +1731,9 @@ public final class AccessibilityInteractionClient      @Override      public void sendAttachOverlayResult(              @AccessibilityService.AttachOverlayResult int result, int interactionId) { +        if (!Flags.a11yOverlayCallbacks()) { +            return; +        }          synchronized (mInstanceLock) {              if (mAttachAccessibilityOverlayCallbacks.contains(interactionId)) {                  final Pair<Executor, IntConsumer> pair = diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index e6a8b7827b04..43bfe139c223 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -1562,9 +1562,8 @@ public class AccessibilityNodeInfo implements Parcelable {       * describes the action.       * </p>       * <p> -     * Use {@link androidx.core.view.ViewCompat#addAccessibilityAction(View, -     * AccessibilityNodeInfoCompat.AccessibilityActionCompat)} to register an action directly on the -     * view. +     * Use {@link androidx.core.view.ViewCompat#addAccessibilityAction(View, CharSequence, +     * AccessibilityViewCommand)} to register an action directly on the view.       * <p>       *   <strong>Note:</strong> Cannot be called from an       *   {@link android.accessibilityservice.AccessibilityService}. @@ -5167,8 +5166,7 @@ public class AccessibilityNodeInfo implements Parcelable {       * </p>       * <aside class="note">       * <b>Note:</b> Use {@link androidx.core.view.ViewCompat#addAccessibilityAction(View, -     * AccessibilityNodeInfoCompat.AccessibilityActionCompat)} to register an action directly on the -     * view. +     * CharSequence, AccessibilityViewCommand)} to register an action directly on the view.       * </p>       */      public static final class AccessibilityAction implements Parcelable { diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.java b/core/java/android/view/accessibility/AccessibilityWindowInfo.java index 09b2f9c1ee15..fa0052cf664a 100644 --- a/core/java/android/view/accessibility/AccessibilityWindowInfo.java +++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.java @@ -99,7 +99,6 @@ public final class AccessibilityWindowInfo implements Parcelable {      /** @hide */      public static final int UNDEFINED_CONNECTION_ID = -1;      /** @hide */ -    @TestApi      public static final int UNDEFINED_WINDOW_ID = -1;      /** @hide */      public static final int ANY_WINDOW_ID = -2; diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig index 85dadd4a061d..cc612ed93b2f 100644 --- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig +++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig @@ -13,3 +13,10 @@ flag {      description: "Allows the a11y shortcut disambig dialog to appear on the lockscreen"      bug: "303871725"  } + +flag { +        name: "a11y_overlay_callbacks" +    namespace: "accessibility" +    description: "Whether to allow the passing of result callbacks when attaching a11y overlays." +    bug: "304478691" +} diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java index a76780d18f71..32256b9b09c8 100644 --- a/core/java/android/view/animation/AnimationUtils.java +++ b/core/java/android/view/animation/AnimationUtils.java @@ -56,7 +56,7 @@ public class AnimationUtils {      private static final int SEQUENTIALLY = 1;       /** -     * For apps targeting {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above, +     * For apps targeting {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} and above,       * this change ID enables to use expectedPresentationTime instead of the frameTime       * for the frame start time .       * diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java index dc3d32317ded..bb815c0e8317 100644 --- a/core/java/android/view/contentcapture/ContentCaptureSession.java +++ b/core/java/android/view/contentcapture/ContentCaptureSession.java @@ -183,7 +183,8 @@ public abstract class ContentCaptureSession implements AutoCloseable {      public static final int FLUSH_REASON_VIEW_TREE_APPEARED = 10;      /** -     * After {@link UPSIDE_DOWN_CAKE}, {@link #notifyViewsDisappeared(AutofillId, long[])} wraps +     * After {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, +     * {@link #notifyViewsDisappeared(AutofillId, long[])} wraps       * the virtual children with a pair of view tree appearing and view tree appeared events.       */      @ChangeId diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java index 2241fd5dc37a..b44d6a496058 100644 --- a/core/java/android/view/contentcapture/MainContentCaptureSession.java +++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java @@ -317,16 +317,14 @@ public final class MainContentCaptureSession extends ContentCaptureSession {              }          } -        // Should not be possible for mComponentName to be null here but check anyway -        if (mManager.mOptions.contentProtectionOptions.enableReceiver -                && mManager.getContentProtectionEventBuffer() != null -                && mComponentName != null) { +        if (isContentProtectionEnabled()) {              mContentProtectionEventProcessor =                      new ContentProtectionEventProcessor(                              mManager.getContentProtectionEventBuffer(),                              mHandler,                              mSystemServerInterface, -                            mComponentName.getPackageName()); +                            mComponentName.getPackageName(), +                            mManager.mOptions.contentProtectionOptions);          } else {              mContentProtectionEventProcessor = null;          } @@ -956,4 +954,15 @@ public final class MainContentCaptureSession extends ContentCaptureSession {      private boolean isContentCaptureReceiverEnabled() {          return mManager.mOptions.enableReceiver;      } + +    @UiThread +    private boolean isContentProtectionEnabled() { +        // Should not be possible for mComponentName to be null here but check anyway +        // Should not be possible for groups to be empty if receiver is enabled but check anyway +        return mManager.mOptions.contentProtectionOptions.enableReceiver +                && mManager.getContentProtectionEventBuffer() != null +                && mComponentName != null +                && (!mManager.mOptions.contentProtectionOptions.requiredGroups.isEmpty() +                        || !mManager.mOptions.contentProtectionOptions.optionalGroups.isEmpty()); +    }  } diff --git a/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java b/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java index b44abf3eb04d..aaf90bd00535 100644 --- a/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java +++ b/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java @@ -19,9 +19,9 @@ package android.view.contentprotection;  import android.annotation.NonNull;  import android.annotation.Nullable;  import android.annotation.UiThread; +import android.content.ContentCaptureOptions;  import android.content.pm.ParceledListSlice;  import android.os.Handler; -import android.text.InputType;  import android.util.Log;  import android.view.contentcapture.ContentCaptureEvent;  import android.view.contentcapture.IContentCaptureManager; @@ -33,10 +33,10 @@ import com.android.internal.util.RingBuffer;  import java.time.Duration;  import java.time.Instant;  import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; +import java.util.Collection;  import java.util.List;  import java.util.Set; +import java.util.stream.Stream;  /**   * Main entry point for processing {@link ContentCaptureEvent} for the content protection flow. @@ -47,33 +47,13 @@ public class ContentProtectionEventProcessor {      private static final String TAG = "ContentProtectionEventProcessor"; -    private static final List<Integer> PASSWORD_FIELD_INPUT_TYPES = -            Collections.unmodifiableList( -                    Arrays.asList( -                            InputType.TYPE_NUMBER_VARIATION_PASSWORD, -                            InputType.TYPE_TEXT_VARIATION_PASSWORD, -                            InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD, -                            InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD)); - -    private static final List<String> PASSWORD_TEXTS = -            Collections.unmodifiableList( -                    Arrays.asList("password", "pass word", "code", "pin", "credential")); - -    private static final List<String> ADDITIONAL_SUSPICIOUS_TEXTS = -            Collections.unmodifiableList( -                    Arrays.asList("user", "mail", "phone", "number", "login", "log in", "sign in")); -      private static final Duration MIN_DURATION_BETWEEN_FLUSHING = Duration.ofSeconds(3); -    private static final String ANDROID_CLASS_NAME_PREFIX = "android."; -      private static final Set<Integer> EVENT_TYPES_TO_STORE = -            Collections.unmodifiableSet( -                    new HashSet<>( -                            Arrays.asList( -                                    ContentCaptureEvent.TYPE_VIEW_APPEARED, -                                    ContentCaptureEvent.TYPE_VIEW_DISAPPEARED, -                                    ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED))); +            Set.of( +                    ContentCaptureEvent.TYPE_VIEW_APPEARED, +                    ContentCaptureEvent.TYPE_VIEW_DISAPPEARED, +                    ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED);      private static final int RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS = 150; @@ -85,11 +65,7 @@ public class ContentProtectionEventProcessor {      @NonNull private final String mPackageName; -    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) -    public boolean mPasswordFieldDetected = false; - -    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) -    public boolean mSuspiciousTextDetected = false; +    @NonNull private final ContentCaptureOptions.ContentProtectionOptions mOptions;      @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)      @Nullable @@ -97,15 +73,32 @@ public class ContentProtectionEventProcessor {      private int mResetLoginRemainingEventsToProcess; +    private boolean mAnyGroupFound = false; + +    // Ordered by priority +    private final List<SearchGroup> mGroupsRequired; + +    // Ordered by priority +    private final List<SearchGroup> mGroupsOptional; + +    // Ordered by priority +    private final List<SearchGroup> mGroupsAll; +      public ContentProtectionEventProcessor(              @NonNull RingBuffer<ContentCaptureEvent> eventBuffer,              @NonNull Handler handler,              @NonNull IContentCaptureManager contentCaptureManager, -            @NonNull String packageName) { +            @NonNull String packageName, +            @NonNull ContentCaptureOptions.ContentProtectionOptions options) {          mEventBuffer = eventBuffer;          mHandler = handler;          mContentCaptureManager = contentCaptureManager;          mPackageName = packageName; +        mOptions = options; +        mGroupsRequired = options.requiredGroups.stream().map(SearchGroup::new).toList(); +        mGroupsOptional = options.optionalGroups.stream().map(SearchGroup::new).toList(); +        mGroupsAll = +                Stream.of(mGroupsRequired, mGroupsOptional).flatMap(Collection::stream).toList();      }      /** Main entry point for {@link ContentCaptureEvent} processing. */ @@ -130,9 +123,31 @@ public class ContentProtectionEventProcessor {      @UiThread      private void processViewAppearedEvent(@NonNull ContentCaptureEvent event) { -        mPasswordFieldDetected |= isPasswordField(event); -        mSuspiciousTextDetected |= isSuspiciousText(event); -        if (mPasswordFieldDetected && mSuspiciousTextDetected) { +        ViewNode viewNode = event.getViewNode(); +        String eventText = ContentProtectionUtils.getEventTextLower(event); +        String viewNodeText = ContentProtectionUtils.getViewNodeTextLower(viewNode); +        String hintText = ContentProtectionUtils.getHintTextLower(viewNode); + +        mGroupsAll.stream() +                .filter(group -> !group.mFound) +                .filter( +                        group -> +                                group.matches(eventText) +                                        || group.matches(viewNodeText) +                                        || group.matches(hintText)) +                .findFirst() +                .ifPresent( +                        group -> { +                            group.mFound = true; +                            mAnyGroupFound = true; +                        }); + +        boolean loginDetected = +                mGroupsRequired.stream().allMatch(group -> group.mFound) +                        && mGroupsOptional.stream().filter(group -> group.mFound).count() +                                >= mOptions.optionalGroupsThreshold; + +        if (loginDetected) {              loginDetected();          } else {              maybeResetLoginFlags(); @@ -150,14 +165,13 @@ public class ContentProtectionEventProcessor {      @UiThread      private void resetLoginFlags() { -        mPasswordFieldDetected = false; -        mSuspiciousTextDetected = false; -        mResetLoginRemainingEventsToProcess = 0; +        mGroupsAll.forEach(group -> group.mFound = false); +        mAnyGroupFound = false;      }      @UiThread      private void maybeResetLoginFlags() { -        if (mPasswordFieldDetected || mSuspiciousTextDetected) { +        if (mAnyGroupFound) {              if (mResetLoginRemainingEventsToProcess <= 0) {                  mResetLoginRemainingEventsToProcess = RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS;              } else { @@ -194,61 +208,21 @@ public class ContentProtectionEventProcessor {          }      } -    private boolean isPasswordField(@NonNull ContentCaptureEvent event) { -        return isPasswordField(event.getViewNode()); -    } - -    private boolean isPasswordField(@Nullable ViewNode viewNode) { -        if (viewNode == null) { -            return false; -        } -        return isAndroidPasswordField(viewNode) || isWebViewPasswordField(viewNode); -    } - -    private boolean isAndroidPasswordField(@NonNull ViewNode viewNode) { -        if (!isAndroidViewNode(viewNode)) { -            return false; -        } -        int inputType = viewNode.getInputType(); -        return PASSWORD_FIELD_INPUT_TYPES.stream() -                .anyMatch(passwordInputType -> (inputType & passwordInputType) != 0); -    } +    private static final class SearchGroup { -    private boolean isWebViewPasswordField(@NonNull ViewNode viewNode) { -        if (viewNode.getClassName() != null) { -            return false; -        } -        return isPasswordText(ContentProtectionUtils.getViewNodeText(viewNode)); -    } +        @NonNull private final List<String> mSearchStrings; -    private boolean isAndroidViewNode(@NonNull ViewNode viewNode) { -        String className = viewNode.getClassName(); -        return className != null && className.startsWith(ANDROID_CLASS_NAME_PREFIX); -    } - -    private boolean isSuspiciousText(@NonNull ContentCaptureEvent event) { -        return isSuspiciousText(ContentProtectionUtils.getEventText(event)) -                || isSuspiciousText(ContentProtectionUtils.getViewNodeText(event)); -    } +        public boolean mFound = false; -    private boolean isSuspiciousText(@Nullable String text) { -        if (text == null) { -            return false; +        SearchGroup(@NonNull List<String> searchStrings) { +            mSearchStrings = searchStrings;          } -        if (isPasswordText(text)) { -            return true; -        } -        String lowerCaseText = text.toLowerCase(); -        return ADDITIONAL_SUSPICIOUS_TEXTS.stream() -                .anyMatch(suspiciousText -> lowerCaseText.contains(suspiciousText)); -    } -    private boolean isPasswordText(@Nullable String text) { -        if (text == null) { -            return false; +        public boolean matches(@Nullable String text) { +            if (text == null) { +                return false; +            } +            return mSearchStrings.stream().anyMatch(text::contains);          } -        String lowerCaseText = text.toLowerCase(); -        return PASSWORD_TEXTS.stream() -                .anyMatch(passwordText -> lowerCaseText.contains(passwordText));      }  } diff --git a/core/java/android/view/contentprotection/ContentProtectionUtils.java b/core/java/android/view/contentprotection/ContentProtectionUtils.java index 9abf6f10d05d..1ecac7f188ab 100644 --- a/core/java/android/view/contentprotection/ContentProtectionUtils.java +++ b/core/java/android/view/contentprotection/ContentProtectionUtils.java @@ -28,33 +28,39 @@ import android.view.contentcapture.ViewNode;   */  public final class ContentProtectionUtils { -    /** Returns the text extracted directly from the {@link ContentCaptureEvent}, if set. */ +    /** Returns the lowercase text extracted from the {@link ContentCaptureEvent}, if set. */      @Nullable -    public static String getEventText(@NonNull ContentCaptureEvent event) { +    public static String getEventTextLower(@NonNull ContentCaptureEvent event) {          CharSequence text = event.getText();          if (text == null) {              return null;          } -        return text.toString(); +        return text.toString().toLowerCase();      } -    /** Returns the text extracted from the event's {@link ViewNode}, if set. */ +    /** Returns the lowercase text extracted from the {@link ViewNode}, if set. */      @Nullable -    public static String getViewNodeText(@NonNull ContentCaptureEvent event) { -        ViewNode viewNode = event.getViewNode(); +    public static String getViewNodeTextLower(@Nullable ViewNode viewNode) {          if (viewNode == null) {              return null;          } -        return getViewNodeText(viewNode); +        CharSequence text = viewNode.getText(); +        if (text == null) { +            return null; +        } +        return text.toString().toLowerCase();      } -    /** Returns the text extracted directly from the {@link ViewNode}, if set. */ +    /** Returns the lowercase hint text extracted from the {@link ViewNode}, if set. */      @Nullable -    public static String getViewNodeText(@NonNull ViewNode viewNode) { -        CharSequence text = viewNode.getText(); +    public static String getHintTextLower(@Nullable ViewNode viewNode) { +        if (viewNode == null) { +            return null; +        } +        String text = viewNode.getHint();          if (text == null) {              return null;          } -        return text.toString(); +        return text.toLowerCase();      }  } diff --git a/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig b/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig index 7e06f87d931a..5c86feb3c22c 100644 --- a/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig +++ b/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig @@ -6,3 +6,10 @@ flag {      description: "If true, content protection blocklist is mutable and can be updated."      bug: "301658008"  } + +flag { +    name: "parse_groups_config_enabled" +    namespace: "content_protection" +    description: "If true, content protection groups config will be parsed." +    bug: "302187922" +} diff --git a/core/java/android/view/displayhash/DisplayHashResultCallback.java b/core/java/android/view/displayhash/DisplayHashResultCallback.java index 6e3d9a8786af..927874ffc3a2 100644 --- a/core/java/android/view/displayhash/DisplayHashResultCallback.java +++ b/core/java/android/view/displayhash/DisplayHashResultCallback.java @@ -106,7 +106,7 @@ public interface DisplayHashResultCallback {       * {@link android.view.View#generateDisplayHash(String, Rect, Executor,       * DisplayHashResultCallback)} results in an error and cannot generate a display hash.       * -     * @param errorCode One of the values in {@link DisplayHashErrorCode} +     * @param errorCode the error code       */      void onDisplayHashError(@DisplayHashErrorCode int errorCode);  } diff --git a/core/java/android/view/flags/refresh_rate_flags.aconfig b/core/java/android/view/flags/refresh_rate_flags.aconfig index 56b5fac1c64a..fd9689013af3 100644 --- a/core/java/android/view/flags/refresh_rate_flags.aconfig +++ b/core/java/android/view/flags/refresh_rate_flags.aconfig @@ -26,4 +26,11 @@ flag {    namespace: "core_graphics"    description: "Enable the `setFrameRate` callback"    bug: "299946220" +} + +flag { +    name: "wm_display_refresh_rate_test" +    namespace: "core_graphics" +    description: "Adds WindowManager display refresh rate fields to test API" +    bug: "304475199"  }
\ No newline at end of file diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java index a92420a2f373..c5114b9550db 100644 --- a/core/java/android/view/inputmethod/EditorInfo.java +++ b/core/java/android/view/inputmethod/EditorInfo.java @@ -47,7 +47,6 @@ import android.view.MotionEvent;  import android.view.MotionEvent.ToolType;  import android.view.View;  import android.view.autofill.AutofillId; -import android.widget.Editor;  import com.android.internal.annotations.VisibleForTesting;  import com.android.internal.inputmethod.InputMethodDebug; @@ -722,9 +721,9 @@ public class EditorInfo implements InputType, Parcelable {      private boolean mIsStylusHandwritingEnabled;      /** -     * Set {@code true} if the {@link Editor} has +     * Set {@code true} if the {@code Editor} has       * {@link InputMethodManager#startStylusHandwriting stylus handwriting} enabled. -     * {@code false} by default, {@link Editor} must set it {@code true} to indicate that +     * {@code false} by default, {@code Editor} must set it {@code true} to indicate that       * it supports stylus handwriting.       *       * @param enabled {@code true} if stylus handwriting is enabled. @@ -736,7 +735,7 @@ public class EditorInfo implements InputType, Parcelable {      }      /** -     * Returns {@code true} when an {@link Editor} has stylus handwriting enabled. +     * Returns {@code true} when an {@code Editor} has stylus handwriting enabled.       * {@code false} by default.       * @see #setStylusHandwritingEnabled(boolean)       * @see InputMethodManager#isStylusHandwritingAvailable() diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 5bb1e9318175..8159af3ddd4a 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -100,7 +100,6 @@ import android.view.WindowManager;  import android.view.WindowManager.LayoutParams.SoftInputModeFlags;  import android.view.autofill.AutofillId;  import android.view.autofill.AutofillManager; -import android.widget.Editor;  import android.window.ImeOnBackInvokedDispatcher;  import android.window.WindowOnBackInvokedDispatcher; @@ -2374,16 +2373,16 @@ public final class InputMethodManager {       * Prepares delegation of starting stylus handwriting session to a different editor in same       * or different window than the view on which initial handwriting stroke was detected.       * -     * Delegation can be used to start stylus handwriting session before the {@link Editor} view or +     * Delegation can be used to start stylus handwriting session before the {@code Editor} view or       * its {@link InputConnection} is started. Calling this method starts buffering of stylus       * motion events until {@link #acceptStylusHandwritingDelegation(View)} is called, at which       * point the handwriting session can be started and the buffered stylus motion events will be       * delivered to the IME.       * e.g. Delegation can be used when initial handwriting stroke is -     * on a pseudo {@link Editor} like widget (with no {@link InputConnection}) but actual -     * {@link Editor} is on a different window. +     * on a pseudo {@code Editor} like widget (with no {@link InputConnection}) but actual +     * {@code Editor} is on a different window.       * -     * <p> Note: If an actual {@link Editor} capable of {@link InputConnection} is being scribbled +     * <p> Note: If an actual {@code Editor} capable of {@link InputConnection} is being scribbled       * upon using stylus, use {@link #startStylusHandwriting(View)} instead.</p>       *       * @param delegatorView the view that receives initial stylus stroke and delegates it to the @@ -2402,21 +2401,21 @@ public final class InputMethodManager {       * different window in a different package than the view on which initial handwriting stroke       * was detected.       * -     * Delegation can be used to start stylus handwriting session before the {@link Editor} view or +     * Delegation can be used to start stylus handwriting session before the {@code Editor} view or       * its {@link InputConnection} is started. Calling this method starts buffering of stylus       * motion events until {@link #acceptStylusHandwritingDelegation(View, String)} is called, at       * which point the handwriting session can be started and the buffered stylus motion events will       * be delivered to the IME.       * e.g. Delegation can be used when initial handwriting stroke is -     * on a pseudo {@link Editor} like widget (with no {@link InputConnection}) but actual -     * {@link Editor} is on a different window in the given package. +     * on a pseudo {@code Editor} like widget (with no {@link InputConnection}) but actual +     * {@code Editor} is on a different window in the given package.       *       * <p>Note: If delegator and delegate are in same package use       * {@link #prepareStylusHandwritingDelegation(View)} instead.</p>       *       * @param delegatorView  the view that receives initial stylus stroke and delegates it to the       * actual editor. Its window must {@link View#hasWindowFocus have focus}. -     * @param delegatePackageName package name that contains actual {@link Editor} which should +     * @param delegatePackageName package name that contains actual {@code Editor} which should       *  start stylus handwriting session by calling {@link #acceptStylusHandwritingDelegation}.       * @see #prepareStylusHandwritingDelegation(View)       * @see #acceptStylusHandwritingDelegation(View, String) diff --git a/core/java/android/view/inspector/PropertyReader.java b/core/java/android/view/inspector/PropertyReader.java index 5be0e3f3ccf2..78de7e16edd7 100644 --- a/core/java/android/view/inspector/PropertyReader.java +++ b/core/java/android/view/inspector/PropertyReader.java @@ -124,7 +124,7 @@ public interface PropertyReader {      void readObject(int id, @Nullable Object value);      /** -     * Read a color packed into a {@link ColorInt} as a property. +     * Read a color packed into an int as a property.       *       * @param id Identifier of the property from a {@link PropertyMapper}       * @param value Value of the property diff --git a/core/java/android/window/BackEvent.java b/core/java/android/window/BackEvent.java index f53737abf707..55623608b025 100644 --- a/core/java/android/window/BackEvent.java +++ b/core/java/android/window/BackEvent.java @@ -49,7 +49,7 @@ public final class BackEvent {      private final int mSwipeEdge;      /** -     * Creates a new {@link BackMotionEvent} instance. +     * Creates a new {@link BackEvent} instance.       *       * @param touchX Absolute X location of the touch point of this event.       * @param touchY Absolute Y location of the touch point of this event. diff --git a/core/java/android/window/SystemPerformanceHinter.java b/core/java/android/window/SystemPerformanceHinter.java index 07ac2922304e..2736b68845a2 100644 --- a/core/java/android/window/SystemPerformanceHinter.java +++ b/core/java/android/window/SystemPerformanceHinter.java @@ -211,7 +211,11 @@ public class SystemPerformanceHinter {                      session.displayId);              mTransaction.setFrameRateSelectionStrategy(displaySurfaceControl,                      FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN); -            mTransaction.setFrameRateCategory(displaySurfaceControl, FRAME_RATE_CATEGORY_HIGH); +            // smoothSwitchOnly is false to request a higher framerate, even if it means switching +            // the display mode will cause would jank on non-VRR devices because keeping a lower +            // refresh rate would mean a poorer user experience. +            mTransaction.setFrameRateCategory( +                    displaySurfaceControl, FRAME_RATE_CATEGORY_HIGH, /* smoothSwitchOnly= */ false);              transactionChanged = true;              Trace.beginAsyncSection("PerfHint-framerate-" + session.displayId + "-"                      + session.reason, session.traceCookie); @@ -251,7 +255,11 @@ public class SystemPerformanceHinter {                      session.displayId);              mTransaction.setFrameRateSelectionStrategy(displaySurfaceControl,                      FRAME_RATE_SELECTION_STRATEGY_SELF); -            mTransaction.setFrameRateCategory(displaySurfaceControl, FRAME_RATE_CATEGORY_DEFAULT); +            // smoothSwitchOnly is false to request a higher framerate, even if it means switching +            // the display mode will cause would jank on non-VRR devices because keeping a lower +            // refresh rate would mean a poorer user experience. +            mTransaction.setFrameRateCategory(displaySurfaceControl, FRAME_RATE_CATEGORY_DEFAULT, +                    /* smoothSwitchOnly= */ false);              transactionChanged = true;              Trace.endAsyncSection("PerfHint-framerate-" + session.displayId + "-" + session.reason,                      session.traceCookie); diff --git a/core/java/android/window/TaskFragmentOperation.java b/core/java/android/window/TaskFragmentOperation.java index 43fa0be6c1b7..4e0f9a51c0a0 100644 --- a/core/java/android/window/TaskFragmentOperation.java +++ b/core/java/android/window/TaskFragmentOperation.java @@ -88,6 +88,26 @@ public final class TaskFragmentOperation implements Parcelable {       */      public static final int OP_TYPE_SET_ISOLATED_NAVIGATION = 11; +    /** +     * Reorders the TaskFragment to be the bottom-most in the Task. Note that this op will bring the +     * TaskFragment to the bottom of the Task below all the other Activities and TaskFragments. +     * +     * This is only allowed for system organizers. See +     * {@link com.android.server.wm.TaskFragmentOrganizerController#registerOrganizer( +     * ITaskFragmentOrganizer, boolean)} +     */ +    public static final int OP_TYPE_REORDER_TO_BOTTOM_OF_TASK = 12; + +    /** +     * Reorders the TaskFragment to be the top-most in the Task. Note that this op will bring the +     * TaskFragment to the top of the Task above all the other Activities and TaskFragments. +     * +     * This is only allowed for system organizers. See +     * {@link com.android.server.wm.TaskFragmentOrganizerController#registerOrganizer( +     * ITaskFragmentOrganizer, boolean)} +     */ +    public static final int OP_TYPE_REORDER_TO_TOP_OF_TASK = 13; +      @IntDef(prefix = { "OP_TYPE_" }, value = {              OP_TYPE_UNKNOWN,              OP_TYPE_CREATE_TASK_FRAGMENT, @@ -101,7 +121,9 @@ public final class TaskFragmentOperation implements Parcelable {              OP_TYPE_SET_ANIMATION_PARAMS,              OP_TYPE_SET_RELATIVE_BOUNDS,              OP_TYPE_REORDER_TO_FRONT, -            OP_TYPE_SET_ISOLATED_NAVIGATION +            OP_TYPE_SET_ISOLATED_NAVIGATION, +            OP_TYPE_REORDER_TO_BOTTOM_OF_TASK, +            OP_TYPE_REORDER_TO_TOP_OF_TASK,      })      @Retention(RetentionPolicy.SOURCE)      public @interface OperationType {} diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index d2a16a3a9212..1a2d202be934 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -29,6 +29,7 @@ import static android.view.Display.INVALID_DISPLAY;  import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;  import static android.view.WindowManager.TRANSIT_CHANGE;  import static android.view.WindowManager.TRANSIT_CLOSE; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING;  import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;  import static android.view.WindowManager.TRANSIT_NONE;  import static android.view.WindowManager.TRANSIT_OPEN; @@ -361,6 +362,15 @@ public final class TransitionInfo implements Parcelable {      }      /** +     * Whether this transition contains any changes to the window hierarchy, +     * including keyguard visibility. +     */ +    public boolean hasChangesOrSideEffects() { +        return !mChanges.isEmpty() || isKeyguardGoingAway() +                || (mFlags & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0; +    } + +    /**       * Whether this transition includes keyguard going away.       */      public boolean isKeyguardGoingAway() { @@ -392,6 +402,18 @@ public final class TransitionInfo implements Parcelable {      @Override      public String toString() { +        return toString(""); +    } + +    /** +     * Returns a string representation of this transition info. +     * @hide +     */ +    public String toString(@NonNull String prefix) { +        final boolean shouldPrettyPrint = !prefix.isEmpty() && !mChanges.isEmpty(); +        final String innerPrefix = shouldPrettyPrint ? prefix + "    " : ""; +        final String changesLineStart = shouldPrettyPrint ? "\n" + prefix : ""; +        final String perChangeLineStart = shouldPrettyPrint ? "\n" + innerPrefix : "";          StringBuilder sb = new StringBuilder();          sb.append("{id=").append(mDebugId).append(" t=").append(transitTypeToString(mType))                  .append(" f=0x").append(Integer.toHexString(mFlags)).append(" trk=").append(mTrack) @@ -403,12 +425,15 @@ public final class TransitionInfo implements Parcelable {              sb.append(mRoots.get(i).mDisplayId).append("@").append(mRoots.get(i).mOffset);          }          sb.append("] c=["); +        sb.append(perChangeLineStart);          for (int i = 0; i < mChanges.size(); ++i) {              if (i > 0) {                  sb.append(','); +                sb.append(perChangeLineStart);              }              sb.append(mChanges.get(i));          } +        sb.append(changesLineStart);          sb.append("]}");          return sb.toString();      } diff --git a/core/java/android/window/TransitionRequestInfo.java b/core/java/android/window/TransitionRequestInfo.java index 932608a3b57b..bd54e14bc996 100644 --- a/core/java/android/window/TransitionRequestInfo.java +++ b/core/java/android/window/TransitionRequestInfo.java @@ -62,13 +62,16 @@ public final class TransitionRequestInfo implements Parcelable {      /** The transition flags known at the time of the request. These may not be complete. */      private final int mFlags; +    /** This is only a BEST-EFFORT id used for log correlation. DO NOT USE for any real work! */ +    private final int mDebugId; +      /** constructor override */      public TransitionRequestInfo(              @WindowManager.TransitionType int type,              @Nullable ActivityManager.RunningTaskInfo triggerTask,              @Nullable RemoteTransition remoteTransition) {          this(type, triggerTask, null /* pipTask */, -                remoteTransition, null /* displayChange */, 0 /* flags */); +                remoteTransition, null /* displayChange */, 0 /* flags */, -1 /* debugId */);      }      /** constructor override */ @@ -78,16 +81,29 @@ public final class TransitionRequestInfo implements Parcelable {              @Nullable RemoteTransition remoteTransition,              int flags) {          this(type, triggerTask, null /* pipTask */, -                remoteTransition, null /* displayChange */, flags); +                remoteTransition, null /* displayChange */, flags, -1 /* debugId */);      } +        /** constructor override */      public TransitionRequestInfo(              @WindowManager.TransitionType int type,              @Nullable ActivityManager.RunningTaskInfo triggerTask,              @Nullable RemoteTransition remoteTransition,              @Nullable TransitionRequestInfo.DisplayChange displayChange,              int flags) { -        this(type, triggerTask, null /* pipTask */, remoteTransition, displayChange, flags); +        this(type, triggerTask, null /* pipTask */, remoteTransition, displayChange, flags, +                -1 /* debugId */); +    } + +    /** constructor override */ +    public TransitionRequestInfo( +            @WindowManager.TransitionType int type, +            @Nullable ActivityManager.RunningTaskInfo triggerTask, +            @Nullable ActivityManager.RunningTaskInfo pipTask, +            @Nullable RemoteTransition remoteTransition, +            @Nullable TransitionRequestInfo.DisplayChange displayChange, +            int flags) { +        this(type, triggerTask, pipTask, remoteTransition, displayChange, flags, -1 /* debugId */);      }      /** @hide */ @@ -270,7 +286,7 @@ public final class TransitionRequestInfo implements Parcelable {          };          @DataClass.Generated( -                time = 1695667226050L, +                time = 1697564781403L,                  codegenVersion = "1.0.23",                  sourceFile = "frameworks/base/core/java/android/window/TransitionRequestInfo.java",                  inputSignatures = "private final  int mDisplayId\nprivate @android.annotation.Nullable android.graphics.Rect mStartAbsBounds\nprivate @android.annotation.Nullable android.graphics.Rect mEndAbsBounds\nprivate  int mStartRotation\nprivate  int mEndRotation\nprivate  boolean mPhysicalDisplayChanged\nclass DisplayChange extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genBuilder=false, genConstructor=false)") @@ -318,6 +334,8 @@ public final class TransitionRequestInfo implements Parcelable {       *   (if size is changing).       * @param flags       *   The transition flags known at the time of the request. These may not be complete. +     * @param debugId +     *   This is only a BEST-EFFORT id used for log correlation. DO NOT USE for any real work!       */      @DataClass.Generated.Member      public TransitionRequestInfo( @@ -326,7 +344,8 @@ public final class TransitionRequestInfo implements Parcelable {              @Nullable ActivityManager.RunningTaskInfo pipTask,              @Nullable RemoteTransition remoteTransition,              @Nullable TransitionRequestInfo.DisplayChange displayChange, -            int flags) { +            int flags, +            int debugId) {          this.mType = type;          com.android.internal.util.AnnotationValidations.validate(                  WindowManager.TransitionType.class, null, mType); @@ -335,6 +354,7 @@ public final class TransitionRequestInfo implements Parcelable {          this.mRemoteTransition = remoteTransition;          this.mDisplayChange = displayChange;          this.mFlags = flags; +        this.mDebugId = debugId;          // onConstructed(); // You can define this method to get a callback      } @@ -392,6 +412,14 @@ public final class TransitionRequestInfo implements Parcelable {      }      /** +     * This is only a BEST-EFFORT id used for log correlation. DO NOT USE for any real work! +     */ +    @DataClass.Generated.Member +    public int getDebugId() { +        return mDebugId; +    } + +    /**       * If non-null, the task containing the activity whose lifecycle change (start or       * finish) has caused this transition to occur.       */ @@ -443,7 +471,8 @@ public final class TransitionRequestInfo implements Parcelable {                  "pipTask = " + mPipTask + ", " +                  "remoteTransition = " + mRemoteTransition + ", " +                  "displayChange = " + mDisplayChange + ", " + -                "flags = " + mFlags + +                "flags = " + mFlags + ", " + +                "debugId = " + mDebugId +          " }";      } @@ -465,6 +494,7 @@ public final class TransitionRequestInfo implements Parcelable {          if (mRemoteTransition != null) dest.writeTypedObject(mRemoteTransition, flags);          if (mDisplayChange != null) dest.writeTypedObject(mDisplayChange, flags);          dest.writeInt(mFlags); +        dest.writeInt(mDebugId);      }      @Override @@ -485,6 +515,7 @@ public final class TransitionRequestInfo implements Parcelable {          RemoteTransition remoteTransition = (flg & 0x8) == 0 ? null : (RemoteTransition) in.readTypedObject(RemoteTransition.CREATOR);          TransitionRequestInfo.DisplayChange displayChange = (flg & 0x10) == 0 ? null : (TransitionRequestInfo.DisplayChange) in.readTypedObject(TransitionRequestInfo.DisplayChange.CREATOR);          int flags = in.readInt(); +        int debugId = in.readInt();          this.mType = type;          com.android.internal.util.AnnotationValidations.validate( @@ -494,6 +525,7 @@ public final class TransitionRequestInfo implements Parcelable {          this.mRemoteTransition = remoteTransition;          this.mDisplayChange = displayChange;          this.mFlags = flags; +        this.mDebugId = debugId;          // onConstructed(); // You can define this method to get a callback      } @@ -513,10 +545,10 @@ public final class TransitionRequestInfo implements Parcelable {      };      @DataClass.Generated( -            time = 1695667226088L, +            time = 1697564781438L,              codegenVersion = "1.0.23",              sourceFile = "frameworks/base/core/java/android/window/TransitionRequestInfo.java", -            inputSignatures = "private final @android.view.WindowManager.TransitionType int mType\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mTriggerTask\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mPipTask\nprivate @android.annotation.Nullable android.window.RemoteTransition mRemoteTransition\nprivate @android.annotation.Nullable android.window.TransitionRequestInfo.DisplayChange mDisplayChange\nprivate final  int mFlags\n  java.lang.String typeToString()\nclass TransitionRequestInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genAidl=true)") +            inputSignatures = "private final @android.view.WindowManager.TransitionType int mType\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mTriggerTask\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mPipTask\nprivate @android.annotation.Nullable android.window.RemoteTransition mRemoteTransition\nprivate @android.annotation.Nullable android.window.TransitionRequestInfo.DisplayChange mDisplayChange\nprivate final  int mFlags\nprivate final  int mDebugId\n  java.lang.String typeToString()\nclass TransitionRequestInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genAidl=true)")      @Deprecated      private void __metadata() {} diff --git a/core/java/android/window/WindowProviderService.java b/core/java/android/window/WindowProviderService.java index 611da3cec5c6..0cbfcc501918 100644 --- a/core/java/android/window/WindowProviderService.java +++ b/core/java/android/window/WindowProviderService.java @@ -155,7 +155,7 @@ public abstract class WindowProviderService extends Service implements WindowPro      }      /** -     * Override {@link Service}'s empty implementation and listen to {@link ActivityThread} for +     * Override {@link Service}'s empty implementation and listen to {@code ActivityThread} for       * low memory and trim memory events.       */      @Override diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index 7c931cd9fa15..6c025a47ff5a 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -13,3 +13,18 @@ flag {    description: "On close to square display, when necessary, configuration includes status bar"    bug: "291870756"  } + +flag { +  name: "dimmer_refactor" +  namespace: "windowing_frontend" +  description: "Refactor dim to fix flickers" +  bug: "281632483,295291019" +  is_fixed_read_only: true +} + +flag { +  name: "transit_ready_tracking" +  namespace: "windowing_frontend" +  description: "Enable accurate transition readiness tracking" +  bug: "294925498" +} diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java index 58376a77c705..0801dd8c0bd8 100644 --- a/core/java/com/android/internal/content/FileSystemProvider.java +++ b/core/java/com/android/internal/content/FileSystemProvider.java @@ -62,16 +62,14 @@ import java.nio.file.FileVisitor;  import java.nio.file.Files;  import java.nio.file.Path;  import java.nio.file.attribute.BasicFileAttributes; + import java.util.ArrayDeque;  import java.util.ArrayList;  import java.util.Arrays; -import java.util.Deque; -import java.util.LinkedList;  import java.util.List;  import java.util.Locale; +import java.util.Queue;  import java.util.Set;  import java.util.concurrent.CopyOnWriteArrayList; -import java.util.function.Predicate; -import java.util.regex.Pattern;  /**   * A helper class for {@link android.provider.DocumentsProvider} to perform file operations on local @@ -89,6 +87,8 @@ public abstract class FileSystemProvider extends DocumentsProvider {              DocumentsContract.QUERY_ARG_LAST_MODIFIED_AFTER,              DocumentsContract.QUERY_ARG_MIME_TYPES); +    private static final int MAX_RESULTS_NUMBER = 23; +      private static String joinNewline(String... args) {          return TextUtils.join("\n", args);      } @@ -375,62 +375,53 @@ public abstract class FileSystemProvider extends DocumentsProvider {      }      /** -     * This method is similar to -     * {@link DocumentsProvider#queryChildDocuments(String, String[], String)}. This method returns -     * all children documents including hidden directories/files. -     * -     * <p> -     * In a scoped storage world, access to "Android/data" style directories are hidden for privacy -     * reasons. This method may show privacy sensitive data, so its usage should only be in -     * restricted modes. -     * -     * @param parentDocumentId the directory to return children for. -     * @param projection list of {@link Document} columns to put into the -     *            cursor. If {@code null} all supported columns should be -     *            included. -     * @param sortOrder how to order the rows, formatted as an SQL -     *            {@code ORDER BY} clause (excluding the ORDER BY itself). -     *            Passing {@code null} will use the default sort order, which -     *            may be unordered. This ordering is a hint that can be used to -     *            prioritize how data is fetched from the network, but UI may -     *            always enforce a specific ordering -     * @throws FileNotFoundException when parent document doesn't exist or query fails +     * WARNING: this method should really be {@code final}, but for the backward compatibility it's +     * not; new classes that extend {@link FileSystemProvider} should override +     * {@link #queryChildDocuments(String, String[], String, boolean)}, not this method.       */ -    protected Cursor queryChildDocumentsShowAll( -            String parentDocumentId, String[] projection, String sortOrder) +    @Override +    public Cursor queryChildDocuments(String documentId, String[] projection, String sortOrder)              throws FileNotFoundException { -        return queryChildDocuments(parentDocumentId, projection, sortOrder, File -> true); +        return queryChildDocuments(documentId, projection, sortOrder, /* includeHidden */ false);      } +    /** +     * This method is similar to {@link #queryChildDocuments(String, String[], String)}, however, it +     * could return <b>all</b> content of the directory, <b>including restricted (hidden) +     * directories and files</b>. +     * <p> +     * In the scoped storage world, some directories and files (e.g. {@code Android/data/} and +     * {@code Android/obb/} on the external storage) are hidden for privacy reasons. +     * Hence, this method may reveal privacy-sensitive data, thus should be used with extra care. +     */      @Override -    public Cursor queryChildDocuments( -            String parentDocumentId, String[] projection, String sortOrder) -            throws FileNotFoundException { -        // Access to some directories is hidden for privacy reasons. -        return queryChildDocuments(parentDocumentId, projection, sortOrder, this::shouldShow); +    public final Cursor queryChildDocumentsForManage(String documentId, String[] projection, +            String sortOrder) throws FileNotFoundException { +        return queryChildDocuments(documentId, projection, sortOrder, /* includeHidden */ true);      } -    private Cursor queryChildDocuments( -            String parentDocumentId, String[] projection, String sortOrder, -            @NonNull Predicate<File> filter) throws FileNotFoundException { -        final File parent = getFileForDocId(parentDocumentId); +    protected Cursor queryChildDocuments(String documentId, String[] projection, String sortOrder, +            boolean includeHidden) throws FileNotFoundException { +        final File parent = getFileForDocId(documentId);          final MatrixCursor result = new DirectoryCursor( -                resolveProjection(projection), parentDocumentId, parent); +                resolveProjection(projection), documentId, parent); + +        if (!parent.isDirectory()) { +            Log.w(TAG, '"' + documentId + "\" is not a directory"); +            return result; +        } -        if (!filter.test(parent)) { -            Log.w(TAG, "No permission to access parentDocumentId: " + parentDocumentId); +        if (!includeHidden && shouldHideDocument(documentId)) { +            Log.w(TAG, "Queried directory \"" + documentId + "\" is hidden");              return result;          } -        if (parent.isDirectory()) { -            for (File file : FileUtils.listFilesOrEmpty(parent)) { -                if (filter.test(file)) { -                    includeFile(result, null, file); -                } -            } -        } else { -            Log.w(TAG, "parentDocumentId '" + parentDocumentId + "' is not Directory"); +        for (File file : FileUtils.listFilesOrEmpty(parent)) { +            if (!includeHidden && shouldHideDocument(file)) continue; + +            includeFile(result, null, file);          } +          return result;      } @@ -452,23 +443,29 @@ public abstract class FileSystemProvider extends DocumentsProvider {       *       * @see ContentResolver#EXTRA_HONORED_ARGS       */ -    protected final Cursor querySearchDocuments( -            File folder, String[] projection, Set<String> exclusion, Bundle queryArgs) -            throws FileNotFoundException { +    protected final Cursor querySearchDocuments(File folder, String[] projection, +            Set<String> exclusion, Bundle queryArgs) throws FileNotFoundException {          final MatrixCursor result = new MatrixCursor(resolveProjection(projection)); -        final List<File> pending = new ArrayList<>(); -        pending.add(folder); -        while (!pending.isEmpty() && result.getCount() < 24) { -            final File file = pending.remove(0); -            if (shouldHide(file)) continue; + +        // We'll be a running a BFS here. +        final Queue<File> pending = new ArrayDeque<>(); +        pending.offer(folder); + +        while (!pending.isEmpty() && result.getCount() < MAX_RESULTS_NUMBER) { +            final File file = pending.poll(); + +            // Skip hidden documents (both files and directories) +            if (shouldHideDocument(file)) continue;              if (file.isDirectory()) {                  for (File child : FileUtils.listFilesOrEmpty(file)) { -                    pending.add(child); +                    pending.offer(child);                  }              } -            if (!exclusion.contains(file.getAbsolutePath()) && matchSearchQueryArguments(file, -                    queryArgs)) { + +            if (exclusion.contains(file.getAbsolutePath())) continue; + +            if (matchSearchQueryArguments(file, queryArgs)) {                  includeFile(result, null, file);              }          } @@ -612,26 +609,23 @@ public abstract class FileSystemProvider extends DocumentsProvider {          final int flagIndex = ArrayUtils.indexOf(columns, Document.COLUMN_FLAGS);          if (flagIndex != -1) { +            final boolean isDir = mimeType.equals(Document.MIME_TYPE_DIR);              int flags = 0;              if (file.canWrite()) { -                if (mimeType.equals(Document.MIME_TYPE_DIR)) { +                flags |= Document.FLAG_SUPPORTS_DELETE; +                flags |= Document.FLAG_SUPPORTS_RENAME; +                flags |= Document.FLAG_SUPPORTS_MOVE; +                if (isDir) {                      flags |= Document.FLAG_DIR_SUPPORTS_CREATE; -                    flags |= Document.FLAG_SUPPORTS_DELETE; -                    flags |= Document.FLAG_SUPPORTS_RENAME; -                    flags |= Document.FLAG_SUPPORTS_MOVE; - -                    if (shouldBlockFromTree(docId)) { -                        flags |= Document.FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE; -                    } -                  } else {                      flags |= Document.FLAG_SUPPORTS_WRITE; -                    flags |= Document.FLAG_SUPPORTS_DELETE; -                    flags |= Document.FLAG_SUPPORTS_RENAME; -                    flags |= Document.FLAG_SUPPORTS_MOVE;                  }              } +            if (isDir && shouldBlockDirectoryFromTree(docId)) { +                flags |= Document.FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE; +            } +              if (mimeType.startsWith("image/")) {                  flags |= Document.FLAG_SUPPORTS_THUMBNAIL;              } @@ -664,22 +658,36 @@ public abstract class FileSystemProvider extends DocumentsProvider {          return row;      } -    private static final Pattern PATTERN_HIDDEN_PATH = Pattern.compile( -            "(?i)^/storage/[^/]+/(?:[0-9]+/)?Android/(?:data|obb|sandbox)$"); -      /** -     * In a scoped storage world, access to "Android/data" style directories are -     * hidden for privacy reasons. +     * Some providers may want to restrict access to certain directories and files, +     * e.g. <i>"Android/data"</i> and <i>"Android/obb"</i> on the shared storage for +     * privacy reasons. +     * Such providers should override this method.       */ -    protected boolean shouldHide(@NonNull File file) { -        return (PATTERN_HIDDEN_PATH.matcher(file.getAbsolutePath()).matches()); +    protected boolean shouldHideDocument(@NonNull String documentId) +            throws FileNotFoundException { +        return false;      } -    private boolean shouldShow(@NonNull File file) { -        return !shouldHide(file); +    /** +     * A variant of the {@link #shouldHideDocument(String)} that takes a {@link File} instead of +     * a {@link String} {@code documentId}. +     * +     * @see #shouldHideDocument(String) +     */ +    protected final boolean shouldHideDocument(@NonNull File document) +            throws FileNotFoundException { +        return shouldHideDocument(getDocIdForFile(document));      } -    protected boolean shouldBlockFromTree(@NonNull String docId) { +    /** +     * @return if the directory that should be blocked from being selected when the user launches +     * an {@link Intent#ACTION_OPEN_DOCUMENT_TREE} intent. +     * +     * @see Document#FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE +     */ +    protected boolean shouldBlockDirectoryFromTree(@NonNull String documentId) +            throws FileNotFoundException {          return false;      } diff --git a/core/java/com/android/internal/jank/DisplayResolutionTracker.java b/core/java/com/android/internal/jank/DisplayResolutionTracker.java index 72a1bac28c9f..ca6c54dc0285 100644 --- a/core/java/com/android/internal/jank/DisplayResolutionTracker.java +++ b/core/java/com/android/internal/jank/DisplayResolutionTracker.java @@ -24,6 +24,7 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN  import android.annotation.IntDef;  import android.annotation.Nullable; +import android.app.ActivityThread;  import android.hardware.display.DisplayManager;  import android.hardware.display.DisplayManagerGlobal;  import android.os.Handler; @@ -147,7 +148,8 @@ public class DisplayResolutionTracker {                  public void registerDisplayListener(DisplayManager.DisplayListener listener) {                      manager.registerDisplayListener(listener, handler,                              DisplayManager.EVENT_FLAG_DISPLAY_ADDED -                                    | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED); +                                    | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED, +                            ActivityThread.currentPackageName());                  }                  @Override diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java index d3103f1ed24b..039943098d7a 100644 --- a/core/java/com/android/internal/os/BatteryStatsHistory.java +++ b/core/java/com/android/internal/os/BatteryStatsHistory.java @@ -37,7 +37,6 @@ import android.util.ArraySet;  import android.util.AtomicFile;  import android.util.Slog;  import android.util.SparseArray; -import android.util.TimeUtils;  import com.android.internal.annotations.GuardedBy;  import com.android.internal.annotations.VisibleForTesting; @@ -78,7 +77,7 @@ public class BatteryStatsHistory {      private static final String TAG = "BatteryStatsHistory";      // Current on-disk Parcel version. Must be updated when the format of the parcelable changes -    private static final int VERSION = 209; +    private static final int VERSION = 210;      private static final String HISTORY_DIR = "battery-history";      private static final String FILE_SUFFIX = ".bh"; @@ -194,10 +193,11 @@ public class BatteryStatsHistory {      private int mNextHistoryTagIdx = 0;      private int mNumHistoryTagChars = 0;      private int mHistoryBufferLastPos = -1; -    private long mLastHistoryElapsedRealtimeMs = 0;      private long mTrackRunningHistoryElapsedRealtimeMs = 0;      private long mTrackRunningHistoryUptimeMs = 0; -    private long mHistoryBaseTimeMs; +    private final MonotonicClock mMonotonicClock; +    // Monotonic time when we started writing to the history buffer +    private long mHistoryBufferStartTime;      private final ArraySet<PowerStats.Descriptor> mWrittenPowerStatsDescriptors = new ArraySet<>();      private byte mLastHistoryStepLevel = 0;      private boolean mMutable = true; @@ -307,23 +307,26 @@ public class BatteryStatsHistory {       * @param maxHistoryBufferSize the most amount of RAM to used for buffering of history steps       */      public BatteryStatsHistory(File systemDir, int maxHistoryFiles, int maxHistoryBufferSize, -            HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock) { +            HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock, +            MonotonicClock monotonicClock) {          this(Parcel.obtain(), systemDir, maxHistoryFiles, maxHistoryBufferSize, -                stepDetailsCalculator, clock, new TraceDelegate()); +                stepDetailsCalculator, clock, monotonicClock, new TraceDelegate());          initHistoryBuffer();      }      @VisibleForTesting      public BatteryStatsHistory(Parcel historyBuffer, File systemDir,              int maxHistoryFiles, int maxHistoryBufferSize, -            HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock, TraceDelegate tracer) { +            HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock, +            MonotonicClock monotonicClock, TraceDelegate tracer) {          this(historyBuffer, systemDir, maxHistoryFiles, maxHistoryBufferSize, stepDetailsCalculator, -                clock, tracer, null); +                clock, monotonicClock, tracer, null);      }      private BatteryStatsHistory(Parcel historyBuffer, File systemDir,              int maxHistoryFiles, int maxHistoryBufferSize, -            HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock, TraceDelegate tracer, +            HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock, +            MonotonicClock monotonicClock, TraceDelegate tracer,              BatteryStatsHistory writableHistory) {          mHistoryBuffer = historyBuffer;          mSystemDir = systemDir; @@ -332,6 +335,7 @@ public class BatteryStatsHistory {          mStepDetailsCalculator = stepDetailsCalculator;          mTracer = tracer;          mClock = clock; +        mMonotonicClock = monotonicClock;          mWritableHistory = writableHistory;          if (mWritableHistory != null) {              mMutable = false; @@ -381,16 +385,18 @@ public class BatteryStatsHistory {      }      private BatteryHistoryFile makeBatteryHistoryFile() { -        return new BatteryHistoryFile(mHistoryDir, mClock.elapsedRealtime() + mHistoryBaseTimeMs); +        return new BatteryHistoryFile(mHistoryDir, mMonotonicClock.monotonicTime());      }      public BatteryStatsHistory(int maxHistoryFiles, int maxHistoryBufferSize, -            HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock) { +            HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock, +            MonotonicClock monotonicClock) {          mMaxHistoryFiles = maxHistoryFiles;          mMaxHistoryBufferSize = maxHistoryBufferSize;          mStepDetailsCalculator = stepDetailsCalculator;          mTracer = new TraceDelegate();          mClock = clock; +        mMonotonicClock = monotonicClock;          mHistoryBuffer = Parcel.obtain();          mSystemDir = null; @@ -417,16 +423,16 @@ public class BatteryStatsHistory {          mHistoryBuffer = Parcel.obtain();          mHistoryBuffer.unmarshall(historyBlob, 0, historyBlob.length); +        mMonotonicClock = null;          readFromParcel(parcel, true /* useBlobs */);      }      private void initHistoryBuffer() { -        mHistoryBaseTimeMs = 0; -        mLastHistoryElapsedRealtimeMs = 0;          mTrackRunningHistoryElapsedRealtimeMs = 0;          mTrackRunningHistoryUptimeMs = 0;          mWrittenPowerStatsDescriptors.clear(); +        mHistoryBufferStartTime = mMonotonicClock.monotonicTime();          mHistoryBuffer.setDataSize(0);          mHistoryBuffer.setDataPosition(0);          mHistoryBuffer.setDataCapacity(mMaxHistoryBufferSize / 2); @@ -466,7 +472,7 @@ public class BatteryStatsHistory {              historyBufferCopy.appendFrom(mHistoryBuffer, 0, mHistoryBuffer.dataSize());              return new BatteryStatsHistory(historyBufferCopy, mSystemDir, 0, 0, null, null, null, -                    this); +                    null, this);          }      } @@ -491,7 +497,7 @@ public class BatteryStatsHistory {       * When {@link #mHistoryBuffer} reaches {@link BatteryStatsImpl.Constants#MAX_HISTORY_BUFFER},       * create next history file.       */ -    public void startNextFile() { +    public void startNextFile(long elapsedRealtimeMs) {          if (mMaxHistoryFiles == 0) {              Slog.wtf(TAG, "mMaxHistoryFiles should not be zero when writing history");              return; @@ -517,6 +523,7 @@ public class BatteryStatsHistory {              Slog.e(TAG, "Could not create history file: " + mActiveFile.getBaseFile());          } +        mHistoryBufferStartTime = mMonotonicClock.monotonicTime(elapsedRealtimeMs);          mHistoryBuffer.setDataSize(0);          mHistoryBuffer.setDataPosition(0);          mHistoryBuffer.setDataCapacity(mMaxHistoryBufferSize / 2); @@ -699,9 +706,12 @@ public class BatteryStatsHistory {          if (mHistoryParcels != null) {              while (mParcelIndex < mHistoryParcels.size()) {                  final Parcel p = mHistoryParcels.get(mParcelIndex++); -                if (!skipHead(p)) { +                if (!verifyVersion(p)) {                      continue;                  } +                // skip monotonic time field. +                p.readLong(); +                  final int bufSize = p.readInt();                  final int curPos = p.dataPosition();                  mCurrentParcelEnd = curPos + bufSize; @@ -745,24 +755,36 @@ public class BatteryStatsHistory {          }          out.unmarshall(raw, 0, raw.length);          out.setDataPosition(0); -        return skipHead(out); +        if (!verifyVersion(out)) { +            return false; +        } +        // skip monotonic time field. +        out.readLong(); +        return true;      }      /** -     * Skip the header part of history parcel. +     * Verify header part of history parcel.       * -     * @param p history parcel to skip head.       * @return true if version match, false if not.       */ -    private boolean skipHead(Parcel p) { +    private boolean verifyVersion(Parcel p) {          p.setDataPosition(0);          final int version = p.readInt(); -        if (version != VERSION) { -            return false; -        } -        // skip historyBaseTime field. -        p.readLong(); -        return true; +        return version == VERSION; +    } + +    /** +     * Extracts the monotonic time, as per {@link MonotonicClock}, from the supplied battery history +     * buffer. +     */ +    public long getHistoryBufferStartTime(Parcel p) { +        int pos = p.dataPosition(); +        p.setDataPosition(0); +        p.readInt();        // Skip the version field +        long monotonicTime = p.readLong(); +        p.setDataPosition(pos); +        return monotonicTime;      }      /** @@ -1438,7 +1460,8 @@ public class BatteryStatsHistory {              throw new ConcurrentModificationException("Battery history is not writable");          } -        final long timeDiffMs = (mHistoryBaseTimeMs + elapsedRealtimeMs) - mHistoryLastWritten.time; +        final long timeDiffMs = mMonotonicClock.monotonicTime(elapsedRealtimeMs) +                                - mHistoryLastWritten.time;          final int diffStates = mHistoryLastWritten.states ^ cur.states;          final int diffStates2 = mHistoryLastWritten.states2 ^ cur.states2;          final int lastDiffStates = mHistoryLastWritten.states ^ mHistoryLastLastWritten.states; @@ -1476,7 +1499,9 @@ public class BatteryStatsHistory {              mHistoryBuffer.setDataSize(mHistoryBufferLastPos);              mHistoryBuffer.setDataPosition(mHistoryBufferLastPos);              mHistoryBufferLastPos = -1; -            elapsedRealtimeMs = mHistoryLastWritten.time - mHistoryBaseTimeMs; + +            elapsedRealtimeMs -= timeDiffMs; +              // If the last written history had a wakelock tag, we need to retain it.              // Note that the condition above made sure that we aren't in a case where              // both it and the current history item have a wakelock tag. @@ -1513,7 +1538,7 @@ public class BatteryStatsHistory {              HistoryItem copy = new HistoryItem();              copy.setTo(cur); -            startNextFile(); +            startNextFile(elapsedRealtimeMs);              // startRecordingHistory will reset mHistoryCur.              startRecordingHistory(elapsedRealtimeMs, uptimeMs, false); @@ -1548,7 +1573,7 @@ public class BatteryStatsHistory {          mHistoryBufferLastPos = mHistoryBuffer.dataPosition();          mHistoryLastLastWritten.setTo(mHistoryLastWritten);          final boolean hasTags = mHistoryLastWritten.tagsFirstOccurrence || cur.tagsFirstOccurrence; -        mHistoryLastWritten.setTo(mHistoryBaseTimeMs + elapsedRealtimeMs, cmd, cur); +        mHistoryLastWritten.setTo(mMonotonicClock.monotonicTime(elapsedRealtimeMs), cmd, cur);          if (mHistoryLastWritten.time < mHistoryLastLastWritten.time - 60000) {              Slog.wtf(TAG, "Significantly earlier event written to battery history:"                      + " time=" + mHistoryLastWritten.time @@ -1556,7 +1581,6 @@ public class BatteryStatsHistory {          }          mHistoryLastWritten.tagsFirstOccurrence = hasTags;          writeHistoryDelta(mHistoryBuffer, mHistoryLastWritten, mHistoryLastLastWritten); -        mLastHistoryElapsedRealtimeMs = elapsedRealtimeMs;          cur.wakelockTag = null;          cur.wakeReasonTag = null;          cur.eventCode = HistoryItem.EVENT_NONE; @@ -1926,6 +1950,10 @@ public class BatteryStatsHistory {              return;          } +        // Save the monotonic time first, so that even if the history write below fails, +        // we still wouldn't end up with overlapping history timelines. +        mMonotonicClock.write(); +          Parcel p = Parcel.obtain();          try {              final long start = SystemClock.uptimeMillis(); @@ -1951,8 +1979,7 @@ public class BatteryStatsHistory {              return;          } -        final long historyBaseTime = in.readLong(); - +        mHistoryBufferStartTime = in.readLong();          mHistoryBuffer.setDataSize(0);          mHistoryBuffer.setDataPosition(0); @@ -1972,39 +1999,11 @@ public class BatteryStatsHistory {              mHistoryBuffer.appendFrom(in, curPos, bufSize);              in.setDataPosition(curPos + bufSize);          } - -        mHistoryBaseTimeMs = historyBaseTime; -        if (DEBUG) { -            StringBuilder sb = new StringBuilder(128); -            sb.append("****************** NEW mHistoryBaseTimeMs: "); -            TimeUtils.formatDuration(mHistoryBaseTimeMs, sb); -            Slog.i(TAG, sb.toString()); -        } - -        if (mHistoryBaseTimeMs > 0) { -            long elapsedRealtimeMs = mClock.elapsedRealtime(); -            mLastHistoryElapsedRealtimeMs = elapsedRealtimeMs; -            mHistoryBaseTimeMs = mHistoryBaseTimeMs - elapsedRealtimeMs + 1; -            if (DEBUG) { -                StringBuilder sb = new StringBuilder(128); -                sb.append("****************** ADJUSTED mHistoryBaseTimeMs: "); -                TimeUtils.formatDuration(mHistoryBaseTimeMs, sb); -                Slog.i(TAG, sb.toString()); -            } -        }      }      private void writeHistoryBuffer(Parcel out) { -        if (DEBUG) { -            StringBuilder sb = new StringBuilder(128); -            sb.append("****************** WRITING mHistoryBaseTimeMs: "); -            TimeUtils.formatDuration(mHistoryBaseTimeMs, sb); -            sb.append(" mLastHistoryElapsedRealtimeMs: "); -            TimeUtils.formatDuration(mLastHistoryElapsedRealtimeMs, sb); -            Slog.i(TAG, sb.toString()); -        }          out.writeInt(BatteryStatsHistory.VERSION); -        out.writeLong(mHistoryBaseTimeMs + mLastHistoryElapsedRealtimeMs); +        out.writeLong(mHistoryBufferStartTime);          out.writeInt(mHistoryBuffer.dataSize());          if (DEBUG) {              Slog.i(TAG, "***************** WRITING HISTORY: " diff --git a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java index a5d2d0fc1a01..6bd5898b1637 100644 --- a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java +++ b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java @@ -16,7 +16,6 @@  package com.android.internal.os; -import android.annotation.CurrentTimeMillisLong;  import android.annotation.NonNull;  import android.os.BatteryManager;  import android.os.BatteryStats; @@ -34,8 +33,8 @@ public class BatteryStatsHistoryIterator implements Iterator<BatteryStats.Histor      private static final boolean DEBUG = false;      private static final String TAG = "BatteryStatsHistoryItr";      private final BatteryStatsHistory mBatteryStatsHistory; -    private final @CurrentTimeMillisLong long mStartTimeMs; -    private final @CurrentTimeMillisLong long mEndTimeMs; +    private final long mStartTimeMs; +    private final long mEndTimeMs;      private final BatteryStats.HistoryStepDetails mReadHistoryStepDetails =              new BatteryStats.HistoryStepDetails();      private final SparseArray<BatteryStats.HistoryTag> mHistoryTags = new SparseArray<>(); @@ -43,10 +42,10 @@ public class BatteryStatsHistoryIterator implements Iterator<BatteryStats.Histor              new PowerStats.DescriptorRegistry();      private BatteryStats.HistoryItem mHistoryItem = new BatteryStats.HistoryItem();      private boolean mNextItemReady; +    private boolean mTimeInitialized; -    public BatteryStatsHistoryIterator(@NonNull BatteryStatsHistory history, -            @CurrentTimeMillisLong long startTimeMs, -            @CurrentTimeMillisLong long endTimeMs) { +    public BatteryStatsHistoryIterator(@NonNull BatteryStatsHistory history, long startTimeMs, +            long endTimeMs) {          mBatteryStatsHistory = history;          mStartTimeMs = startTimeMs;          mEndTimeMs = (endTimeMs != 0) ? endTimeMs : Long.MAX_VALUE; @@ -82,7 +81,12 @@ public class BatteryStatsHistoryIterator implements Iterator<BatteryStats.Histor                  break;              } -            final long lastRealtimeMs = mHistoryItem.time; +            if (!mTimeInitialized) { +                mHistoryItem.time = mBatteryStatsHistory.getHistoryBufferStartTime(p); +                mTimeInitialized = true; +            } + +            final long lastMonotonicTimeMs = mHistoryItem.time;              final long lastWalltimeMs = mHistoryItem.currentTime;              try {                  readHistoryDelta(p, mHistoryItem); @@ -93,12 +97,13 @@ public class BatteryStatsHistoryIterator implements Iterator<BatteryStats.Histor              if (mHistoryItem.cmd != BatteryStats.HistoryItem.CMD_CURRENT_TIME                      && mHistoryItem.cmd != BatteryStats.HistoryItem.CMD_RESET                      && lastWalltimeMs != 0) { -                mHistoryItem.currentTime = lastWalltimeMs + (mHistoryItem.time - lastRealtimeMs); +                mHistoryItem.currentTime = +                        lastWalltimeMs + (mHistoryItem.time - lastMonotonicTimeMs);              } -            if (mEndTimeMs != 0 && mHistoryItem.currentTime >= mEndTimeMs) { +            if (mEndTimeMs != 0 && mHistoryItem.time >= mEndTimeMs) {                  break;              } -            if (mHistoryItem.currentTime >= mStartTimeMs) { +            if (mHistoryItem.time >= mStartTimeMs) {                  mNextItemReady = true;                  return;              } diff --git a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java index 5ea6ba86da71..1f44b338f3f7 100644 --- a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java +++ b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java @@ -191,6 +191,27 @@ public final class LongArrayMultiStateCounter implements Parcelable {      }      /** +     * Sets the new values for the given state. +     */ +    public void setValues(int state, long[] values) { +        if (state < 0 || state >= mStateCount) { +            throw new IllegalArgumentException( +                    "State: " + state + ", outside the range: [0-" + (mStateCount - 1) + "]"); +        } +        if (values.length != mLength) { +            throw new IllegalArgumentException( +                    "Invalid array length: " + values.length + ", expected: " + mLength); +        } +        LongArrayContainer container = sTmpArrayContainer.getAndSet(null); +        if (container == null || container.mLength != values.length) { +            container = new LongArrayContainer(values.length); +        } +        container.setValues(values); +        native_setValues(mNativeObject, state, container.mNativeObject); +        sTmpArrayContainer.set(container); +    } + +    /**       * Sets the new values.  The delta between the previously set values and these values       * is distributed among the state according to the time the object spent in those states       * since the previous call to updateValues. @@ -317,6 +338,10 @@ public final class LongArrayMultiStateCounter implements Parcelable {      private static native void native_setState(long nativeObject, int state, long timestampMs);      @CriticalNative +    private static native void native_setValues(long nativeObject, int state, +            long longArrayContainerNativeObject); + +    @CriticalNative      private static native void native_updateValues(long nativeObject,              long longArrayContainerNativeObject, long timestampMs); diff --git a/core/java/com/android/internal/os/MonotonicClock.java b/core/java/com/android/internal/os/MonotonicClock.java new file mode 100644 index 000000000000..6f114e34b21c --- /dev/null +++ b/core/java/com/android/internal/os/MonotonicClock.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.os; + +import android.annotation.NonNull; +import android.util.AtomicFile; +import android.util.Log; +import android.util.Xml; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.modules.utils.TypedXmlPullParser; +import com.android.modules.utils.TypedXmlSerializer; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; + +/** + * A clock that is similar to SystemClock#elapsedRealtime(), except that it is not reset + * on reboot, but keeps going. + */ +public class MonotonicClock { +    private static final String TAG = "MonotonicClock"; + +    private static final String XML_TAG_MONOTONIC_TIME = "monotonic_time"; +    private static final String XML_ATTR_TIMESHIFT = "timeshift"; + +    private final AtomicFile mFile; +    private final Clock mClock; +    private long mTimeshift; + +    public MonotonicClock(File file) { +        mFile = new AtomicFile(file); +        mClock = Clock.SYSTEM_CLOCK; +        read(); +    } + +    public MonotonicClock(long monotonicTime, @NonNull Clock clock) { +        mClock = clock; +        mFile = null; +        mTimeshift = monotonicTime - mClock.elapsedRealtime(); +    } + +    /** +     * Returns time in milliseconds, based on SystemClock.elapsedTime, adjusted so that +     * after a device reboot the time keeps increasing. +     */ +    public long monotonicTime() { +        return monotonicTime(mClock.elapsedRealtime()); +    } + +    /** +     * Like {@link #monotonicTime()}, except the elapsed time is supplied as an argument instead +     * of being read from the Clock. +     */ +    public long monotonicTime(long elapsedRealtimeMs) { +        return mTimeshift + elapsedRealtimeMs; +    } + +    private void read() { +        if (!mFile.exists()) { +            return; +        } + +        try { +            readXml(new ByteArrayInputStream(mFile.readFully()), Xml.newBinaryPullParser()); +        } catch (IOException e) { +            Log.e(TAG, "Cannot load monotonic clock from " + mFile.getBaseFile(), e); +        } +    } + +    /** +     * Saves the timeshift into a file.  Call this method just before system shutdown, after +     * writing the last battery history event. +     */ +    public void write() { +        if (mFile == null) { +            return; +        } + +        mFile.write(out -> { +            try { +                writeXml(out, Xml.newBinarySerializer()); +                out.close(); +            } catch (IOException e) { +                Log.e(TAG, "Cannot write monotonic clock to " + mFile.getBaseFile(), e); +            } +        }); +    } + +    /** +     * Parses an XML file containing the persistent state of the monotonic clock. +     */ +    @VisibleForTesting +    public void readXml(InputStream inputStream, TypedXmlPullParser parser) throws IOException { +        long savedTimeshift = 0; +        try { +            parser.setInput(inputStream, StandardCharsets.UTF_8.name()); +            int eventType = parser.getEventType(); +            while (eventType != XmlPullParser.END_DOCUMENT) { +                if (eventType == XmlPullParser.START_TAG +                        && parser.getName().equals(XML_TAG_MONOTONIC_TIME)) { +                    savedTimeshift = parser.getAttributeLong(null, XML_ATTR_TIMESHIFT); +                } +                eventType = parser.next(); +            } +        } catch (XmlPullParserException e) { +            throw new IOException(e); +        } +        mTimeshift = savedTimeshift - mClock.elapsedRealtime(); +    } + +    /** +     * Creates an XML file containing the persistent state of the monotonic clock. +     */ +    @VisibleForTesting +    public void writeXml(OutputStream out, TypedXmlSerializer serializer) throws IOException { +        serializer.setOutput(out, StandardCharsets.UTF_8.name()); +        serializer.startDocument(null, true); +        serializer.startTag(null, XML_TAG_MONOTONIC_TIME); +        serializer.attributeLong(null, XML_ATTR_TIMESHIFT, monotonicTime()); +        serializer.endTag(null, XML_TAG_MONOTONIC_TIME); +        serializer.endDocument(); +    } +} diff --git a/core/java/com/android/internal/os/MultiStateStats.java b/core/java/com/android/internal/os/MultiStateStats.java index f971849987dd..dc5055a0881b 100644 --- a/core/java/com/android/internal/os/MultiStateStats.java +++ b/core/java/com/android/internal/os/MultiStateStats.java @@ -16,9 +16,17 @@  package com.android.internal.os; +import android.util.Slog; +  import com.android.internal.annotations.VisibleForTesting;  import com.android.internal.util.Preconditions; +import com.android.modules.utils.TypedXmlPullParser; +import com.android.modules.utils.TypedXmlSerializer; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import java.io.IOException;  import java.io.PrintWriter;  import java.util.Arrays; @@ -29,15 +37,21 @@ import java.util.Arrays;   * values;   */  public class MultiStateStats { +    private static final String TAG = "MultiStateStats"; + +    private static final String XML_TAG_STATS = "stats"; +      /**       * A set of states, e.g. on-battery, screen-on, procstate.  The state values are integers       * from 0 to States.mLabels.length       */      public static class States { +        final String mName;          final boolean mTracked;          final String[] mLabels; -        public States(boolean tracked, String... labels) { +        public States(String name, boolean tracked, String... labels) { +            mName = name;              this.mTracked = tracked;              this.mLabels = labels;          } @@ -155,11 +169,28 @@ public class MultiStateStats {                     >>> mStateBitFieldShifts[stateIndex];          } -        private int setStateInComposite(int baseCompositeState, int stateIndex, int value) { +        int setStateInComposite(int baseCompositeState, int stateIndex, int value) {              return (baseCompositeState & ~mStateBitFieldMasks[stateIndex])                      | (value << mStateBitFieldShifts[stateIndex]);          } +        int setStateInComposite(int compositeState, String stateName, String stateLabel) { +            for (int stateIndex = 0; stateIndex < mStates.length; stateIndex++) { +                States stateConfig = mStates[stateIndex]; +                if (stateConfig.mName.equals(stateName)) { +                    for (int state = 0; state < stateConfig.mLabels.length; state++) { +                        if (stateConfig.mLabels[state].equals(stateLabel)) { +                            return setStateInComposite(compositeState, stateIndex, state); +                        } +                    } +                    Slog.e(TAG, "Unexpected label '" + stateLabel + "' for state: " + stateName); +                    return -1; +                } +            } +            Slog.e(TAG, "Unsupported state: " + stateName); +            return -1; +        } +          /**           * Allocates a new stats container using this Factory's configuration.           */ @@ -195,6 +226,10 @@ public class MultiStateStats {              }              return serialState;          } + +        int getSerialState(int compositeState) { +            return mCompositeToSerialState[compositeState]; +        }      }      private final Factory mFactory; @@ -254,6 +289,106 @@ public class MultiStateStats {      }      /** +     * Stores contents in an XML doc. +     */ +    public void writeXml(TypedXmlSerializer serializer) throws IOException { +        long[] tmpArray = new long[mCounter.getArrayLength()]; +        writeXmlAllStates(serializer, new int[mFactory.mStates.length], 0, tmpArray); +    } + +    private void writeXmlAllStates(TypedXmlSerializer serializer, int[] states, int stateIndex, +            long[] values) throws IOException { +        if (stateIndex < states.length) { +            if (!mFactory.mStates[stateIndex].mTracked) { +                writeXmlAllStates(serializer, states, stateIndex + 1, values); +                return; +            } + +            for (int i = 0; i < mFactory.mStates[stateIndex].mLabels.length; i++) { +                states[stateIndex] = i; +                writeXmlAllStates(serializer, states, stateIndex + 1, values); +            } +            return; +        } + +        mCounter.getCounts(values, mFactory.getSerialState(states)); +        boolean nonZero = false; +        for (long value : values) { +            if (value != 0) { +                nonZero = true; +                break; +            } +        } +        if (!nonZero) { +            return; +        } + +        serializer.startTag(null, XML_TAG_STATS); + +        for (int i = 0; i < states.length; i++) { +            if (mFactory.mStates[i].mTracked && states[i] != 0) { +                serializer.attribute(null, mFactory.mStates[i].mName, +                        mFactory.mStates[i].mLabels[states[i]]); +            } +        } +        for (int i = 0; i < values.length; i++) { +            if (values[i] != 0) { +                serializer.attributeLong(null, "_" + i, values[i]); +            } +        } +        serializer.endTag(null, XML_TAG_STATS); +    } + +    /** +     * Populates the object with contents in an XML doc. The parser is expected to be +     * positioned on the opening tag of the corresponding element. +     */ +    public boolean readFromXml(TypedXmlPullParser parser) throws XmlPullParserException, +            IOException { +        String outerTag = parser.getName(); +        long[] tmpArray = new long[mCounter.getArrayLength()]; +        int eventType = parser.getEventType(); +        while (eventType != XmlPullParser.END_DOCUMENT +               && !(eventType == XmlPullParser.END_TAG && parser.getName().equals(outerTag))) { +            if (eventType == XmlPullParser.START_TAG) { +                if (parser.getName().equals(XML_TAG_STATS)) { +                    Arrays.fill(tmpArray, 0); +                    int compositeState = 0; +                    int attributeCount = parser.getAttributeCount(); +                    for (int i = 0; i < attributeCount; i++) { +                        String attributeName = parser.getAttributeName(i); +                        if (attributeName.startsWith("_")) { +                            int index; +                            try { +                                index = Integer.parseInt(attributeName.substring(1)); +                            } catch (NumberFormatException e) { +                                throw new XmlPullParserException( +                                        "Unexpected index syntax: " + attributeName, parser, e); +                            } +                            if (index < 0 || index >= tmpArray.length) { +                                Slog.e(TAG, "State index out of bounds: " + index +                                            + " length: " + tmpArray.length); +                                return false; +                            } +                            tmpArray[index] = parser.getAttributeLong(i); +                        } else { +                            String attributeValue = parser.getAttributeValue(i); +                            compositeState = mFactory.setStateInComposite(compositeState, +                                    attributeName, attributeValue); +                            if (compositeState == -1) { +                                return false; +                            } +                        } +                    } +                    mCounter.setValues(mFactory.getSerialState(compositeState), tmpArray); +                } +            } +            eventType = parser.next(); +        } +        return true; +    } + +    /**       * Prints the accumulated stats, one line of every combination of states that has data.       */      public void dump(PrintWriter pw) { diff --git a/core/java/com/android/internal/os/OWNERS b/core/java/com/android/internal/os/OWNERS index e35b7f184663..996e4244c473 100644 --- a/core/java/com/android/internal/os/OWNERS +++ b/core/java/com/android/internal/os/OWNERS @@ -5,14 +5,10 @@ per-file *Binder* = file:BINDER_OWNERS  per-file *BinaryTransparency* = file:/core/java/android/transparency/OWNERS  # BatteryStats -per-file BatterySipper.java = file:/BATTERY_STATS_OWNERS  per-file BatteryStats* = file:/BATTERY_STATS_OWNERS -per-file BatteryUsageStats* = file:/BATTERY_STATS_OWNERS -per-file *ChargeCalculator* = file:/BATTERY_STATS_OWNERS -per-file *PowerCalculator* = file:/BATTERY_STATS_OWNERS -per-file *PowerEstimator* = file:/BATTERY_STATS_OWNERS -per-file *PowerStats* = file:/BATTERY_STATS_OWNERS  per-file *Kernel* = file:/BATTERY_STATS_OWNERS +per-file *Clock* = file:/BATTERY_STATS_OWNERS  per-file *MultiState* = file:/BATTERY_STATS_OWNERS  per-file *PowerProfile* = file:/BATTERY_STATS_OWNERS +per-file *PowerStats* = file:/BATTERY_STATS_OWNERS diff --git a/core/java/com/android/internal/os/PowerStats.java b/core/java/com/android/internal/os/PowerStats.java index 8f66d1f9365c..1130a454c6b0 100644 --- a/core/java/com/android/internal/os/PowerStats.java +++ b/core/java/com/android/internal/os/PowerStats.java @@ -24,9 +24,16 @@ import android.os.Parcel;  import android.os.PersistableBundle;  import android.os.UserHandle;  import android.util.IndentingPrintWriter; -import android.util.Log; +import android.util.Slog;  import android.util.SparseArray; +import com.android.modules.utils.TypedXmlPullParser; +import com.android.modules.utils.TypedXmlSerializer; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException;  import java.util.Arrays;  import java.util.Objects; @@ -61,6 +68,13 @@ public final class PowerStats {       * to adjust the algorithm in accordance with the stats available on the device.       */      public static class Descriptor { +        public static final String XML_TAG_DESCRIPTOR = "descriptor"; +        private static final String XML_ATTR_ID = "id"; +        private static final String XML_ATTR_NAME = "name"; +        private static final String XML_ATTR_STATS_ARRAY_LENGTH = "stats-array-length"; +        private static final String XML_ATTR_UID_STATS_ARRAY_LENGTH = "uid-stats-array-length"; +        private static final String XML_TAG_EXTRAS = "extras"; +          /**           * {@link BatteryConsumer.PowerComponent} (e.g. CPU, WIFI etc) that this snapshot relates           * to; or a custom power component ID (if the value @@ -126,7 +140,7 @@ public final class PowerStats {              int firstWord = parcel.readInt();              int version = (firstWord & PARCEL_FORMAT_VERSION_MASK) >>> PARCEL_FORMAT_VERSION_SHIFT;              if (version != PARCEL_FORMAT_VERSION) { -                Log.w(TAG, "Cannot read PowerStats from Parcel - the parcel format version has " +                Slog.w(TAG, "Cannot read PowerStats from Parcel - the parcel format version has "                             + "changed from " + version + " to " + PARCEL_FORMAT_VERSION);                  return null;              } @@ -155,6 +169,71 @@ public final class PowerStats {                      that.extras);  // Since the Parcel is now unparceled, do a deep comparison          } +        /** +         * Stores contents in an XML doc. +         */ +        public void writeXml(TypedXmlSerializer serializer) throws IOException { +            serializer.startTag(null, XML_TAG_DESCRIPTOR); +            serializer.attributeInt(null, XML_ATTR_ID, powerComponentId); +            serializer.attribute(null, XML_ATTR_NAME, name); +            serializer.attributeInt(null, XML_ATTR_STATS_ARRAY_LENGTH, statsArrayLength); +            serializer.attributeInt(null, XML_ATTR_UID_STATS_ARRAY_LENGTH, uidStatsArrayLength); +            try { +                serializer.startTag(null, XML_TAG_EXTRAS); +                extras.saveToXml(serializer); +                serializer.endTag(null, XML_TAG_EXTRAS); +            } catch (XmlPullParserException e) { +                throw new IOException(e); +            } +            serializer.endTag(null, XML_TAG_DESCRIPTOR); +        } + +        /** +         * Creates a Descriptor by parsing an XML doc.  The parser is expected to be positioned +         * on or before the opening "descriptor" tag. +         */ +        public static Descriptor createFromXml(TypedXmlPullParser parser) +                throws XmlPullParserException, IOException { +            int powerComponentId = -1; +            String name = null; +            int statsArrayLength = 0; +            int uidStatsArrayLength = 0; +            PersistableBundle extras = null; +            int eventType = parser.getEventType(); +            while (eventType != XmlPullParser.END_DOCUMENT +                   && !(eventType == XmlPullParser.END_TAG +                        && parser.getName().equals(XML_TAG_DESCRIPTOR))) { +                if (eventType == XmlPullParser.START_TAG) { +                    switch (parser.getName()) { +                        case XML_TAG_DESCRIPTOR: +                            powerComponentId = parser.getAttributeInt(null, XML_ATTR_ID); +                            name = parser.getAttributeValue(null, XML_ATTR_NAME); +                            statsArrayLength = parser.getAttributeInt(null, +                                    XML_ATTR_STATS_ARRAY_LENGTH); +                            uidStatsArrayLength = parser.getAttributeInt(null, +                                    XML_ATTR_UID_STATS_ARRAY_LENGTH); +                            break; +                        case XML_TAG_EXTRAS: +                            extras = PersistableBundle.restoreFromXml(parser); +                            break; +                    } +                } +                eventType = parser.next(); +            } +            if (powerComponentId == -1) { +                return null; +            } else if (powerComponentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID) { +                return new Descriptor(powerComponentId, name, statsArrayLength, uidStatsArrayLength, +                        extras); +            } else if (powerComponentId < BatteryConsumer.POWER_COMPONENT_COUNT) { +                return new Descriptor(powerComponentId, statsArrayLength, uidStatsArrayLength, +                        extras); +            } else { +                Slog.e(TAG, "Unrecognized power component: " + powerComponentId); +                return null; +            } +        } +          @Override          public int hashCode() {              return Objects.hash(powerComponentId); @@ -259,7 +338,7 @@ public final class PowerStats {              Descriptor descriptor = registry.get(powerComponentId);              if (descriptor == null) { -                Log.e(TAG, "Unsupported PowerStats for power component ID: " + powerComponentId); +                Slog.e(TAG, "Unsupported PowerStats for power component ID: " + powerComponentId);                  return null;              }              PowerStats stats = new PowerStats(descriptor); @@ -274,7 +353,7 @@ public final class PowerStats {                  stats.uidStats.put(uid, uidStats);              }              if (parcel.dataPosition() != endPos) { -                Log.e(TAG, "Corrupted PowerStats parcel. Expected length: " + length +                Slog.e(TAG, "Corrupted PowerStats parcel. Expected length: " + length                             + ", actual length: " + (parcel.dataPosition() - startPos));                  return null;              } diff --git a/core/java/com/android/internal/policy/WearGestureInterceptionDetector.java b/core/java/com/android/internal/policy/WearGestureInterceptionDetector.java index 6fd50180e78b..504928cd4347 100644 --- a/core/java/com/android/internal/policy/WearGestureInterceptionDetector.java +++ b/core/java/com/android/internal/policy/WearGestureInterceptionDetector.java @@ -74,16 +74,15 @@ public class WearGestureInterceptionDetector {          return windowSwipeToDismiss;      } -    private boolean isPointerIndexValid(MotionEvent ev) { +    private int getIndexForValidPointer(MotionEvent ev) {          int pointerIndex = ev.findPointerIndex(mActivePointerId);          if (pointerIndex == -1) {              if (DEBUG) {                  Log.e(TAG, "Invalid pointer index: ignoring.");              }              mDiscardIntercept = true; -            return false;          } -        return true; +        return pointerIndex;      }      private void updateSwiping(MotionEvent ev) { @@ -98,7 +97,7 @@ public class WearGestureInterceptionDetector {          }      } -    private void updateDiscardIntercept(MotionEvent ev) { +    private void updateDiscardIntercept(MotionEvent ev, int pointerIndex) {          if (!mSwiping) {              // Don't look at canScroll until we have passed the touch slop              return; @@ -107,8 +106,8 @@ public class WearGestureInterceptionDetector {              return;          }          final boolean checkLeft = mDownX < ev.getRawX(); -        final float x = ev.getX(mActivePointerId); -        final float y = ev.getY(mActivePointerId); +        final float x = ev.getX(pointerIndex); +        final float y = ev.getY(pointerIndex);          if (canScroll(mInstalledDecorView, false, checkLeft, x, y)) {              mDiscardIntercept = true;          } @@ -152,11 +151,12 @@ public class WearGestureInterceptionDetector {                  if (mDiscardIntercept) {                      break;                  } -                if (!isPointerIndexValid(ev)) { +                final int pointerIndex = getIndexForValidPointer(ev); +                if (pointerIndex == -1) {                      break;                  }                  updateSwiping(ev); -                updateDiscardIntercept(ev); +                updateDiscardIntercept(ev, pointerIndex);                  break;              case MotionEvent.ACTION_CANCEL:              case MotionEvent.ACTION_UP: diff --git a/core/java/com/android/internal/protolog/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java index ec525f09fa88..4bb7c33b41e2 100644 --- a/core/java/com/android/internal/protolog/ProtoLogGroup.java +++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java @@ -91,6 +91,8 @@ public enum ProtoLogGroup implements IProtoLogGroup {      WM_DEBUG_BACK_PREVIEW(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,              "CoreBackPreview"),      WM_DEBUG_DREAM(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, Consts.TAG_WM), + +    WM_DEBUG_DIMMER(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM),      TEST_GROUP(true, true, false, "WindowManagerProtoLogTest");      private final boolean mEnabled; diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index b5d70d379e0c..50253cf9e457 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -1315,7 +1315,16 @@ void AndroidRuntime::exit(int code)          ALOGI("VM exiting with result code %d.", code);          onExit(code);      } + +#ifdef __ANDROID_CLANG_COVERAGE__ +    // When compiled with coverage, a function is registered with atexit to call +    // `__llvm_profile_write_file` when the process exit. +    // For Clang code coverage to work, call exit instead of _exit to run hooks +    // registered with atexit. +    ::exit(code); +#else      ::_exit(code); +#endif  }  void AndroidRuntime::onVmCreated(JNIEnv* env) diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp index deb138fda867..9c883d18a9ae 100644 --- a/core/jni/android_hardware_SensorManager.cpp +++ b/core/jni/android_hardware_SensorManager.cpp @@ -265,6 +265,18 @@ static jboolean nativeIsDataInjectionEnabled(JNIEnv *_env, jclass _this, jlong s      return mgr->isDataInjectionEnabled();  } +static jboolean nativeIsReplayDataInjectionEnabled(JNIEnv *_env, jclass _this, +                                                   jlong sensorManager) { +    SensorManager *mgr = reinterpret_cast<SensorManager *>(sensorManager); +    return mgr->isReplayDataInjectionEnabled(); +} + +static jboolean nativeIsHalBypassReplayDataInjectionEnabled(JNIEnv *_env, jclass _this, +                                                            jlong sensorManager) { +    SensorManager *mgr = reinterpret_cast<SensorManager *>(sensorManager); +    return mgr->isHalBypassReplayDataInjectionEnabled(); +} +  static jint nativeCreateDirectChannel(JNIEnv *_env, jclass _this, jlong sensorManager,                                        jint deviceId, jlong size, jint channelType, jint fd,                                        jobject hardwareBufferObj) { @@ -533,6 +545,11 @@ static const JNINativeMethod gSystemSensorManagerMethods[] = {          {"nativeIsDataInjectionEnabled", "(J)Z", (void *)nativeIsDataInjectionEnabled}, +        {"nativeIsReplayDataInjectionEnabled", "(J)Z", (void *)nativeIsReplayDataInjectionEnabled}, + +        {"nativeIsHalBypassReplayDataInjectionEnabled", "(J)Z", +         (void *)nativeIsHalBypassReplayDataInjectionEnabled}, +          {"nativeCreateDirectChannel", "(JIJIILandroid/hardware/HardwareBuffer;)I",           (void *)nativeCreateDirectChannel}, diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index ba644eb0c03e..178c0d0d95be 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -963,10 +963,11 @@ static void nativeSetDefaultFrameRateCompatibility(JNIEnv* env, jclass clazz, jl  }  static void nativeSetFrameRateCategory(JNIEnv* env, jclass clazz, jlong transactionObj, -                                       jlong nativeObject, jint category) { +                                       jlong nativeObject, jint category, +                                       jboolean smoothSwitchOnly) {      auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);      const auto ctrl = reinterpret_cast<SurfaceControl*>(nativeObject); -    transaction->setFrameRateCategory(ctrl, static_cast<int8_t>(category)); +    transaction->setFrameRateCategory(ctrl, static_cast<int8_t>(category), smoothSwitchOnly);  }  static void nativeSetFrameRateSelectionStrategy(JNIEnv* env, jclass clazz, jlong transactionObj, @@ -2181,7 +2182,7 @@ static const JNINativeMethod sSurfaceControlMethods[] = {              (void*)nativeSetFrameRate },      {"nativeSetDefaultFrameRateCompatibility", "(JJI)V",              (void*)nativeSetDefaultFrameRateCompatibility}, -    {"nativeSetFrameRateCategory", "(JJI)V", +    {"nativeSetFrameRateCategory", "(JJIZ)V",              (void*)nativeSetFrameRateCategory},      {"nativeSetFrameRateSelectionStrategy", "(JJI)V",              (void*)nativeSetFrameRateSelectionStrategy}, diff --git a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp index 69202111f74c..1f29735b93a4 100644 --- a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp +++ b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp @@ -55,6 +55,15 @@ static void native_setState(jlong nativePtr, jint state, jlong timestamp) {      counter->setState(state, timestamp);  } +static void native_setValues(jlong nativePtr, jint state, jlong longArrayContainerNativePtr) { +    battery::LongArrayMultiStateCounter *counter = +            reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr); +    std::vector<uint64_t> *vector = +            reinterpret_cast<std::vector<uint64_t> *>(longArrayContainerNativePtr); + +    counter->setValue(state, *vector); +} +  static void native_updateValues(jlong nativePtr, jlong longArrayContainerNativePtr,                                  jlong timestamp) {      battery::LongArrayMultiStateCounter *counter = @@ -210,6 +219,8 @@ static const JNINativeMethod g_LongArrayMultiStateCounter_methods[] = {          // @CriticalNative          {"native_setState", "(JIJ)V", (void *)native_setState},          // @CriticalNative +        {"native_setValues", "(JIJ)V", (void *)native_setValues}, +        // @CriticalNative          {"native_updateValues", "(JJJ)V", (void *)native_updateValues},          // @CriticalNative          {"native_incrementValues", "(JJJ)V", (void *)native_incrementValues}, diff --git a/core/proto/android/service/package.proto b/core/proto/android/service/package.proto index eb14db070f9f..068f4dd07ccb 100644 --- a/core/proto/android/service/package.proto +++ b/core/proto/android/service/package.proto @@ -115,6 +115,9 @@ message PackageProto {                  // Only set if the app defined a monochrome icon.                  optional string monochrome_icon_bitmap_path = 3; + +                // The component name of the original activity (pre-archival). +                optional string original_component_name = 4;              }              /** Information about main activities. */ diff --git a/core/res/Android.bp b/core/res/Android.bp index b71995f899c5..4e686b7ad80c 100644 --- a/core/res/Android.bp +++ b/core/res/Android.bp @@ -104,6 +104,7 @@ genrule {  android_app {      name: "framework-res", +    use_resource_processor: false,      sdk_version: "core_platform",      certificate: "platform", diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index e120f82347c1..cffbaa75ab76 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -4565,12 +4565,6 @@      <permission android:name="android.permission.SET_DEBUG_APP"          android:protectionLevel="signature|privileged|development" /> -    <!-- Allows an application to access the data in Dropbox. -    <p>Not for use by third-party applications. -    @hide --> -    <permission android:name="android.permission.READ_DROPBOX_DATA" -        android:protectionLevel="signature|privileged|development" /> -      <!-- Allows an application to set the maximum number of (not needed)           application processes that can be running.           <p>Not for use by third-party applications. --> @@ -6128,7 +6122,9 @@          android:protectionLevel="signature|privileged|development|appop|retailDemo" />      <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" /> -    <!-- @SystemApi @hide Allows trusted system components to report events to UsageStatsManager --> +    <!-- @SystemApi @hide +         @FlaggedApi("android.app.usage.report_usage_stats_permission") +         Allows trusted system components to report events to UsageStatsManager -->      <permission android:name="android.permission.REPORT_USAGE_STATS"                  android:protectionLevel="signature|module" /> @@ -7241,13 +7237,23 @@      <!-- @SystemApi Required for the privileged assistant apps targeting           {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} -         that receive voice trigger from the trusted hotword detection service. +         that receive voice trigger from a sandboxed {@link HotwordDetectionService}.           <p>Protection level: signature|privileged|appop           @FlaggedApi("android.permission.flags.voice_activation_permission_apis")           @hide -->      <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/res/res/values/config_battery_stats.xml b/core/res/res/values/config_battery_stats.xml index 8fb48bcb8947..e42962ce9195 100644 --- a/core/res/res/values/config_battery_stats.xml +++ b/core/res/res/values/config_battery_stats.xml @@ -31,4 +31,13 @@      is a relatively expensive operation, this throttle period may need to be adjusted for low-power      devices-->      <integer name="config_defaultPowerStatsThrottlePeriodCpu">60000</integer> + +    <!-- PowerStats aggregation period in milliseconds. This is the interval at which the power +    stats aggregation procedure is performed and the results stored in PowerStatsStore. --> +    <integer name="config_powerStatsAggregationPeriod">14400000</integer> + +    <!-- PowerStats aggregation span duration in milliseconds. This is the length of battery +    history time for every aggregated power stats span that is stored stored in PowerStatsStore. +    It should not be larger than config_powerStatsAggregationPeriod (but it can be the same) --> +    <integer name="config_aggregatedPowerStatsSpanDuration">3600000</integer>  </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 5db513d20dca..2b9194f8c54e 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -5126,6 +5126,8 @@    <java-symbol type="bool" name="config_batteryStatsResetOnUnplugHighBatteryLevel" />    <java-symbol type="bool" name="config_batteryStatsResetOnUnplugAfterSignificantCharge" />    <java-symbol type="integer" name="config_defaultPowerStatsThrottlePeriodCpu" /> +  <java-symbol type="integer" name="config_powerStatsAggregationPeriod" /> +  <java-symbol type="integer" name="config_aggregatedPowerStatsSpanDuration" />    <java-symbol name="materialColorOnSecondaryFixedVariant" type="attr"/>    <java-symbol name="materialColorOnTertiaryFixedVariant" type="attr"/> diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml index af8c69ea1441..3a2e50aa06e8 100644 --- a/core/res/res/xml/sms_short_codes.xml +++ b/core/res/res/xml/sms_short_codes.xml @@ -54,6 +54,9 @@      <!-- Azerbaijan: 4-5 digits, known premium codes listed -->      <shortcode country="az" pattern="\\d{4,5}" premium="330[12]|87744|901[234]|93(?:94|101)|9426|9525" /> +    <!-- Bangladesh: 1-5 digits (standard system default, not country specific) --> +    <shortcode country="bd" pattern="\\d{1,5}" free="16672" /> +      <!-- Belgium: 4 digits, plus EU: http://www.mobileweb.be/en/mobileweb/sms-numberplan.asp -->      <shortcode country="be" premium="\\d{4}" free="8\\d{3}|116\\d{3}" /> @@ -145,7 +148,7 @@      <shortcode country="in" pattern="\\d{1,5}" free="59336|53969" />      <!-- Indonesia: 1-5 digits (standard system default, not country specific) --> -    <shortcode country="id" pattern="\\d{1,5}" free="99477|6006|46645|363" /> +    <shortcode country="id" pattern="\\d{1,5}" free="99477|6006|46645|363|93457" />      <!-- Ireland: 5 digits, 5xxxx (50xxx=free, 5[12]xxx=standard), plus EU:           http://www.comreg.ie/_fileupload/publications/ComReg1117.pdf --> @@ -190,7 +193,7 @@      <shortcode country="mk" pattern="\\d{1,6}" free="129005|122" />      <!-- Mexico: 4-5 digits (not confirmed), known premium codes listed --> -    <shortcode country="mx" pattern="\\d{4,5}" premium="53035|7766" free="26259|46645|50025|50052|5050|76551|88778|9963|91101" /> +    <shortcode country="mx" pattern="\\d{4,5}" premium="53035|7766" free="26259|46645|50025|50052|5050|76551|88778|9963|91101|45453" />      <!-- Malaysia: 5 digits: http://www.skmm.gov.my/attachment/Consumer_Regulation/Mobile_Content_Services_FAQs.pdf -->      <shortcode country="my" pattern="\\d{5}" premium="32298|33776" free="22099|28288|66668" /> @@ -205,7 +208,7 @@      <shortcode country="no" pattern="\\d{4,5}" premium="2201|222[67]" free="2171" />      <!-- New Zealand: 3-4 digits, known premium codes listed --> -    <shortcode country="nz" pattern="\\d{3,4}" premium="3903|8995|4679" free="1737|176|2141|3067|3068|3110|4006|4053|4061|4062|4202|4300|4334|4412|4575|5626|8006|8681" /> +    <shortcode country="nz" pattern="\\d{3,4}" premium="3903|8995|4679" free="1737|176|2141|3067|3068|3110|3876|4006|4053|4061|4062|4202|4300|4334|4412|4575|5626|8006|8681" />      <!-- Peru: 4-5 digits (not confirmed), known premium codes listed -->      <shortcode country="pe" pattern="\\d{4,5}" free="9963|40778" /> diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index 2993a0e63228..445ddf52bf1c 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -65,6 +65,7 @@ android_test {          "device-time-shell-utils",          "testables",          "com.android.text.flags-aconfig-java", +        "flag-junit",      ],      libs: [ @@ -75,6 +76,7 @@ android_test {          "framework",          "ext",          "framework-res", +        "android.view.flags-aconfig-java",      ],      jni_libs: [          "libpowermanagertest_jni", diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java index 0941a2b6d263..6c14ee382bdc 100644 --- a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java +++ b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java @@ -137,7 +137,7 @@ public class FontScaleConverterActivityTest {              );          }); -        PollingCheck.waitFor(/* timeout= */ 7000, () -> { +        PollingCheck.waitFor(/* timeout= */ 10000, () -> {              AtomicBoolean isActivityAtCorrectScale = new AtomicBoolean(false);              rule.getScenario().onActivity(activity ->                      isActivityAtCorrectScale.set( @@ -163,7 +163,7 @@ public class FontScaleConverterActivityTest {          });          PollingCheck.waitFor( -                /* timeout= */ 5000, +                /* timeout= */ 10000,                  () -> InstrumentationRegistry.getInstrumentation()                                          .getContext()                                          .getResources() diff --git a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java index 149f58f0a69b..c2e6b60cd680 100644 --- a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java +++ b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java @@ -42,8 +42,8 @@ import org.mockito.MockitoAnnotations;  public class DisplayManagerGlobalTest {      private static final long ALL_DISPLAY_EVENTS = DisplayManager.EVENT_FLAG_DISPLAY_ADDED -                    | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED -                    | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED; +            | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED +            | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED;      @Mock      private IDisplayManager mDisplayManager; @@ -69,7 +69,8 @@ public class DisplayManagerGlobalTest {      @Test      public void testDisplayListenerIsCalled_WhenDisplayEventOccurs() throws RemoteException { -        mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, ALL_DISPLAY_EVENTS); +        mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, ALL_DISPLAY_EVENTS, +                null);          Mockito.verify(mDisplayManager)                  .registerCallbackWithEventMask(mCallbackCaptor.capture(), anyLong());          IDisplayManagerCallback callback = mCallbackCaptor.getValue(); @@ -97,26 +98,27 @@ public class DisplayManagerGlobalTest {      public void testDisplayListenerIsNotCalled_WhenClientIsNotSubscribed() throws RemoteException {          // First we subscribe to all events in order to test that the subsequent calls to          // registerDisplayListener will update the event mask. -        mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, ALL_DISPLAY_EVENTS); +        mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, ALL_DISPLAY_EVENTS, +                null);          Mockito.verify(mDisplayManager)                  .registerCallbackWithEventMask(mCallbackCaptor.capture(), anyLong());          IDisplayManagerCallback callback = mCallbackCaptor.getValue();          int displayId = 1;          mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, -                ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_ADDED); +                ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_ADDED, null);          callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED);          waitForHandler();          Mockito.verifyZeroInteractions(mListener);          mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, -                ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_CHANGED); +                ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_CHANGED, null);          callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);          waitForHandler();          Mockito.verifyZeroInteractions(mListener);          mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, -                ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_REMOVED); +                ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_REMOVED, null);          callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);          waitForHandler();          Mockito.verifyZeroInteractions(mListener); @@ -139,7 +141,7 @@ public class DisplayManagerGlobalTest {      public void testDisplayManagerGlobalRegistersWithDisplayManager_WhenThereAreListeners()              throws RemoteException {          mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler, -                DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS); +                DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS, null);          InOrder inOrder = Mockito.inOrder(mDisplayManager);          inOrder.verify(mDisplayManager) @@ -163,6 +165,7 @@ public class DisplayManagerGlobalTest {      }      private void waitForHandler() { -        mHandler.runWithScissors(() -> { }, 0); +        mHandler.runWithScissors(() -> { +        }, 0);      }  } diff --git a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java index 07dec5d9e222..b843ad75ac0f 100644 --- a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java +++ b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java @@ -79,6 +79,8 @@ public class FaceManagerTest {      private FaceManager.AuthenticationCallback mAuthCallback;      @Mock      private FaceManager.EnrollmentCallback mEnrollmentCallback; +    @Mock +    private FaceManager.FaceDetectionCallback mFaceDetectionCallback;      @Captor      private ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback> mCaptor; @@ -191,6 +193,23 @@ public class FaceManagerTest {                  any(), anyString(), any(), any(), anyBoolean());      } +    @Test +    public void detectClient_onError() throws RemoteException { +        ArgumentCaptor<IFaceServiceReceiver> argumentCaptor = +                ArgumentCaptor.forClass(IFaceServiceReceiver.class); + +        CancellationSignal cancellationSignal = new CancellationSignal(); +        mFaceManager.detectFace(cancellationSignal, mFaceDetectionCallback, +                new FaceAuthenticateOptions.Builder().build()); + +        verify(mService).detectFace(any(), argumentCaptor.capture(), any()); + +        argumentCaptor.getValue().onError(5 /* error */, 0 /* vendorCode */); +        mLooper.dispatchAll(); + +        verify(mFaceDetectionCallback).onDetectionError(anyInt()); +    } +      private void initializeProperties() throws RemoteException {          verify(mService).addAuthenticatorsRegisteredCallback(mCaptor.capture()); diff --git a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java index 625e2e3723a7..70313b8c9ea7 100644 --- a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java +++ b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java @@ -74,6 +74,8 @@ public class FingerprintManagerTest {      private FingerprintManager.AuthenticationCallback mAuthCallback;      @Mock      private FingerprintManager.EnrollmentCallback mEnrollCallback; +    @Mock +    private FingerprintManager.FingerprintDetectionCallback mFingerprintDetectionCallback;      @Captor      private ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> mCaptor; @@ -166,4 +168,21 @@ public class FingerprintManagerTest {                  anyString());          verify(mService, never()).enroll(any(), any(), anyInt(), any(), anyString(), anyInt());      } + +    @Test +    public void detectClient_onError() throws RemoteException { +        ArgumentCaptor<IFingerprintServiceReceiver> argumentCaptor = +                ArgumentCaptor.forClass(IFingerprintServiceReceiver.class); + +        mFingerprintManager.detectFingerprint(new CancellationSignal(), +                mFingerprintDetectionCallback, +                new FingerprintAuthenticateOptions.Builder().build()); + +        verify(mService).detectFingerprint(any(), argumentCaptor.capture(), any()); + +        argumentCaptor.getValue().onError(5 /* error */, 0 /* vendorCode */); +        mLooper.dispatchAll(); + +        verify(mFingerprintDetectionCallback).onDetectionError(anyInt()); +    }  } diff --git a/core/tests/coretests/src/android/os/health/SystemHealthManagerTest.java b/core/tests/coretests/src/android/os/health/SystemHealthManagerTest.java deleted file mode 100644 index e1f9523f1b49..000000000000 --- a/core/tests/coretests/src/android/os/health/SystemHealthManagerTest.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - *      http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.os.health; - -import static androidx.test.InstrumentationRegistry.getContext; - -import static com.google.common.truth.Truth.assertThat; - -import android.os.ConditionVariable; -import android.os.PowerMonitor; -import android.os.PowerMonitorReadings; - -import org.junit.Test; - -import java.util.ArrayList; -import java.util.List; - -public class SystemHealthManagerTest { -    private List<PowerMonitor> mPowerMonitorInfo; -    private PowerMonitorReadings mReadings; -    private RuntimeException mException; - -    @Test -    public void getPowerMonitors() { -        SystemHealthManager shm = getContext().getSystemService(SystemHealthManager.class); -        List<PowerMonitor> powerMonitorInfo = shm.getSupportedPowerMonitors(); -        assertThat(powerMonitorInfo).isNotNull(); -        if (powerMonitorInfo.isEmpty()) { -            // This device does not support PowerStats HAL -            return; -        } - -        PowerMonitor consumerMonitor = null; -        PowerMonitor measurementMonitor = null; -        for (PowerMonitor pmi : powerMonitorInfo) { -            if (pmi.type == PowerMonitor.POWER_MONITOR_TYPE_MEASUREMENT) { -                measurementMonitor = pmi; -            } else { -                consumerMonitor = pmi; -            } -        } - -        List<PowerMonitor> selectedMonitors = new ArrayList<>(); -        if (consumerMonitor != null) { -            selectedMonitors.add(consumerMonitor); -        } -        if (measurementMonitor != null) { -            selectedMonitors.add(measurementMonitor); -        } - -        PowerMonitorReadings readings = shm.getPowerMonitorReadings(selectedMonitors); - -        for (PowerMonitor monitor : selectedMonitors) { -            assertThat(readings.getConsumedEnergyUws(monitor)).isAtLeast(0); -            assertThat(readings.getTimestamp(monitor)).isGreaterThan(0); -        } -    } - -    @Test -    public void getPowerMonitorsAsync() { -        SystemHealthManager shm = getContext().getSystemService(SystemHealthManager.class); -        ConditionVariable done = new ConditionVariable(); -        shm.getSupportedPowerMonitors(null, pms -> { -            mPowerMonitorInfo = pms; -            done.open(); -        }); -        done.block(); -        assertThat(mPowerMonitorInfo).isNotNull(); -        if (mPowerMonitorInfo.isEmpty()) { -            // This device does not support PowerStats HAL -            return; -        } - -        PowerMonitor consumerMonitor = null; -        PowerMonitor measurementMonitor = null; -        for (PowerMonitor pmi : mPowerMonitorInfo) { -            if (pmi.type == PowerMonitor.POWER_MONITOR_TYPE_MEASUREMENT) { -                measurementMonitor = pmi; -            } else { -                consumerMonitor = pmi; -            } -        } - -        List<PowerMonitor> selectedMonitors = new ArrayList<>(); -        if (consumerMonitor != null) { -            selectedMonitors.add(consumerMonitor); -        } -        if (measurementMonitor != null) { -            selectedMonitors.add(measurementMonitor); -        } - -        done.close(); -        shm.getPowerMonitorReadings(selectedMonitors, null, -                readings -> { -                    mReadings = readings; -                    done.open(); -                }, -                exception -> { -                    mException = exception; -                    done.open(); -                } -        ); -        done.block(); - -        assertThat(mException).isNull(); - -        for (PowerMonitor monitor : selectedMonitors) { -            assertThat(mReadings.getConsumedEnergyUws(monitor)).isAtLeast(0); -            assertThat(mReadings.getTimestamp(monitor)).isGreaterThan(0); -        } -    } -} diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java index 6a9fc04230f8..1a38decae604 100644 --- a/core/tests/coretests/src/android/view/ViewRootImplTest.java +++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java @@ -17,6 +17,11 @@  package android.view;  import static android.view.accessibility.Flags.FLAG_FORCE_INVERT_COLOR; +import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE; +import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH; +import static android.view.Surface.FRAME_RATE_CATEGORY_LOW; +import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL; +import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;  import static android.view.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;  import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;  import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; @@ -48,8 +53,12 @@ import android.hardware.display.DisplayManagerGlobal;  import android.os.Binder;  import android.os.SystemProperties;  import android.platform.test.annotations.Presubmit; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider;  import android.platform.test.flag.junit.SetFlagsRule;  import android.provider.Settings; +import android.util.DisplayMetrics;  import android.util.Log;  import android.view.WindowInsets.Side;  import android.view.WindowInsets.Type; @@ -97,6 +106,10 @@ public class ViewRootImplTest {      // state after the test completes.      private static boolean sOriginalTouchMode; +    @Rule +    public final CheckFlagsRule mCheckFlagsRule = +            DeviceFlagsValueProvider.createCheckFlagsRule(); +      @BeforeClass      public static void setUpClass() {          sContext = sInstrumentation.getTargetContext(); @@ -427,6 +440,129 @@ public class ViewRootImplTest {          assertThat(result).isFalse();      } +    /** +     * Test the default values are properly set +     */ +    @UiThreadTest +    @Test +    @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE) +    public void votePreferredFrameRate_getDefaultValues() { +        ViewRootImpl viewRootImpl = new ViewRootImpl(sContext, +                sContext.getDisplayNoVerify()); +        assertEquals(viewRootImpl.getPreferredFrameRateCategory(), +                FRAME_RATE_CATEGORY_NO_PREFERENCE); +        assertEquals(viewRootImpl.getPreferredFrameRate(), 0, 0.1); +    } + +    /** +     * Test the value of the frame rate cateogry based on the visibility of a view +     * Invsible: FRAME_RATE_CATEGORY_NO_PREFERENCE +     * Visible: FRAME_RATE_CATEGORY_NORMAL +     */ +    @Test +    @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE) +    public void votePreferredFrameRate_voteFrameRateCategory_visibility() { +        View view = new View(sContext); +        attachViewToWindow(view); +        ViewRootImpl viewRootImpl = view.getViewRootImpl(); +        sInstrumentation.runOnMainSync(() -> { +            view.setVisibility(View.INVISIBLE); +            view.invalidate(); +            assertEquals(viewRootImpl.getPreferredFrameRateCategory(), +                    FRAME_RATE_CATEGORY_NO_PREFERENCE); +        }); + +        sInstrumentation.runOnMainSync(() -> { +            view.setVisibility(View.VISIBLE); +            view.invalidate(); +            assertEquals(viewRootImpl.getPreferredFrameRateCategory(), +                    FRAME_RATE_CATEGORY_NORMAL); +        }); +    } + +    /** +     * Test the value of the frame rate cateogry based on the size of a view. +     * The current threshold value is 7% of the screen size +     * <7%: FRAME_RATE_CATEGORY_LOW +     */ +    @Test +    @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE) +    public void votePreferredFrameRate_voteFrameRateCategory_smallSize() { +        View view = new View(sContext); +        WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY); +        wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check +        wmlp.width = 1; +        wmlp.height = 1; + +        sInstrumentation.runOnMainSync(() -> { +            WindowManager wm = sContext.getSystemService(WindowManager.class); +            wm.addView(view, wmlp); +        }); +        sInstrumentation.waitForIdleSync(); + +        ViewRootImpl viewRootImpl = view.getViewRootImpl(); +        sInstrumentation.runOnMainSync(() -> { +            view.invalidate(); +            assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_LOW); +        }); +    } + +    /** +     * Test the value of the frame rate cateogry based on the size of a view. +     * The current threshold value is 7% of the screen size +     * >=7% : FRAME_RATE_CATEGORY_NORMAL +     */ +    @Test +    @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE) +    public void votePreferredFrameRate_voteFrameRateCategory_normalSize() { +        View view = new View(sContext); +        WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY); +        wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check + +        sInstrumentation.runOnMainSync(() -> { +            WindowManager wm = sContext.getSystemService(WindowManager.class); +            Display display = wm.getDefaultDisplay(); +            DisplayMetrics metrics = new DisplayMetrics(); +            display.getMetrics(metrics); +            wmlp.width = (int) (metrics.widthPixels * 0.9); +            wmlp.height = (int) (metrics.heightPixels * 0.9); +            wm.addView(view, wmlp); +        }); +        sInstrumentation.waitForIdleSync(); + +        ViewRootImpl viewRootImpl = view.getViewRootImpl(); +        sInstrumentation.runOnMainSync(() -> { +            view.invalidate(); +            assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL); +        }); +    } + +    /** +     * Test how values of the frame rate cateogry are aggregated. +     * It should take the max value among all of the voted categories per frame. +     */ +    @Test +    @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE) +    public void votePreferredFrameRate_voteFrameRateCategory_aggregate() { +        View view = new View(sContext); +        attachViewToWindow(view); +        sInstrumentation.runOnMainSync(() -> { +            ViewRootImpl viewRootImpl = view.getViewRootImpl(); +            assertEquals(viewRootImpl.getPreferredFrameRateCategory(), +                    FRAME_RATE_CATEGORY_NO_PREFERENCE); +            viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW); +            assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_LOW); +            viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL); +            assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL); +            viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH); +            assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH); +            viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL); +            assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH); +            viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW); +            assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH); +        }); +    } +      @Test      public void forceInvertOffDarkThemeOff_forceDarkModeDisabled() {          mSetFlagsRule.enableFlags(FLAG_FORCE_INVERT_COLOR); diff --git a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java index e76d266c614c..d47d7891d0e4 100644 --- a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java +++ b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java @@ -115,6 +115,26 @@ public class MainContentCaptureSessionTest {                          new ContentCaptureOptions.ContentProtectionOptions(                                  /* enableReceiver= */ true,                                  -BUFFER_SIZE, +                                /* requiredGroups= */ List.of(List.of("a")), +                                /* optionalGroups= */ Collections.emptyList(), +                                /* optionalGroupsThreshold= */ 0)); +        MainContentCaptureSession session = createSession(options); +        session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor; + +        session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null); + +        assertThat(session.mContentProtectionEventProcessor).isNull(); +        verifyZeroInteractions(mMockContentProtectionEventProcessor); +    } + +    @Test +    public void onSessionStarted_contentProtectionNoGroups_processorNotCreated() { +        ContentCaptureOptions options = +                createOptions( +                        /* enableContentCaptureReceiver= */ true, +                        new ContentCaptureOptions.ContentProtectionOptions( +                                /* enableReceiver= */ true, +                                BUFFER_SIZE,                                  /* requiredGroups= */ Collections.emptyList(),                                  /* optionalGroups= */ Collections.emptyList(),                                  /* optionalGroupsThreshold= */ 0)); @@ -320,7 +340,7 @@ public class MainContentCaptureSessionTest {                  new ContentCaptureOptions.ContentProtectionOptions(                          enableContentProtectionReceiver,                          BUFFER_SIZE, -                        /* requiredGroups= */ Collections.emptyList(), +                        /* requiredGroups= */ List.of(List.of("a")),                          /* optionalGroups= */ Collections.emptyList(),                          /* optionalGroupsThreshold= */ 0));      } diff --git a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java index 39a2e0e048e8..ba0dbf454ad2 100644 --- a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java +++ b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java @@ -29,12 +29,13 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;  import static org.mockito.Mockito.verifyZeroInteractions;  import static org.mockito.Mockito.when; +import android.annotation.NonNull;  import android.annotation.Nullable; +import android.content.ContentCaptureOptions;  import android.content.Context;  import android.content.pm.ParceledListSlice;  import android.os.Handler; -import android.os.Looper; -import android.text.InputType; +import android.os.test.TestLooper;  import android.view.View;  import android.view.contentcapture.ContentCaptureEvent;  import android.view.contentcapture.IContentCaptureManager; @@ -57,6 +58,7 @@ import org.mockito.ArgumentCaptor;  import org.mockito.Mock;  import org.mockito.junit.MockitoJUnit;  import org.mockito.junit.MockitoRule; +import org.mockito.verification.VerificationMode;  import java.time.Instant;  import java.util.ArrayList; @@ -75,13 +77,25 @@ public class ContentProtectionEventProcessorTest {      private static final String PACKAGE_NAME = "com.test.package.name"; -    private static final String ANDROID_CLASS_NAME = "android.test.some.class.name"; +    private static final String TEXT_REQUIRED1 = "TEXT REQUIRED1 TEXT"; -    private static final String PASSWORD_TEXT = "ENTER PASSWORD HERE"; +    private static final String TEXT_REQUIRED2 = "TEXT REQUIRED2 TEXT"; -    private static final String SUSPICIOUS_TEXT = "PLEASE SIGN IN"; +    private static final String TEXT_OPTIONAL1 = "TEXT OPTIONAL1 TEXT"; -    private static final String SAFE_TEXT = "SAFE TEXT"; +    private static final String TEXT_OPTIONAL2 = "TEXT OPTIONAL2 TEXT"; + +    private static final String TEXT_CONTAINS_OPTIONAL3 = "TEXTOPTIONAL3TEXT"; + +    private static final String TEXT_SHARED = "TEXT SHARED TEXT"; + +    private static final String TEXT_SAFE = "TEXT SAFE TEXT"; + +    private static final List<List<String>> REQUIRED_GROUPS = +            List.of(List.of("required1", "missing"), List.of("required2", "shared")); + +    private static final List<List<String>> OPTIONAL_GROUPS = +            List.of(List.of("optional1"), List.of("optional2", "optional3"), List.of("shared"));      private static final ContentCaptureEvent PROCESS_EVENT = createProcessEvent(); @@ -91,7 +105,17 @@ public class ContentProtectionEventProcessorTest {      private static final Set<Integer> EVENT_TYPES_TO_STORE =              ImmutableSet.of(TYPE_VIEW_APPEARED, TYPE_VIEW_DISAPPEARED, TYPE_VIEW_TEXT_CHANGED); -    private static final int RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS = 150; +    private static final int BUFFER_SIZE = 150; + +    private static final int OPTIONAL_GROUPS_THRESHOLD = 1; + +    private static final ContentCaptureOptions.ContentProtectionOptions OPTIONS = +            new ContentCaptureOptions.ContentProtectionOptions( +                    /* enableReceiver= */ true, +                    BUFFER_SIZE, +                    REQUIRED_GROUPS, +                    OPTIONAL_GROUPS, +                    OPTIONAL_GROUPS_THRESHOLD);      @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); @@ -101,16 +125,19 @@ public class ContentProtectionEventProcessorTest {      private final Context mContext = ApplicationProvider.getApplicationContext(); -    private ContentProtectionEventProcessor mContentProtectionEventProcessor; +    private final TestLooper mTestLooper = new TestLooper(); + +    @NonNull private ContentProtectionEventProcessor mContentProtectionEventProcessor;      @Before      public void setup() {          mContentProtectionEventProcessor =                  new ContentProtectionEventProcessor(                          mMockEventBuffer, -                        new Handler(Looper.getMainLooper()), +                        new Handler(mTestLooper.getLooper()),                          mMockContentCaptureManager, -                        PACKAGE_NAME); +                        PACKAGE_NAME, +                        OPTIONS);      }      @Test @@ -156,347 +183,224 @@ public class ContentProtectionEventProcessorTest {      }      @Test -    public void processEvent_loginDetected_inspectsOnlyTypeViewAppeared() { -        mContentProtectionEventProcessor.mPasswordFieldDetected = true; -        mContentProtectionEventProcessor.mSuspiciousTextDetected = true; - -        for (int type = -100; type <= 100; type++) { -            if (type == TYPE_VIEW_APPEARED) { -                continue; -            } - -            mContentProtectionEventProcessor.processEvent(createEvent(type)); - -            assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue(); -            assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue(); -        } - -        verify(mMockEventBuffer, never()).clear(); -        verify(mMockEventBuffer, never()).toArray(); -        verifyZeroInteractions(mMockContentCaptureManager); -    } - -    @Test -    public void processEvent_loginDetected() throws Exception { +    public void processEvent_loginDetected_true_eventText() throws Exception {          when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); -        mContentProtectionEventProcessor.mPasswordFieldDetected = true; -        mContentProtectionEventProcessor.mSuspiciousTextDetected = true; -        mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); - -        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); -        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); -        verify(mMockEventBuffer).clear(); -        verify(mMockEventBuffer).toArray(); -        assertOnLoginDetected(); -    } - -    @Test -    public void processEvent_loginDetected_passwordFieldNotDetected() { -        mContentProtectionEventProcessor.mSuspiciousTextDetected = true; -        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); - -        mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); - -        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); -        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue(); -        verify(mMockEventBuffer, never()).clear(); -        verify(mMockEventBuffer, never()).toArray(); -        verifyZeroInteractions(mMockContentCaptureManager); -    } - -    @Test -    public void processEvent_loginDetected_suspiciousTextNotDetected() { -        mContentProtectionEventProcessor.mPasswordFieldDetected = true; -        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); - -        mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); - -        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue(); -        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); -        verify(mMockEventBuffer, never()).clear(); -        verify(mMockEventBuffer, never()).toArray(); -        verifyZeroInteractions(mMockContentCaptureManager); -    } - -    @Test -    public void processEvent_loginDetected_withoutViewNode() { -        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); -        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); - -        mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); +        mContentProtectionEventProcessor.processEvent( +                createProcessEvent( +                        /* eventText= */ TEXT_REQUIRED1, +                        /* viewNodeText= */ null, +                        /* hintText= */ null)); +        mContentProtectionEventProcessor.processEvent( +                createProcessEvent( +                        /* eventText= */ TEXT_REQUIRED2, +                        /* viewNodeText= */ null, +                        /* hintText= */ null)); +        mContentProtectionEventProcessor.processEvent( +                createProcessEvent( +                        /* eventText= */ TEXT_OPTIONAL1, +                        /* viewNodeText= */ null, +                        /* hintText= */ null)); -        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); -        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); -        verify(mMockEventBuffer, never()).clear(); -        verify(mMockEventBuffer, never()).toArray(); -        verifyZeroInteractions(mMockContentCaptureManager); +        assertLoginDetected();      }      @Test -    public void processEvent_loginDetected_belowResetLimit() throws Exception { +    public void processEvent_loginDetected_true_viewNodeText() throws Exception {          when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); -        mContentProtectionEventProcessor.mSuspiciousTextDetected = true; -        ContentCaptureEvent event = -                createAndroidPasswordFieldEvent( -                        ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_PASSWORD); - -        for (int i = 0; i < RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS; i++) { -            mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); -        } -        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); -        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue(); -        verify(mMockEventBuffer, never()).clear(); -        verify(mMockEventBuffer, never()).toArray(); - -        mContentProtectionEventProcessor.processEvent(event); +        mContentProtectionEventProcessor.processEvent( +                createProcessEvent( +                        /* eventText= */ null, +                        /* viewNodeText= */ TEXT_REQUIRED1, +                        /* hintText= */ null)); +        mContentProtectionEventProcessor.processEvent( +                createProcessEvent( +                        /* eventText= */ null, +                        /* viewNodeText= */ TEXT_REQUIRED2, +                        /* hintText= */ null)); +        mContentProtectionEventProcessor.processEvent( +                createProcessEvent( +                        /* eventText= */ null, +                        /* viewNodeText= */ TEXT_OPTIONAL1, +                        /* hintText= */ null)); -        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); -        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); -        verify(mMockEventBuffer).clear(); -        verify(mMockEventBuffer).toArray(); -        assertOnLoginDetected(); +        assertLoginDetected();      }      @Test -    public void processEvent_loginDetected_aboveResetLimit() throws Exception { -        mContentProtectionEventProcessor.mSuspiciousTextDetected = true; -        ContentCaptureEvent event = -                createAndroidPasswordFieldEvent( -                        ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_PASSWORD); - -        for (int i = 0; i < RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS + 1; i++) { -            mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); -        } - -        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); -        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); -        verify(mMockEventBuffer, never()).clear(); -        verify(mMockEventBuffer, never()).toArray(); +    public void processEvent_loginDetected_true_hintText() throws Exception { +        when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); -        mContentProtectionEventProcessor.processEvent(event); +        mContentProtectionEventProcessor.processEvent( +                createProcessEvent( +                        /* eventText= */ null, +                        /* viewNodeText= */ null, +                        /* hintText= */ TEXT_REQUIRED1)); +        mContentProtectionEventProcessor.processEvent( +                createProcessEvent( +                        /* eventText= */ null, +                        /* viewNodeText= */ null, +                        /* hintText= */ TEXT_REQUIRED2)); +        mContentProtectionEventProcessor.processEvent( +                createProcessEvent( +                        /* eventText= */ null, +                        /* viewNodeText= */ null, +                        /* hintText= */ TEXT_OPTIONAL1)); -        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue(); -        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); -        verify(mMockEventBuffer, never()).clear(); -        verify(mMockEventBuffer, never()).toArray(); +        assertLoginDetected();      }      @Test -    public void processEvent_multipleLoginsDetected_belowFlushThreshold() throws Exception { +    public void processEvent_loginDetected_true_differentOptionalGroup() throws Exception {          when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); -        mContentProtectionEventProcessor.mPasswordFieldDetected = true; -        mContentProtectionEventProcessor.mSuspiciousTextDetected = true; -        mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); - -        mContentProtectionEventProcessor.mPasswordFieldDetected = true; -        mContentProtectionEventProcessor.mSuspiciousTextDetected = true; -        mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); +        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); +        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2)); +        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL2)); -        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); -        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); -        verify(mMockEventBuffer).clear(); -        verify(mMockEventBuffer).toArray(); -        assertOnLoginDetected(); +        assertLoginDetected();      }      @Test -    public void processEvent_multipleLoginsDetected_aboveFlushThreshold() throws Exception { +    public void processEvent_loginDetected_true_usesContains() throws Exception {          when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); -        mContentProtectionEventProcessor.mPasswordFieldDetected = true; -        mContentProtectionEventProcessor.mSuspiciousTextDetected = true; -        mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); - -        mContentProtectionEventProcessor.mLastFlushTime = Instant.now().minusSeconds(5); +        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); +        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2)); +        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_CONTAINS_OPTIONAL3)); -        mContentProtectionEventProcessor.mPasswordFieldDetected = true; -        mContentProtectionEventProcessor.mSuspiciousTextDetected = true; -        mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); - -        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); -        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); -        verify(mMockEventBuffer, times(2)).clear(); -        verify(mMockEventBuffer, times(2)).toArray(); -        assertOnLoginDetected(PROCESS_EVENT, /* times= */ 2); +        assertLoginDetected();      }      @Test -    public void isPasswordField_android() { -        ContentCaptureEvent event = -                createAndroidPasswordFieldEvent( -                        ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_PASSWORD); - -        mContentProtectionEventProcessor.processEvent(event); +    public void processEvent_loginDetected_false_missingRequiredGroups() { +        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); +        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1)); -        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue(); -        verify(mMockEventBuffer, never()).clear(); -        verify(mMockEventBuffer, never()).toArray(); -        verifyZeroInteractions(mMockContentCaptureManager); +        assertLoginNotDetected();      }      @Test -    public void isPasswordField_android_withoutClassName() { -        ContentCaptureEvent event = -                createAndroidPasswordFieldEvent( -                        /* className= */ null, InputType.TYPE_TEXT_VARIATION_PASSWORD); - -        mContentProtectionEventProcessor.processEvent(event); +    public void processEvent_loginDetected_false_missingOptionalGroups() { +        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); +        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2)); -        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); -        verify(mMockEventBuffer, never()).clear(); -        verify(mMockEventBuffer, never()).toArray(); -        verifyZeroInteractions(mMockContentCaptureManager); +        assertLoginNotDetected();      }      @Test -    public void isPasswordField_android_wrongClassName() { -        ContentCaptureEvent event = -                createAndroidPasswordFieldEvent( -                        "wrong.prefix" + ANDROID_CLASS_NAME, -                        InputType.TYPE_TEXT_VARIATION_PASSWORD); +    public void processEvent_loginDetected_false_safeText() { +        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); +        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2)); +        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SAFE)); +        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SAFE)); +        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SAFE)); -        mContentProtectionEventProcessor.processEvent(event); - -        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); -        verify(mMockEventBuffer, never()).clear(); -        verify(mMockEventBuffer, never()).toArray(); -        verifyZeroInteractions(mMockContentCaptureManager); +        assertLoginNotDetected();      }      @Test -    public void isPasswordField_android_wrongInputType() { -        ContentCaptureEvent event = -                createAndroidPasswordFieldEvent( -                        ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_NORMAL); +    public void processEvent_loginDetected_false_sharedTextOnce() { +        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); +        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SHARED)); -        mContentProtectionEventProcessor.processEvent(event); - -        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); -        verify(mMockEventBuffer, never()).clear(); -        verify(mMockEventBuffer, never()).toArray(); -        verifyZeroInteractions(mMockContentCaptureManager); +        assertLoginNotDetected();      }      @Test -    public void isPasswordField_webView() throws Exception { -        ContentCaptureEvent event = -                createWebViewPasswordFieldEvent( -                        /* className= */ null, /* eventText= */ null, PASSWORD_TEXT); -        when(mMockEventBuffer.toArray()).thenReturn(new ContentCaptureEvent[] {event}); +    public void processEvent_loginDetected_true_sharedTextMultiple() throws Exception { +        when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); -        mContentProtectionEventProcessor.processEvent(event); +        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); +        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SHARED)); +        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SHARED)); -        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); -        verify(mMockEventBuffer).clear(); -        verify(mMockEventBuffer).toArray(); -        assertOnLoginDetected(event, /* times= */ 1); +        assertLoginDetected();      }      @Test -    public void isPasswordField_webView_withClassName() { -        ContentCaptureEvent event = -                createWebViewPasswordFieldEvent( -                        /* className= */ "any.class.name", /* eventText= */ null, PASSWORD_TEXT); +    public void processEvent_loginDetected_false_inspectsOnlyTypeViewAppeared() { +        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); +        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2)); -        mContentProtectionEventProcessor.processEvent(event); +        for (int type = -100; type <= 100; type++) { +            if (type == TYPE_VIEW_APPEARED) { +                continue; +            } +            ContentCaptureEvent event = createEvent(type); +            event.setText(TEXT_OPTIONAL1); +            mContentProtectionEventProcessor.processEvent(event); +        } -        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); -        verify(mMockEventBuffer, never()).clear(); -        verify(mMockEventBuffer, never()).toArray(); -        verifyZeroInteractions(mMockContentCaptureManager); +        assertLoginNotDetected();      }      @Test -    public void isPasswordField_webView_withSafeViewNodeText() { -        ContentCaptureEvent event = -                createWebViewPasswordFieldEvent( -                        /* className= */ null, /* eventText= */ null, SAFE_TEXT); +    public void processEvent_loginDetected_true_belowResetLimit() throws Exception { +        when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); -        mContentProtectionEventProcessor.processEvent(event); +        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); +        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2)); -        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); -        verify(mMockEventBuffer, never()).clear(); -        verify(mMockEventBuffer, never()).toArray(); -        verifyZeroInteractions(mMockContentCaptureManager); -    } +        for (int i = 0; i < BUFFER_SIZE - 2; i++) { +            mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); +        } -    @Test -    public void isPasswordField_webView_withEventText() { -        ContentCaptureEvent event = -                createWebViewPasswordFieldEvent(/* className= */ null, PASSWORD_TEXT, SAFE_TEXT); +        assertLoginNotDetected(); -        mContentProtectionEventProcessor.processEvent(event); +        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1)); -        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse(); -        verify(mMockEventBuffer, never()).clear(); -        verify(mMockEventBuffer, never()).toArray(); -        verifyZeroInteractions(mMockContentCaptureManager); +        assertLoginDetected();      }      @Test -    public void isSuspiciousText_withSafeText() { -        ContentCaptureEvent event = createSuspiciousTextEvent(SAFE_TEXT, SAFE_TEXT); +    public void processEvent_loginDetected_false_aboveResetLimit() { +        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); +        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2)); -        mContentProtectionEventProcessor.processEvent(event); - -        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse(); -        verify(mMockEventBuffer, never()).clear(); -        verify(mMockEventBuffer, never()).toArray(); -        verifyZeroInteractions(mMockContentCaptureManager); -    } +        for (int i = 0; i < BUFFER_SIZE - 1; i++) { +            mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); +        } -    @Test -    public void isSuspiciousText_eventText_suspiciousText() { -        ContentCaptureEvent event = createSuspiciousTextEvent(SUSPICIOUS_TEXT, SAFE_TEXT); +        assertLoginNotDetected(); -        mContentProtectionEventProcessor.processEvent(event); +        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1)); -        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue(); -        verify(mMockEventBuffer, never()).clear(); -        verify(mMockEventBuffer, never()).toArray(); -        verifyZeroInteractions(mMockContentCaptureManager); +        assertLoginNotDetected();      }      @Test -    public void isSuspiciousText_viewNodeText_suspiciousText() { -        ContentCaptureEvent event = createSuspiciousTextEvent(SAFE_TEXT, SUSPICIOUS_TEXT); +    public void processEvent_multipleLoginsDetected_belowFlushThreshold() throws Exception { +        when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); -        mContentProtectionEventProcessor.processEvent(event); +        for (int i = 0; i < 2; i++) { +            mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); +            mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2)); +            mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1)); +            mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); +        } -        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue(); -        verify(mMockEventBuffer, never()).clear(); -        verify(mMockEventBuffer, never()).toArray(); -        verifyZeroInteractions(mMockContentCaptureManager); +        assertLoginDetected();      }      @Test -    public void isSuspiciousText_eventText_passwordText() { -        ContentCaptureEvent event = createSuspiciousTextEvent(PASSWORD_TEXT, SAFE_TEXT); - -        mContentProtectionEventProcessor.processEvent(event); +    public void processEvent_multipleLoginsDetected_aboveFlushThreshold() throws Exception { +        when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS); -        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue(); -        verify(mMockEventBuffer, never()).clear(); -        verify(mMockEventBuffer, never()).toArray(); -        verifyZeroInteractions(mMockContentCaptureManager); -    } +        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); +        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2)); +        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1)); +        mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); -    @Test -    public void isSuspiciousText_viewNodeText_passwordText() { -        // Specify the class to differ from {@link isPasswordField_webView} test in this version -        ContentCaptureEvent event = -                createProcessEvent( -                        "test.class.not.a.web.view", /* inputType= */ 0, SAFE_TEXT, PASSWORD_TEXT); +        mContentProtectionEventProcessor.mLastFlushTime = Instant.now().minusSeconds(5); -        mContentProtectionEventProcessor.processEvent(event); +        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1)); +        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2)); +        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1)); +        mContentProtectionEventProcessor.processEvent(PROCESS_EVENT); -        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue(); -        verify(mMockEventBuffer, never()).clear(); -        verify(mMockEventBuffer, never()).toArray(); -        verifyZeroInteractions(mMockContentCaptureManager); +        assertLoginDetected(times(2));      }      private static ContentCaptureEvent createEvent(int type) { @@ -511,20 +415,20 @@ public class ContentProtectionEventProcessorTest {          return createEvent(TYPE_VIEW_APPEARED);      } +    private ContentCaptureEvent createProcessEvent(@Nullable String eventText) { +        return createProcessEvent(eventText, /* viewNodeText= */ null, /* hintText= */ null); +    } +      private ContentCaptureEvent createProcessEvent( -            @Nullable String className, -            int inputType, -            @Nullable String eventText, -            @Nullable String viewNodeText) { +            @Nullable String eventText, @Nullable String viewNodeText, @Nullable String hintText) {          View view = new View(mContext);          ViewStructureImpl viewStructure = new ViewStructureImpl(view); -        if (className != null) { -            viewStructure.setClassName(className); -        }          if (viewNodeText != null) {              viewStructure.setText(viewNodeText);          } -        viewStructure.setInputType(inputType); +        if (hintText != null) { +            viewStructure.setHint(hintText); +        }          ContentCaptureEvent event = createProcessEvent();          event.setViewNode(viewStructure.getNode()); @@ -535,34 +439,28 @@ public class ContentProtectionEventProcessorTest {          return event;      } -    private ContentCaptureEvent createAndroidPasswordFieldEvent( -            @Nullable String className, int inputType) { -        return createProcessEvent( -                className, inputType, /* eventText= */ null, /* viewNodeText= */ null); -    } - -    private ContentCaptureEvent createWebViewPasswordFieldEvent( -            @Nullable String className, @Nullable String eventText, @Nullable String viewNodeText) { -        return createProcessEvent(className, /* inputType= */ 0, eventText, viewNodeText); +    private void assertLoginNotDetected() { +        mTestLooper.dispatchAll(); +        verify(mMockEventBuffer, never()).clear(); +        verify(mMockEventBuffer, never()).toArray(); +        verifyZeroInteractions(mMockContentCaptureManager);      } -    private ContentCaptureEvent createSuspiciousTextEvent( -            @Nullable String eventText, @Nullable String viewNodeText) { -        return createProcessEvent( -                /* className= */ null, /* inputType= */ 0, eventText, viewNodeText); +    private void assertLoginDetected() throws Exception { +        assertLoginDetected(times(1));      } -    private void assertOnLoginDetected() throws Exception { -        assertOnLoginDetected(PROCESS_EVENT, /* times= */ 1); -    } +    private void assertLoginDetected(@NonNull VerificationMode verificationMode) throws Exception { +        mTestLooper.dispatchAll(); +        verify(mMockEventBuffer, verificationMode).clear(); +        verify(mMockEventBuffer, verificationMode).toArray(); -    private void assertOnLoginDetected(ContentCaptureEvent event, int times) throws Exception {          ArgumentCaptor<ParceledListSlice> captor = ArgumentCaptor.forClass(ParceledListSlice.class); -        verify(mMockContentCaptureManager, times(times)).onLoginDetected(captor.capture()); +        verify(mMockContentCaptureManager, verificationMode).onLoginDetected(captor.capture());          assertThat(captor.getValue()).isNotNull();          List<ContentCaptureEvent> actual = captor.getValue().getList();          assertThat(actual).isNotNull(); -        assertThat(actual).containsExactly(event); +        assertThat(actual).containsExactly(PROCESS_EVENT);      }  } diff --git a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionUtilsTest.java b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionUtilsTest.java index 1459799adee5..fbe478e31888 100644 --- a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionUtilsTest.java +++ b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionUtilsTest.java @@ -1,5 +1,5 @@  /* - * Copyright (C) 2013 The Android Open Source Project + * Copyright (C) 2023 The Android Open Source Project   *   * Licensed under the Apache License, Version 2.0 (the "License");   * you may not use this file except in compliance with the License. @@ -44,68 +44,74 @@ public class ContentProtectionUtilsTest {      private static final String TEXT = "TEST_TEXT"; -    private static final ContentCaptureEvent EVENT = createEvent(); - -    private static final ViewNode VIEW_NODE = new ViewNode(); - -    private static final ViewNode VIEW_NODE_WITH_TEXT = createViewNodeWithText(); +    private static final String TEXT_LOWER = TEXT.toLowerCase();      @Test -    public void event_getEventText_null() { -        String actual = ContentProtectionUtils.getEventText(EVENT); +    public void getEventTextLower_null() { +        String actual = ContentProtectionUtils.getEventTextLower(createEvent());          assertThat(actual).isNull();      }      @Test -    public void event_getEventText_notNull() { -        ContentCaptureEvent event = createEvent(); -        event.setText(TEXT); - -        String actual = ContentProtectionUtils.getEventText(event); +    public void getEventTextLower_notNull() { +        String actual = ContentProtectionUtils.getEventTextLower(createEventWithText()); -        assertThat(actual).isEqualTo(TEXT); +        assertThat(actual).isEqualTo(TEXT_LOWER);      }      @Test -    public void event_getViewNodeText_null() { -        String actual = ContentProtectionUtils.getViewNodeText(EVENT); +    public void getViewNodeTextLower_null() { +        String actual = ContentProtectionUtils.getViewNodeTextLower(new ViewNode());          assertThat(actual).isNull();      }      @Test -    public void event_getViewNodeText_notNull() { -        ContentCaptureEvent event = createEvent(); -        event.setViewNode(VIEW_NODE_WITH_TEXT); - -        String actual = ContentProtectionUtils.getViewNodeText(event); +    public void getViewNodeTextLower_notNull() { +        String actual = ContentProtectionUtils.getViewNodeTextLower(createViewNodeWithText()); -        assertThat(actual).isEqualTo(TEXT); +        assertThat(actual).isEqualTo(TEXT_LOWER);      }      @Test -    public void viewNode_getViewNodeText_null() { -        String actual = ContentProtectionUtils.getViewNodeText(VIEW_NODE); +    public void getHintTextLower_null() { +        String actual = ContentProtectionUtils.getHintTextLower(new ViewNode());          assertThat(actual).isNull();      }      @Test -    public void viewNode_getViewNodeText_notNull() { -        String actual = ContentProtectionUtils.getViewNodeText(VIEW_NODE_WITH_TEXT); +    public void getHintTextLower_notNull() { +        String actual = ContentProtectionUtils.getHintTextLower(createViewNodeWithHint()); -        assertThat(actual).isEqualTo(TEXT); +        assertThat(actual).isEqualTo(TEXT_LOWER);      }      private static ContentCaptureEvent createEvent() {          return new ContentCaptureEvent(/* sessionId= */ 123, TYPE_SESSION_STARTED);      } -    private static ViewNode createViewNodeWithText() { +    private static ContentCaptureEvent createEventWithText() { +        ContentCaptureEvent event = createEvent(); +        event.setText(TEXT); +        return event; +    } + +    private static ViewStructureImpl createViewStructureImpl() {          View view = new View(ApplicationProvider.getApplicationContext()); -        ViewStructureImpl viewStructure = new ViewStructureImpl(view); -        viewStructure.setText(TEXT); -        return viewStructure.getNode(); +        return new ViewStructureImpl(view); +    } + +    private static ViewNode createViewNodeWithText() { +        ViewStructureImpl viewStructureImpl = createViewStructureImpl(); +        viewStructureImpl.setText(TEXT); +        return viewStructureImpl.getNode(); +    } + +    private static ViewNode createViewNodeWithHint() { +        ViewStructureImpl viewStructureImpl = createViewStructureImpl(); +        viewStructureImpl.setHint(TEXT); +        return viewStructureImpl.getNode();      }  } diff --git a/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java b/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java index 86f26e59e370..df212ebe1744 100644 --- a/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java +++ b/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java @@ -17,7 +17,9 @@  package android.widget;  import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import android.content.Context;  import android.platform.test.annotations.Presubmit;  import android.util.PollingCheck; @@ -32,6 +34,9 @@ import org.junit.Rule;  import org.junit.Test;  import org.junit.runner.RunWith; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +  @RunWith(AndroidJUnit4.class)  @MediumTest  @Presubmit @@ -49,23 +54,43 @@ public class HorizontalScrollViewFunctionalTest {      }      @Test -    public void testScrollAfterFlingTop() { -        mHorizontalScrollView.scrollTo(100, 0); -        mHorizontalScrollView.fling(-10000); -        PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowLeft.getDistance() > 0); -        PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowLeft.getDistance() == 0f); +    public void testScrollAfterFlingLeft() throws Throwable { +        WatchedEdgeEffect edgeEffect = new WatchedEdgeEffect(mActivity); +        mHorizontalScrollView.mEdgeGlowLeft = edgeEffect; +        mActivityRule.runOnUiThread(() -> mHorizontalScrollView.scrollTo(100, 0)); +        mActivityRule.runOnUiThread(() -> mHorizontalScrollView.fling(-10000)); +        assertTrue(edgeEffect.onAbsorbLatch.await(1, TimeUnit.SECONDS)); +        mActivityRule.runOnUiThread(() -> {}); // let the absorb takes effect -- least one frame +        PollingCheck.waitFor(() -> edgeEffect.getDistance() == 0f);          assertEquals(0, mHorizontalScrollView.getScrollX());      }      @Test -    public void testScrollAfterFlingBottom() { +    public void testScrollAfterFlingRight() throws Throwable { +        WatchedEdgeEffect edgeEffect = new WatchedEdgeEffect(mActivity); +        mHorizontalScrollView.mEdgeGlowRight = edgeEffect;          int childWidth = mHorizontalScrollView.getChildAt(0).getWidth();          int maxScroll = childWidth - mHorizontalScrollView.getWidth(); -        mHorizontalScrollView.scrollTo(maxScroll - 100, 0); -        mHorizontalScrollView.fling(10000); -        PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowRight.getDistance() > 0); +        mActivityRule.runOnUiThread(() -> mHorizontalScrollView.scrollTo(maxScroll - 100, 0)); +        mActivityRule.runOnUiThread(() -> mHorizontalScrollView.fling(10000)); +        assertTrue(edgeEffect.onAbsorbLatch.await(1, TimeUnit.SECONDS)); +        mActivityRule.runOnUiThread(() -> {}); // let the absorb takes effect -- at least one frame          PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowRight.getDistance() == 0f);          assertEquals(maxScroll, mHorizontalScrollView.getScrollX());      } + +    static class WatchedEdgeEffect extends EdgeEffect { +        public CountDownLatch onAbsorbLatch = new CountDownLatch(1); + +        WatchedEdgeEffect(Context context) { +            super(context); +        } + +        @Override +        public void onAbsorb(int velocity) { +            super.onAbsorb(velocity); +            onAbsorbLatch.countDown(); +        } +    }  } diff --git a/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java b/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java index 263e563bc224..6229530dc33f 100644 --- a/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java +++ b/core/tests/coretests/src/android/window/SystemPerformanceHinterTests.java @@ -31,6 +31,7 @@ import static org.junit.Assert.assertEquals;  import static org.junit.Assert.assertThrows;  import static org.mockito.ArgumentMatchers.any;  import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyBoolean;  import static org.mockito.ArgumentMatchers.eq;  import static org.mockito.Mockito.never;  import static org.mockito.Mockito.reset; @@ -154,7 +155,8 @@ public class SystemPerformanceHinterTests {                  eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN));          verify(mTransaction).setFrameRateCategory(                  eq(mDefaultDisplayRoot), -                eq(FRAME_RATE_CATEGORY_HIGH)); +                eq(FRAME_RATE_CATEGORY_HIGH), +                eq(false));          verify(mTransaction).applyAsyncUnsafe();      } @@ -171,7 +173,8 @@ public class SystemPerformanceHinterTests {                  eq(FRAME_RATE_SELECTION_STRATEGY_SELF));          verify(mTransaction).setFrameRateCategory(                  eq(mDefaultDisplayRoot), -                eq(FRAME_RATE_CATEGORY_DEFAULT)); +                eq(FRAME_RATE_CATEGORY_DEFAULT), +                eq(false));          verify(mTransaction).applyAsyncUnsafe();      } @@ -241,7 +244,8 @@ public class SystemPerformanceHinterTests {                  eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN));          verify(mTransaction).setFrameRateCategory(                  eq(mDefaultDisplayRoot), -                eq(FRAME_RATE_CATEGORY_HIGH)); +                eq(FRAME_RATE_CATEGORY_HIGH), +                eq(false));          verify(mTransaction).setEarlyWakeupStart();          verify(mTransaction).applyAsyncUnsafe();          verify(mAdpfSession).sendHint(eq(CPU_LOAD_UP)); @@ -261,7 +265,8 @@ public class SystemPerformanceHinterTests {                  eq(FRAME_RATE_SELECTION_STRATEGY_SELF));          verify(mTransaction).setFrameRateCategory(                  eq(mDefaultDisplayRoot), -                eq(FRAME_RATE_CATEGORY_DEFAULT)); +                eq(FRAME_RATE_CATEGORY_DEFAULT), +                eq(false));          verify(mTransaction).setEarlyWakeupEnd();          verify(mTransaction).applyAsyncUnsafe();          verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET)); @@ -281,7 +286,8 @@ public class SystemPerformanceHinterTests {                      eq(FRAME_RATE_SELECTION_STRATEGY_SELF));              verify(mTransaction).setFrameRateCategory(                      eq(mDefaultDisplayRoot), -                    eq(FRAME_RATE_CATEGORY_DEFAULT)); +                    eq(FRAME_RATE_CATEGORY_DEFAULT), +                    eq(false));              verify(mTransaction).setEarlyWakeupEnd();              verify(mTransaction).applyAsyncUnsafe();              verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET)); @@ -299,7 +305,8 @@ public class SystemPerformanceHinterTests {                  eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN));          verify(mTransaction).setFrameRateCategory(                  eq(mDefaultDisplayRoot), -                eq(FRAME_RATE_CATEGORY_HIGH)); +                eq(FRAME_RATE_CATEGORY_HIGH), +                eq(false));          verify(mTransaction).setEarlyWakeupStart();          verify(mTransaction).applyAsyncUnsafe();          verify(mAdpfSession).sendHint(eq(CPU_LOAD_UP)); @@ -310,7 +317,7 @@ public class SystemPerformanceHinterTests {                  mHinter.startSession(HINT_ALL, DEFAULT_DISPLAY_ID, TEST_OTHER_REASON);          // Verify we never call SF and perf manager since session1 is already running          verify(mTransaction, never()).setFrameRateSelectionStrategy(any(), anyInt()); -        verify(mTransaction, never()).setFrameRateCategory(any(), anyInt()); +        verify(mTransaction, never()).setFrameRateCategory(any(), anyInt(), anyBoolean());          verify(mTransaction, never()).setEarlyWakeupEnd();          verify(mTransaction, never()).applyAsyncUnsafe();          verify(mAdpfSession, never()).sendHint(anyInt()); @@ -318,7 +325,7 @@ public class SystemPerformanceHinterTests {          session2.close();          // Verify we have not cleaned up because session1 is still running          verify(mTransaction, never()).setFrameRateSelectionStrategy(any(), anyInt()); -        verify(mTransaction, never()).setFrameRateCategory(any(), anyInt()); +        verify(mTransaction, never()).setFrameRateCategory(any(), anyInt(), anyBoolean());          verify(mTransaction, never()).setEarlyWakeupEnd();          verify(mTransaction, never()).applyAsyncUnsafe();          verify(mAdpfSession, never()).sendHint(anyInt()); @@ -330,7 +337,8 @@ public class SystemPerformanceHinterTests {                  eq(FRAME_RATE_SELECTION_STRATEGY_SELF));          verify(mTransaction).setFrameRateCategory(                  eq(mDefaultDisplayRoot), -                eq(FRAME_RATE_CATEGORY_DEFAULT)); +                eq(FRAME_RATE_CATEGORY_DEFAULT), +                eq(false));          verify(mTransaction).setEarlyWakeupEnd();          verify(mTransaction).applyAsyncUnsafe();          verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET)); @@ -348,7 +356,8 @@ public class SystemPerformanceHinterTests {                  eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN));          verify(mTransaction).setFrameRateCategory(                  eq(mDefaultDisplayRoot), -                eq(FRAME_RATE_CATEGORY_HIGH)); +                eq(FRAME_RATE_CATEGORY_HIGH), +                eq(false));          verify(mTransaction).setEarlyWakeupStart();          verify(mTransaction).applyAsyncUnsafe();          verify(mAdpfSession).sendHint(eq(CPU_LOAD_UP)); @@ -363,7 +372,8 @@ public class SystemPerformanceHinterTests {                  eq(FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN));          verify(mTransaction).setFrameRateCategory(                  eq(mSecondaryDisplayRoot), -                eq(FRAME_RATE_CATEGORY_HIGH)); +                eq(FRAME_RATE_CATEGORY_HIGH), +                eq(false));          verify(mTransaction, never()).setEarlyWakeupStart();          verify(mTransaction).applyAsyncUnsafe();          verify(mAdpfSession, never()).sendHint(anyInt()); @@ -378,13 +388,15 @@ public class SystemPerformanceHinterTests {                  eq(FRAME_RATE_SELECTION_STRATEGY_SELF));          verify(mTransaction).setFrameRateCategory(                  eq(mDefaultDisplayRoot), -                eq(FRAME_RATE_CATEGORY_DEFAULT)); +                eq(FRAME_RATE_CATEGORY_DEFAULT), +                eq(false));          verify(mTransaction, never()).setFrameRateSelectionStrategy(                  eq(mSecondaryDisplayRoot),                  anyInt());          verify(mTransaction, never()).setFrameRateCategory(                  eq(mSecondaryDisplayRoot), -                anyInt()); +                anyInt(), +                eq(false));          verify(mTransaction, never()).setEarlyWakeupEnd();          verify(mTransaction).applyAsyncUnsafe();          verify(mAdpfSession, never()).sendHint(anyInt()); @@ -401,7 +413,8 @@ public class SystemPerformanceHinterTests {                  eq(FRAME_RATE_SELECTION_STRATEGY_SELF));          verify(mTransaction).setFrameRateCategory(                  eq(mSecondaryDisplayRoot), -                eq(FRAME_RATE_CATEGORY_DEFAULT)); +                eq(FRAME_RATE_CATEGORY_DEFAULT), +                eq(false));          verify(mTransaction).setEarlyWakeupEnd();          verify(mTransaction).applyAsyncUnsafe();          verify(mAdpfSession).sendHint(eq(CPU_LOAD_RESET)); diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java index 2cbeaa17332c..f846ac50d5fb 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java @@ -34,6 +34,7 @@ import org.junit.runners.Suite;          KernelSingleUidTimeReaderTest.class,          LongArrayMultiStateCounterTest.class,          LongMultiStateCounterTest.class, +        MonotonicClockTest.class,          PowerProfileTest.class,          PowerStatsTest.class, diff --git a/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java new file mode 100644 index 000000000000..7951270461d7 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.os; + +import static com.google.common.truth.Truth.assertThat; + +import android.util.Xml; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class MonotonicClockTest { +    private final MockClock mClock = new MockClock(); + +    @Test +    public void persistence() throws IOException { +        MonotonicClock monotonicClock = new MonotonicClock(1000, mClock); +        mClock.realtime = 234; + +        assertThat(monotonicClock.monotonicTime()).isEqualTo(1234); + +        ByteArrayOutputStream out = new ByteArrayOutputStream(); +        monotonicClock.writeXml(out, Xml.newFastSerializer()); +        String xml = out.toString(); +        assertThat(xml).contains("timeshift=\"1234\""); + +        mClock.realtime = 42; +        MonotonicClock newMonotonicClock = new MonotonicClock(0, mClock); +        newMonotonicClock.readXml(new ByteArrayInputStream(out.toByteArray()), +                Xml.newFastPullParser()); + +        mClock.realtime = 2000; +        assertThat(newMonotonicClock.monotonicTime()).isEqualTo(1234 - 42 + 2000); +    } +} diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 9c6528785584..b71aaf3fc2e6 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -1801,6 +1801,12 @@        "group": "WM_ERROR",        "at": "com\/android\/server\/wm\/WindowManagerService.java"      }, +    "-504637678": { +      "message": "Starting animation on dim layer %s, requested by %s, alpha: %f -> %f, blur: %d -> %d", +      "level": "VERBOSE", +      "group": "WM_DEBUG_DIMMER", +      "at": "com\/android\/server\/wm\/SmoothDimmer.java" +    },      "-503656156": {        "message": "Update process config of %s to new config %s",        "level": "VERBOSE", @@ -3739,6 +3745,12 @@        "group": "WM_DEBUG_CONFIGURATION",        "at": "com\/android\/server\/wm\/ActivityClientController.java"      }, +    "1309365288": { +      "message": "Removing dim surface %s on transaction %s", +      "level": "DEBUG", +      "group": "WM_DEBUG_DIMMER", +      "at": "com\/android\/server\/wm\/SmoothDimmer.java" +    },      "1316533291": {        "message": "State movement: %s from:%s to:%s reason:%s",        "level": "VERBOSE", @@ -4003,6 +4015,12 @@        "group": "WM_DEBUG_STATES",        "at": "com\/android\/server\/wm\/ActivityRecord.java"      }, +    "1620751818": { +      "message": "Dim %s skipping animation and directly setting alpha=%f, blur=%d", +      "level": "DEBUG", +      "group": "WM_DEBUG_DIMMER", +      "at": "com\/android\/server\/wm\/SmoothDimmer.java" +    },      "1621562070": {        "message": "    startWCT=%s",        "level": "VERBOSE", @@ -4560,6 +4578,9 @@      "WM_DEBUG_CONTENT_RECORDING": {        "tag": "WindowManager"      }, +    "WM_DEBUG_DIMMER": { +      "tag": "WindowManager" +    },      "WM_DEBUG_DRAW": {        "tag": "WindowManager"      }, diff --git a/graphics/java/Android.bp b/graphics/java/Android.bp index 63d1f6d6f2d6..db37a38756d2 100644 --- a/graphics/java/Android.bp +++ b/graphics/java/Android.bp @@ -7,6 +7,12 @@ package {      default_applicable_licenses: ["frameworks_base_license"],  } +aconfig_declarations { +    name: "framework_graphics_flags", +    package: "com.android.graphics.flags", +    srcs: ["android/framework_graphics.aconfig"], +} +  filegroup {      name: "framework-graphics-nonupdatable-sources",      srcs: [ diff --git a/graphics/java/android/framework_graphics.aconfig b/graphics/java/android/framework_graphics.aconfig new file mode 100644 index 000000000000..e030dad6bf14 --- /dev/null +++ b/graphics/java/android/framework_graphics.aconfig @@ -0,0 +1,8 @@ +package: "com.android.graphics.flags" + +flag { +     name: "exact_compute_bounds" +     namespace: "framework_graphics" +     description: "Add a function without unused exact param for computeBounds." +     bug: "304478551" +}
\ No newline at end of file diff --git a/graphics/java/android/graphics/Gainmap.java b/graphics/java/android/graphics/Gainmap.java index b5fb13db4ae4..0a6fb8424094 100644 --- a/graphics/java/android/graphics/Gainmap.java +++ b/graphics/java/android/graphics/Gainmap.java @@ -16,11 +16,14 @@  package android.graphics; +import android.annotation.FlaggedApi;  import android.annotation.FloatRange;  import android.annotation.NonNull;  import android.os.Parcel;  import android.os.Parcelable; +import com.android.graphics.hwui.flags.Flags; +  import libcore.util.NativeAllocationRegistry;  /** @@ -125,6 +128,7 @@ public final class Gainmap implements Parcelable {       * Creates a new gainmap using the provided gainmap as the metadata source and the provided       * bitmap as the replacement for the gainmapContents       */ +    @FlaggedApi(Flags.FLAG_GAINMAP_CONSTRUCTOR_WITH_METADATA)      public Gainmap(@NonNull Gainmap gainmap, @NonNull Bitmap gainmapContents) {          this(gainmapContents, nCreateCopy(gainmap.mNativePtr));      } diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index db1cc446b2d6..9fde0fd6e6ab 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -1609,7 +1609,7 @@ public class Paint {      /**       * Returns the color of the shadow layer.       * -     * @return the shadow layer's color encoded as a {@link ColorLong}. +     * @return the shadow layer's color encoded as a {@code ColorLong}.       * @see #setShadowLayer(float,float,float,int)       * @see #setShadowLayer(float,float,float,long)       * @see Color diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java index c9c1b23d874c..deb89faf3419 100644 --- a/graphics/java/android/graphics/Path.java +++ b/graphics/java/android/graphics/Path.java @@ -16,11 +16,14 @@  package android.graphics; +import android.annotation.FlaggedApi;  import android.annotation.FloatRange;  import android.annotation.NonNull;  import android.annotation.Nullable;  import android.annotation.Size; +import com.android.graphics.flags.Flags; +  import dalvik.annotation.optimization.CriticalNative;  import dalvik.annotation.optimization.FastNative; @@ -309,6 +312,7 @@ public class Path {       *       * @param bounds Returns the computed bounds of the path's control points.       */ +    @FlaggedApi(Flags.FLAG_EXACT_COMPUTE_BOUNDS)      public void computeBounds(@NonNull RectF bounds) {          nComputeBounds(mNativePath, bounds);      } diff --git a/graphics/java/android/graphics/drawable/RippleShader.java b/graphics/java/android/graphics/drawable/RippleShader.java index 4461f39fd006..c2a7a84ad809 100644 --- a/graphics/java/android/graphics/drawable/RippleShader.java +++ b/graphics/java/android/graphics/drawable/RippleShader.java @@ -109,9 +109,8 @@ final class RippleShader extends RuntimeShader {              + "    float alpha = min(fadeIn, 1. - fadeOutNoise);\n"              + "    vec2 uv = p * in_resolutionScale;\n"              + "    vec2 densityUv = uv - mod(uv, in_noiseScale);\n" -            + "    float turbulence = turbulence(uv, in_turbulencePhase);\n" -            + "    float sparkleAlpha = sparkles(densityUv, in_noisePhase) * ring * alpha " -            + "* turbulence;\n" +            + "    float turb = turbulence(uv, in_turbulencePhase);\n" +            + "    float sparkleAlpha = sparkles(densityUv, in_noisePhase) * ring * alpha * turb;\n"              + "    float fade = min(fadeIn, 1. - fadeOutRipple);\n"              + "    float waveAlpha = softCircle(p, center, in_maxRadius * scaleIn, 1.) * fade "              + "* in_color.a;\n" diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java index f5e5803d4796..dc1773bd7290 100644 --- a/graphics/java/android/graphics/text/LineBreakConfig.java +++ b/graphics/java/android/graphics/text/LineBreakConfig.java @@ -39,12 +39,14 @@ public final class LineBreakConfig {      /**       * No hyphenation preference is specified.       * +     * <p>       * This is a special value of hyphenation preference indicating no hyphenation preference is       * specified. When overriding a {@link LineBreakConfig} with another {@link LineBreakConfig}       * with {@link Builder#merge(LineBreakConfig)} function, the hyphenation preference of       * overridden config will be kept if the hyphenation preference of overriding config is       * {@link #HYPHENATION_UNSPECIFIED}.       * +     * <p>       * <pre>       *     val override = LineBreakConfig.Builder()       *          .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE) @@ -57,6 +59,7 @@ public final class LineBreakConfig {       *     // LINE_BREAK_WORD_STYLE_PHRASE for line break word style.       * </pre>       * +     * <p>       * This value is resolved to {@link #HYPHENATION_ENABLED} if this value is used for text       * layout/rendering.       */ @@ -89,6 +92,7 @@ public final class LineBreakConfig {      /**       * No line break style is specified.       * +     * <p>       * This is a special value of line break style indicating no style value is specified.       * When overriding a {@link LineBreakConfig} with another {@link LineBreakConfig} with       * {@link Builder#merge(LineBreakConfig)} function, the line break style of overridden config @@ -107,6 +111,7 @@ public final class LineBreakConfig {       *     // LINE_BREAK_WORD_STYLE_PHRASE for line break word style.       * </pre>       * +     * <p>       * This value is resolved to {@link #LINE_BREAK_STYLE_NONE} if this value is used for text       * layout/rendering.       */ @@ -270,6 +275,8 @@ public final class LineBreakConfig {          /**           * Resets this builder to the given config state.           * +         * @param config a config value used for resetting. {@code null} is allowed. If {@code null} +         *              is passed, all configs are reset to unspecified.           * @return This {@code Builder}.           * @hide           */ diff --git a/graphics/java/android/graphics/text/LineBreaker.java b/graphics/java/android/graphics/text/LineBreaker.java index dc2e794a1df4..0e3fb163ef75 100644 --- a/graphics/java/android/graphics/text/LineBreaker.java +++ b/graphics/java/android/graphics/text/LineBreaker.java @@ -249,7 +249,7 @@ public class LineBreaker {           * @param useBoundsForWidth True for using bounding box, false for advances.           * @return this builder instance           * @see Layout#getUseBoundsForWidth() -         * @see StaticLayout.Builder#setUseBoundsForWidth(boolean) +         * @see android.text.StaticLayout.Builder#setUseBoundsForWidth(boolean)           */          @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)          public @NonNull Builder setUseBoundsForWidth(boolean useBoundsForWidth) { diff --git a/graphics/java/android/view/PixelCopy.java b/graphics/java/android/view/PixelCopy.java index 0e198d5c56ec..e6de5978ceb0 100644 --- a/graphics/java/android/view/PixelCopy.java +++ b/graphics/java/android/view/PixelCopy.java @@ -322,7 +322,7 @@ public final class PixelCopy {          }          /** -         * Returns the {@link CopyResultStatus} of the copy request. +         * Returns the status of the copy request.           */          public @CopyResultStatus int getStatus() {              return mStatus; diff --git a/keystore/java/android/security/AndroidKeyStoreMaintenance.java b/keystore/java/android/security/AndroidKeyStoreMaintenance.java index 31c2eb2efaed..b7ea04fdfe07 100644 --- a/keystore/java/android/security/AndroidKeyStoreMaintenance.java +++ b/keystore/java/android/security/AndroidKeyStoreMaintenance.java @@ -128,25 +128,6 @@ public class AndroidKeyStoreMaintenance {      }      /** -     * Queries user state from Keystore 2.0. -     * -     * @param userId - Android user id of the user. -     * @return UserState enum variant as integer if successful or an error -     */ -    public static int getState(int userId) { -        StrictMode.noteDiskRead(); -        try { -            return getService().getState(userId); -        } catch (ServiceSpecificException e) { -            Log.e(TAG, "getState failed", e); -            return e.errorCode; -        } catch (Exception e) { -            Log.e(TAG, "Can not connect to keystore", e); -            return SYSTEM_ERROR; -        } -    } - -    /**       * Informs Keystore 2.0 that an off body event was detected.       */      public static void onDeviceOffBody() { diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java index 8045f55f6b4c..11b827117aa3 100644 --- a/keystore/java/android/security/KeyStore.java +++ b/keystore/java/android/security/KeyStore.java @@ -19,8 +19,6 @@ package android.security;  import android.compat.annotation.UnsupportedAppUsage;  import android.os.Build;  import android.os.StrictMode; -import android.os.UserHandle; -import android.security.maintenance.UserState;  /**   * @hide This should not be made public in its present form because it @@ -37,15 +35,6 @@ public class KeyStore {      // Used for UID field to indicate the calling UID.      public static final int UID_SELF = -1; -    // States -    public enum State { -        @UnsupportedAppUsage -        UNLOCKED, -        @UnsupportedAppUsage -        LOCKED, -        UNINITIALIZED -    }; -      private static final KeyStore KEY_STORE = new KeyStore();      @UnsupportedAppUsage @@ -55,28 +44,6 @@ public class KeyStore {      /** @hide */      @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) -    public State state(int userId) { -        int userState = AndroidKeyStoreMaintenance.getState(userId); -        switch (userState) { -            case UserState.UNINITIALIZED: -                return KeyStore.State.UNINITIALIZED; -            case UserState.LSKF_UNLOCKED: -                return KeyStore.State.UNLOCKED; -            case UserState.LSKF_LOCKED: -                return KeyStore.State.LOCKED; -            default: -                throw new AssertionError(userState); -        } -    } - -    /** @hide */ -    @UnsupportedAppUsage -    public State state() { -        return state(UserHandle.myUserId()); -    } - -    /** @hide */ -    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)      public byte[] get(String key) {          return null;      } diff --git a/keystore/java/android/security/KeyStoreException.java b/keystore/java/android/security/KeyStoreException.java index 253d70465720..5825facee021 100644 --- a/keystore/java/android/security/KeyStoreException.java +++ b/keystore/java/android/security/KeyStoreException.java @@ -319,7 +319,7 @@ public class KeyStoreException extends Exception {      /**       * Returns one of the error codes exported by the class.       * -     * @return a public error code, one of the values in {@link PublicErrorCode}. +     * @return a public error code       */      @PublicErrorCode      public int getNumericErrorCode() { diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java index 96c257b304a0..1ba41b106f56 100644 --- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java @@ -75,16 +75,18 @@ import javax.security.auth.x500.X500Principal;   * {@link java.security.interfaces.ECPublicKey} or {@link java.security.interfaces.RSAPublicKey}   * interfaces.   * - * <p>For asymmetric key pairs, a self-signed X.509 certificate will be also generated and stored in - * the Android Keystore. This is because the {@link java.security.KeyStore} abstraction does not - * support storing key pairs without a certificate. The subject, serial number, and validity dates - * of the certificate can be customized in this spec. The self-signed certificate may be replaced at - * a later time by a certificate signed by a Certificate Authority (CA). + * <p>For asymmetric key pairs, a X.509 certificate will be also generated and stored in the Android + * Keystore. This is because the {@link java.security.KeyStore} abstraction does not support storing + * key pairs without a certificate. The subject, serial number, and validity dates of the + * certificate can be customized in this spec. The certificate may be replaced at a later time by a + * certificate signed by a Certificate Authority (CA).   * - * <p>NOTE: If a private key is not authorized to sign the self-signed certificate, then the - * certificate will be created with an invalid signature which will not verify. Such a certificate - * is still useful because it provides access to the public key. To generate a valid signature for - * the certificate the key needs to be authorized for all of the following: + * <p>NOTE: If attestation is not requested using {@link Builder#setAttestationChallenge(byte[])}, + * generated certificate may be self-signed. If a private key is not authorized to sign the + * certificate, then the certificate will be created with an invalid signature which will not + * verify. Such a certificate is still useful because it provides access to the public key. To + * generate a valid signature for the certificate the key needs to be authorized for all of the + * following:   * <ul>   * <li>{@link KeyProperties#PURPOSE_SIGN},</li>   * <li>operation without requiring the user to be authenticated (see @@ -989,12 +991,6 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu           * @param purposes set of purposes (e.g., encrypt, decrypt, sign) for which the key can be           *        used. Attempts to use the key for any other purpose will be rejected.           * -         *        <p>If the set of purposes for which the key can be used does not contain -         *        {@link KeyProperties#PURPOSE_SIGN}, the self-signed certificate generated by -         *        {@link KeyPairGenerator} of {@code AndroidKeyStore} provider will contain an -         *        invalid signature. This is OK if the certificate is only used for obtaining the -         *        public key from Android KeyStore. -         *           *        <p>See {@link KeyProperties}.{@code PURPOSE} flags.           */          public Builder(@NonNull String keystoreAlias, @KeyProperties.PurposeEnum int purposes) { @@ -1140,7 +1136,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu          }          /** -         * Sets the subject used for the self-signed certificate of the generated key pair. +         * Sets the subject used for the certificate of the generated key pair.           *           * <p>By default, the subject is {@code CN=fake}.           */ @@ -1154,7 +1150,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu          }          /** -         * Sets the serial number used for the self-signed certificate of the generated key pair. +         * Sets the serial number used for the certificate of the generated key pair.           *           * <p>By default, the serial number is {@code 1}.           */ @@ -1168,8 +1164,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu          }          /** -         * Sets the start of the validity period for the self-signed certificate of the generated -         * key pair. +         * Sets the start of the validity period for the certificate of the generated key pair.           *           * <p>By default, this date is {@code Jan 1 1970}.           */ @@ -1183,8 +1178,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu          }          /** -         * Sets the end of the validity period for the self-signed certificate of the generated key -         * pair. +         * Sets the end of the validity period for the certificate of the generated key pair.           *           * <p>By default, this date is {@code Jan 1 2048}.           */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java index 06ce37148eaf..8cf869b175ef 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java @@ -87,33 +87,28 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle          mTransitions.addHandler(this);      } -    @Override -    public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, -            @NonNull SurfaceControl.Transaction startTransaction, -            @NonNull SurfaceControl.Transaction finishTransaction, -            @NonNull Transitions.TransitionFinishCallback finishCallback) { -        boolean containsEmbeddingSplit = false; -        boolean containsNonEmbeddedChange = false; -        final List<TransitionInfo.Change> changes = info.getChanges(); -        for (int i = changes.size() - 1; i >= 0; i--) { -            final TransitionInfo.Change change = changes.get(i); -            if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) { -                containsNonEmbeddedChange = true; -            } else if (!change.hasFlags(FLAG_FILLS_TASK)) { +    /** Whether ActivityEmbeddingController should animate this transition. */ +    public boolean shouldAnimate(@NonNull TransitionInfo info) { +        boolean containsEmbeddingChange = false; +        for (TransitionInfo.Change change : info.getChanges()) { +            if (!change.hasFlags(FLAG_FILLS_TASK) && change.hasFlags( +                    FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) {                  // Whether the Task contains any ActivityEmbedding split before or after the                  // transition. -                containsEmbeddingSplit = true; +                containsEmbeddingChange = true;              }          } -        if (!containsEmbeddingSplit) { +        if (!containsEmbeddingChange) {              // Let the system to play the default animation if there is no ActivityEmbedding split              // window. This allows to play the app customized animation when there is no embedding,              // such as the device is in a folded state.              return false;          } -        if (containsNonEmbeddedChange && !handleNonEmbeddedChanges(changes)) { + +        if (containsNonEmbeddedChange(info) && !handleNonEmbeddedChanges(info.getChanges())) {              return false;          } +          final TransitionInfo.AnimationOptions options = info.getAnimationOptions();          if (options != null                  // Scene-transition will be handled by app side. @@ -123,6 +118,17 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle              return false;          } +        return true; +    } + +    @Override +    public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, +            @NonNull SurfaceControl.Transaction startTransaction, +            @NonNull SurfaceControl.Transaction finishTransaction, +            @NonNull Transitions.TransitionFinishCallback finishCallback) { + +        if (!shouldAnimate(info)) return false; +          // Start ActivityEmbedding animation.          mTransitionCallbacks.put(transition, finishCallback);          mAnimationRunner.startAnimation(transition, info, startTransaction, finishTransaction); @@ -136,6 +142,16 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle          mAnimationRunner.cancelAnimationFromMerge();      } +    /** Whether TransitionInfo contains non-ActivityEmbedding embedded window. */ +    private boolean containsNonEmbeddedChange(@NonNull TransitionInfo info) { +        for (TransitionInfo.Change change : info.getChanges()) { +            if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) { +                return true; +            } +        } +        return false; +    } +      private boolean handleNonEmbeddedChanges(List<TransitionInfo.Change> changes) {          final Rect nonClosingEmbeddedArea = new Rect();          for (int i = changes.size() - 1; i >= 0; i--) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index c51af46accdb..ea7b2e92fefb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -605,6 +605,7 @@ public abstract class WMShellBaseModule {      @Provides      static Transitions provideTransitions(Context context,              ShellInit shellInit, +            ShellCommandHandler shellCommandHandler,              ShellController shellController,              ShellTaskOrganizer organizer,              TransactionPool pool, @@ -612,14 +613,13 @@ public abstract class WMShellBaseModule {              @ShellMainThread ShellExecutor mainExecutor,              @ShellMainThread Handler mainHandler,              @ShellAnimationThread ShellExecutor animExecutor, -            ShellCommandHandler shellCommandHandler,              RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {          if (!context.getResources().getBoolean(R.bool.config_registerShellTransitionsOnInit)) {              // TODO(b/238217847): Force override shell init if registration is disabled              shellInit = new ShellInit(mainExecutor);          } -        return new Transitions(context, shellInit, shellController, organizer, pool, -                displayController, mainExecutor, mainHandler, animExecutor, shellCommandHandler, +        return new Transitions(context, shellInit, shellCommandHandler, shellController, organizer, +                pool, displayController, mainExecutor, mainHandler, animExecutor,                  rootTaskDisplayAreaOrganizer);      } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 14a040a40874..a533ca5fa8fb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -32,6 +32,7 @@ import com.android.launcher3.icons.IconProvider;  import com.android.wm.shell.RootTaskDisplayAreaOrganizer;  import com.android.wm.shell.ShellTaskOrganizer;  import com.android.wm.shell.WindowManagerShellWrapper; +import com.android.wm.shell.activityembedding.ActivityEmbeddingController;  import com.android.wm.shell.bubbles.BubbleController;  import com.android.wm.shell.bubbles.BubbleData;  import com.android.wm.shell.bubbles.BubbleDataRepository; @@ -366,11 +367,12 @@ public abstract class WMShellModule {              KeyguardTransitionHandler keyguardTransitionHandler,              Optional<DesktopTasksController> desktopTasksController,              Optional<UnfoldTransitionHandler> unfoldHandler, +            Optional<ActivityEmbeddingController> activityEmbeddingController,              Transitions transitions) {          return new DefaultMixedHandler(shellInit, transitions, splitScreenOptional,                  pipTransitionController, recentsTransitionHandler,                  keyguardTransitionHandler, desktopTasksController, -                unfoldHandler); +                unfoldHandler, activityEmbeddingController);      }      @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/OWNERS new file mode 100644 index 000000000000..74a29ddad073 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/OWNERS @@ -0,0 +1 @@ +hwwang@google.com diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java index 8a6403705c1c..1898ea737729 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java @@ -17,19 +17,28 @@  package com.android.wm.shell.dagger.pip;  import android.annotation.NonNull; +import android.content.Context;  import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.DisplayInsetsController;  import com.android.wm.shell.common.pip.PipBoundsAlgorithm;  import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; +import com.android.wm.shell.common.pip.PipUtils;  import com.android.wm.shell.dagger.WMShellBaseModule;  import com.android.wm.shell.dagger.WMSingleton; +import com.android.wm.shell.pip2.phone.PipController;  import com.android.wm.shell.pip2.phone.PipTransition; +import com.android.wm.shell.sysui.ShellController;  import com.android.wm.shell.sysui.ShellInit;  import com.android.wm.shell.transition.Transitions;  import dagger.Module;  import dagger.Provides; +import java.util.Optional; +  /**   * Provides dependencies from {@link com.android.wm.shell.pip2}, this implementation is meant to be   * the successor of its sibling {@link Pip1Module}. @@ -42,8 +51,26 @@ public abstract class Pip2Module {              @NonNull ShellTaskOrganizer shellTaskOrganizer,              @NonNull Transitions transitions,              PipBoundsState pipBoundsState, -            PipBoundsAlgorithm pipBoundsAlgorithm) { +            PipBoundsAlgorithm pipBoundsAlgorithm, +            Optional<PipController> pipController) {          return new PipTransition(shellInit, shellTaskOrganizer, transitions, pipBoundsState, null,                  pipBoundsAlgorithm);      } + +    @WMSingleton +    @Provides +    static Optional<PipController> providePipController(Context context, +            ShellInit shellInit, +            ShellController shellController, +            DisplayController displayController, +            DisplayInsetsController displayInsetsController, +            PipDisplayLayoutState pipDisplayLayoutState) { +        if (!PipUtils.isPip2ExperimentEnabled()) { +            return Optional.empty(); +        } else { +            return Optional.ofNullable(PipController.create( +                    context, shellInit, shellController, displayController, displayInsetsController, +                    pipDisplayLayoutState)); +        } +    }  } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java new file mode 100644 index 000000000000..186cb615f4ec --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.pip2.phone; + +import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE; + +import android.content.Context; +import android.content.res.Configuration; +import android.view.InsetsState; + +import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.DisplayInsetsController; +import com.android.wm.shell.common.DisplayLayout; +import com.android.wm.shell.common.pip.PipDisplayLayoutState; +import com.android.wm.shell.common.pip.PipUtils; +import com.android.wm.shell.protolog.ShellProtoLogGroup; +import com.android.wm.shell.sysui.ConfigurationChangeListener; +import com.android.wm.shell.sysui.ShellController; +import com.android.wm.shell.sysui.ShellInit; + +/** + * Manages the picture-in-picture (PIP) UI and states for Phones. + */ +public class PipController implements ConfigurationChangeListener, +        DisplayController.OnDisplaysChangedListener { +    private static final String TAG = PipController.class.getSimpleName(); + +    private Context mContext; +    private ShellController mShellController; +    private DisplayController mDisplayController; +    private DisplayInsetsController mDisplayInsetsController; +    private PipDisplayLayoutState mPipDisplayLayoutState; + +    private PipController(Context context, +            ShellInit shellInit, +            ShellController shellController, +            DisplayController displayController, +            DisplayInsetsController displayInsetsController, +            PipDisplayLayoutState pipDisplayLayoutState) { +        mContext = context; +        mShellController = shellController; +        mDisplayController = displayController; +        mDisplayInsetsController = displayInsetsController; +        mPipDisplayLayoutState = pipDisplayLayoutState; + +        if (PipUtils.isPip2ExperimentEnabled()) { +            shellInit.addInitCallback(this::onInit, this); +        } +    } + +    private void onInit() { +        // Ensure that we have the display info in case we get calls to update the bounds before the +        // listener calls back +        mPipDisplayLayoutState.setDisplayId(mContext.getDisplayId()); +        DisplayLayout layout = new DisplayLayout(mContext, mContext.getDisplay()); +        mPipDisplayLayoutState.setDisplayLayout(layout); + +        mShellController.addConfigurationChangeListener(this); +        mDisplayController.addDisplayWindowListener(this); +        mDisplayInsetsController.addInsetsChangedListener(mPipDisplayLayoutState.getDisplayId(), +                new DisplayInsetsController.OnInsetsChangedListener() { +                    @Override +                    public void insetsChanged(InsetsState insetsState) { +                        onDisplayChanged(mDisplayController +                                        .getDisplayLayout(mPipDisplayLayoutState.getDisplayId())); +                    } +                }); +    } + +    /** +     * Instantiates {@link PipController}, returns {@code null} if the feature not supported. +     */ +    public static PipController create(Context context, +            ShellInit shellInit, +            ShellController shellController, +            DisplayController displayController, +            DisplayInsetsController displayInsetsController, +            PipDisplayLayoutState pipDisplayLayoutState) { +        if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) { +            ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, +                    "%s: Device doesn't support Pip feature", TAG); +            return null; +        } +        return new PipController(context, shellInit, shellController, displayController, +                displayInsetsController, pipDisplayLayoutState); +    } + + +    @Override +    public void onConfigurationChanged(Configuration newConfiguration) { +        mPipDisplayLayoutState.onConfigurationChanged(); +    } + +    @Override +    public void onThemeChanged() { +        onDisplayChanged(new DisplayLayout(mContext, mContext.getDisplay())); +    } + +    @Override +    public void onDisplayAdded(int displayId) { +        if (displayId != mPipDisplayLayoutState.getDisplayId()) { +            return; +        } +        onDisplayChanged(mDisplayController.getDisplayLayout(displayId)); +    } + +    @Override +    public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) { +        if (displayId != mPipDisplayLayoutState.getDisplayId()) { +            return; +        } +        onDisplayChanged(mDisplayController.getDisplayLayout(displayId)); +    } + +    private void onDisplayChanged(DisplayLayout layout) { +        mPipDisplayLayoutState.setDisplayLayout(layout); +    } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java index d31476c63890..d277eef761e9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java @@ -925,19 +925,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {                  if (toHome) wct.reorder(mRecentsTask, true /* toTop */);                  else wct.restoreTransientOrder(mRecentsTask);              } -            if (!toHome -                    // If a recents gesture starts on the 3p launcher, then the 3p launcher is the -                    // live tile (pausing app). If the gesture is "cancelled" we need to return to -                    // 3p launcher instead of "task-switching" away from it. -                    && (!mWillFinishToHome || mPausingSeparateHome) -                    && mPausingTasks != null && mState == STATE_NORMAL) { -                if (mPausingSeparateHome) { -                    ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, -                            "  returning to 3p home"); -                } else { -                    ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, -                            "  returning to app"); -                } +            if (!toHome && !mWillFinishToHome && mPausingTasks != null && mState == STATE_NORMAL) { +                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "  returning to app");                  // The gesture is returning to the pausing-task(s) rather than continuing with                  // recents, so end the transition by moving the app back to the top (and also                  // re-showing it's task). @@ -969,6 +958,15 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {                      wct.restoreTransientOrder(mRecentsTask);                  }              } else { +                if (mPausingSeparateHome) { +                    if (mOpeningTasks.isEmpty()) { +                        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, +                                "  recents occluded 3p home"); +                    } else { +                        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, +                                "  switch task by recents on 3p home"); +                    } +                }                  ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "  normal finish");                  // The general case: committing to recents, going home, or switching tasks.                  for (int i = 0; i < mOpeningTasks.size(); ++i) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 68ca2313f709..7a4834cb5adb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -23,6 +23,7 @@ import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVIT  import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;  import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;  import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT;  import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;  import static android.view.Display.DEFAULT_DISPLAY;  import static android.view.RemoteAnimationTarget.MODE_OPENING; @@ -395,6 +396,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,          return mMainStage.isActive();      } +    /** @return whether this transition-request has the launch-adjacent flag. */ +    public boolean requestHasLaunchAdjacentFlag(TransitionRequestInfo request) { +        final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask(); +        return triggerTask != null && triggerTask.baseIntent != null +                && (triggerTask.baseIntent.getFlags() & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0; +    } +      /** @return whether the transition-request implies entering pip from split. */      public boolean requestImpliesSplitToPip(TransitionRequestInfo request) {          if (!isSplitActive() || !mMixedHandler.requestHasPipEnter(request)) { @@ -2459,10 +2467,20 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,                          EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);              } -            // When split in the background, it should be only opening/dismissing transition and -            // would keep out not empty. Prevent intercepting all transitions for split screen when -            // it is in the background and not identify to handle it. -            return (!out.isEmpty() || isSplitScreenVisible()) ? out : null; +            if (!out.isEmpty()) { +                // One of the cases above handled it +                return out; +            } else if (isSplitScreenVisible()) { +                // If split is visible, only defer handling this transition if it's launching +                // adjacent while there is already a split pair -- this may trigger PIP and +                // that should be handled by the mixed handler. +                final boolean deferTransition = requestHasLaunchAdjacentFlag(request) +                    && mMainStage.getChildCount() != 0 && mSideStage.getChildCount() != 0; +                return !deferTransition ? out : null; +            } +            // Don't intercept the transition if we are not handling it as a part of one of the +            // cases above and it is not already visible +            return null;          } else {              if (isOpening && getStageOfTask(triggerTask) != null) {                  // One task is appearing into split, prepare to enter split screen. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java index 451e61855943..918a5a4bd53e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java @@ -21,7 +21,9 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;  import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;  import static android.view.Display.DEFAULT_DISPLAY;  import static android.view.WindowManager.TRANSIT_CHANGE; +import static android.view.WindowManager.TRANSIT_PIP;  import static android.view.WindowManager.TRANSIT_TO_BACK; +import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;  import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;  import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR; @@ -43,6 +45,7 @@ import android.window.TransitionRequestInfo;  import android.window.WindowContainerTransaction;  import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.activityembedding.ActivityEmbeddingController;  import com.android.wm.shell.common.split.SplitScreenUtils;  import com.android.wm.shell.desktopmode.DesktopModeStatus;  import com.android.wm.shell.desktopmode.DesktopTasksController; @@ -74,6 +77,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,      private final KeyguardTransitionHandler mKeyguardHandler;      private DesktopTasksController mDesktopTasksController;      private UnfoldTransitionHandler mUnfoldHandler; +    private ActivityEmbeddingController mActivityEmbeddingController;      private static class MixedTransition {          static final int TYPE_ENTER_PIP_FROM_SPLIT = 1; @@ -93,9 +97,12 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,          /** Recents Transition while in desktop mode. */          static final int TYPE_RECENTS_DURING_DESKTOP = 6; -        /** Fuld/Unfold transition. */ +        /** Fold/Unfold transition. */          static final int TYPE_UNFOLD = 7; +        /** Enter pip from one of the Activity Embedding windows. */ +        static final int TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING = 8; +          /** The default animation for this mixed transition. */          static final int ANIM_TYPE_DEFAULT = 0; @@ -150,7 +157,8 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,              Optional<RecentsTransitionHandler> recentsHandlerOptional,              KeyguardTransitionHandler keyguardHandler,              Optional<DesktopTasksController> desktopTasksControllerOptional, -            Optional<UnfoldTransitionHandler> unfoldHandler) { +            Optional<UnfoldTransitionHandler> unfoldHandler, +            Optional<ActivityEmbeddingController> activityEmbeddingController) {          mPlayer = player;          mKeyguardHandler = keyguardHandler;          if (Transitions.ENABLE_SHELL_TRANSITIONS @@ -170,6 +178,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,                  }                  mDesktopTasksController = desktopTasksControllerOptional.orElse(null);                  mUnfoldHandler = unfoldHandler.orElse(null); +                mActivityEmbeddingController = activityEmbeddingController.orElse(null);              }, this);          }      } @@ -192,6 +201,16 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,              mPipHandler.augmentRequest(transition, request, out);              mSplitHandler.addEnterOrExitIfNeeded(request, out);              return out; +        } else if (request.getType() == TRANSIT_PIP +                && (request.getFlags() & FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY) != 0 && ( +                mActivityEmbeddingController != null)) { +            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, +                    " Got a PiP-enter request from an Activity Embedding split"); +            mActiveTransitions.add(new MixedTransition( +                    MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING, transition)); +            // Postpone transition splitting to later. +            WindowContainerTransaction out = new WindowContainerTransaction(); +            return out;          } else if (request.getRemoteTransition() != null                  && TransitionUtil.isOpeningType(request.getType())                  && (request.getTriggerTask() == null @@ -355,6 +374,9 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,          if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {              return animateEnterPipFromSplit(mixed, info, startTransaction, finishTransaction,                      finishCallback); +        } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) { +            return animateEnterPipFromActivityEmbedding(mixed, info, startTransaction, +                    finishTransaction, finishCallback);          } else if (mixed.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) {              return false;          } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) { @@ -400,6 +422,58 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,          }      } +    private boolean animateEnterPipFromActivityEmbedding(@NonNull MixedTransition mixed, +            @NonNull TransitionInfo info, +            @NonNull SurfaceControl.Transaction startTransaction, +            @NonNull SurfaceControl.Transaction finishTransaction, +            @NonNull Transitions.TransitionFinishCallback finishCallback) { +        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for " +                + "entering PIP from an Activity Embedding window"); +        // Split into two transitions (wct) +        TransitionInfo.Change pipChange = null; +        final TransitionInfo everythingElse = subCopy(info, TRANSIT_TO_BACK, true /* changes */); +        for (int i = info.getChanges().size() - 1; i >= 0; --i) { +            TransitionInfo.Change change = info.getChanges().get(i); +            if (mPipHandler.isEnteringPip(change, info.getType())) { +                if (pipChange != null) { +                    throw new IllegalStateException("More than 1 pip-entering changes in one" +                            + " transition? " + info); +                } +                pipChange = change; +                // going backwards, so remove-by-index is fine. +                everythingElse.getChanges().remove(i); +            } +        } + +        final Transitions.TransitionFinishCallback finishCB = (wct) -> { +            --mixed.mInFlightSubAnimations; +            mixed.joinFinishArgs(wct); +            if (mixed.mInFlightSubAnimations > 0) return; +            mActiveTransitions.remove(mixed); +            finishCallback.onTransitionFinished(mixed.mFinishWCT); +        }; + +        if (!mActivityEmbeddingController.shouldAnimate(everythingElse)) { +            // Fallback to dispatching to other handlers. +            return false; +        } + +        // PIP window should always be on the highest Z order. +        if (pipChange != null) { +            mixed.mInFlightSubAnimations = 2; +            mPipHandler.startEnterAnimation( +                    pipChange, startTransaction.setLayer(pipChange.getLeash(), Integer.MAX_VALUE), +                    finishTransaction, +                    finishCB); +        } else { +            mixed.mInFlightSubAnimations = 1; +        } + +        mActivityEmbeddingController.startAnimation(mixed.mTransition, everythingElse, +                startTransaction, finishTransaction, finishCB); +        return true; +    } +      private boolean animateOpenIntentWithRemoteAndPip(@NonNull MixedTransition mixed,              @NonNull TransitionInfo info,              @NonNull SurfaceControl.Transaction startTransaction, @@ -811,6 +885,10 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,                  } else {                      mPipHandler.end();                  } +            } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) { +                mPipHandler.end(); +                mActivityEmbeddingController.mergeAnimation(transition, info, t, mergeTarget, +                        finishCallback);              } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {                  mPipHandler.end();                  if (mixed.mLeftoversHandler != null) { @@ -851,6 +929,9 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,          if (mixed == null) return;          if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {              mPipHandler.onTransitionConsumed(transition, aborted, finishT); +        } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) { +            mPipHandler.onTransitionConsumed(transition, aborted, finishT); +            mActivityEmbeddingController.onTransitionConsumed(transition, aborted, finishT);          } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {              mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);          } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java index 8b050e524038..b1fc16ddf19b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java @@ -63,7 +63,7 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler {              @NonNull Transitions.TransitionFinishCallback finishCallback) {          if (mTransition != transition) return false;          ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Using registered One-shot remote" -                + " transition %s for #%d.", mRemote, info.getDebugId()); +                + " transition %s for (#%d).", mRemote, info.getDebugId());          final IBinder.DeathRecipient remoteDied = () -> {              Log.e(Transitions.TAG, "Remote transition died, finishing"); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java index 592b22a47bc4..ca2c3b4fbff1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java @@ -126,7 +126,7 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler {                  }              }          } -        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Delegate animation for #%d to %s", +        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Delegate animation for (#%d) to %s",                  info.getDebugId(), pendingRemote);          if (pendingRemote == null) return false; @@ -241,7 +241,7 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler {          if (remote == null) return null;          mRequestedRemotes.put(transition, remote);          ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "RemoteTransition directly requested" -                + " for %s: %s", transition, remote); +                + " for (#%d) %s: %s", request.getDebugId(), transition, remote);          return new WindowContainerTransaction();      } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java index 87c438a5b37d..ba0ef20c412e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java @@ -19,13 +19,12 @@ package com.android.wm.shell.transition;  import android.annotation.NonNull;  import android.annotation.Nullable;  import android.os.IBinder; +import android.util.Slog;  import android.view.SurfaceControl;  import android.window.TransitionInfo;  import android.window.TransitionRequestInfo;  import android.window.WindowContainerTransaction; -import java.util.ArrayList; -  /**   * A Simple handler that tracks SLEEP transitions. We track them specially since we (ab)use these   * as sentinels for fast-forwarding through animations when the screen is off. @@ -34,30 +33,25 @@ import java.util.ArrayList;   * don't register it like a normal handler.   */  class SleepHandler implements Transitions.TransitionHandler { -    final ArrayList<IBinder> mSleepTransitions = new ArrayList<>(); -      @Override      public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,              @NonNull SurfaceControl.Transaction startTransaction,              @NonNull SurfaceControl.Transaction finishTransaction,              @NonNull Transitions.TransitionFinishCallback finishCallback) { -        mSleepTransitions.remove(transition); -        startTransaction.apply(); -        finishCallback.onTransitionFinished(null); -        return true; +        if (info.hasChangesOrSideEffects()) { +            Slog.e(Transitions.TAG, "Real changes included in a SLEEP transition"); +            return false; +        } else { +            startTransaction.apply(); +            finishCallback.onTransitionFinished(null); +            return true; +        }      }      @Override      @Nullable      public WindowContainerTransaction handleRequest(@NonNull IBinder transition,              @NonNull TransitionRequestInfo request) { -        mSleepTransitions.add(transition);          return new WindowContainerTransaction();      } - -    @Override -    public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted, -            @Nullable SurfaceControl.Transaction finishTransaction) { -        mSleepTransitions.remove(transition); -    }  } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index 0d9a9e9f07ff..576bba96044c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -179,6 +179,7 @@ public class Transitions implements RemoteCallable<Transitions>,      private final DefaultTransitionHandler mDefaultTransitionHandler;      private final RemoteTransitionHandler mRemoteTransitionHandler;      private final DisplayController mDisplayController; +    private final ShellCommandHandler mShellCommandHandler;      private final ShellController mShellController;      private final ShellTransitionImpl mImpl = new ShellTransitionImpl();      private final SleepHandler mSleepHandler = new SleepHandler(); @@ -188,9 +189,6 @@ public class Transitions implements RemoteCallable<Transitions>,      /** List of possible handlers. Ordered by specificity (eg. tapped back to front). */      private final ArrayList<TransitionHandler> mHandlers = new ArrayList<>(); -    @Nullable -    private final ShellCommandHandler mShellCommandHandler; -      private final ArrayList<TransitionObserver> mObservers = new ArrayList<>();      /** List of {@link Runnable} instances to run when the last active transition has finished.  */ @@ -237,7 +235,7 @@ public class Transitions implements RemoteCallable<Transitions>,          @Override          public String toString() {              if (mInfo != null && mInfo.getDebugId() >= 0) { -                return "(#" + mInfo.getDebugId() + ")" + mToken + "@" + getTrack(); +                return "(#" + mInfo.getDebugId() + ") " + mToken + "@" + getTrack();              }              return mToken.toString() + "@" + getTrack();          } @@ -275,13 +273,14 @@ public class Transitions implements RemoteCallable<Transitions>,              @NonNull ShellExecutor mainExecutor,              @NonNull Handler mainHandler,              @NonNull ShellExecutor animExecutor) { -        this(context, shellInit, shellController, organizer, pool, displayController, mainExecutor, -                mainHandler, animExecutor, null, +        this(context, shellInit, new ShellCommandHandler(), shellController, organizer, pool, +                displayController, mainExecutor, mainHandler, animExecutor,                  new RootTaskDisplayAreaOrganizer(mainExecutor, context, shellInit));      }      public Transitions(@NonNull Context context,              @NonNull ShellInit shellInit, +            @Nullable ShellCommandHandler shellCommandHandler,              @NonNull ShellController shellController,              @NonNull WindowOrganizer organizer,              @NonNull TransactionPool pool, @@ -289,7 +288,6 @@ public class Transitions implements RemoteCallable<Transitions>,              @NonNull ShellExecutor mainExecutor,              @NonNull Handler mainHandler,              @NonNull ShellExecutor animExecutor, -            @Nullable ShellCommandHandler shellCommandHandler,              @NonNull RootTaskDisplayAreaOrganizer rootTDAOrganizer) {          mOrganizer = organizer;          mContext = context; @@ -300,13 +298,13 @@ public class Transitions implements RemoteCallable<Transitions>,          mDefaultTransitionHandler = new DefaultTransitionHandler(context, shellInit,                  displayController, pool, mainExecutor, mainHandler, animExecutor, rootTDAOrganizer);          mRemoteTransitionHandler = new RemoteTransitionHandler(mMainExecutor); +        mShellCommandHandler = shellCommandHandler;          mShellController = shellController;          // The very last handler (0 in the list) should be the default one.          mHandlers.add(mDefaultTransitionHandler);          ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Default");          // Next lowest priority is remote transitions.          mHandlers.add(mRemoteTransitionHandler); -        mShellCommandHandler = shellCommandHandler;          ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Remote");          shellInit.addInitCallback(this::onInit, this);      } @@ -339,9 +337,8 @@ public class Transitions implements RemoteCallable<Transitions>,              TransitionMetrics.getInstance();          } -        if (mShellCommandHandler != null) { -            mShellCommandHandler.addCommandCallback("transitions", this, this); -        } +        mShellCommandHandler.addCommandCallback("transitions", this, this); +        mShellCommandHandler.addDumpCallback(this::dump, this);      }      public boolean isRegistered() { @@ -655,8 +652,8 @@ public class Transitions implements RemoteCallable<Transitions>,      void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info,              @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {          info.setUnreleasedWarningCallSiteForAllSurfaces("Transitions.onTransitionReady"); -        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady %s: %s", -                transitionToken, info); +        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady (#%d) %s: %s", +                info.getDebugId(), transitionToken, info);          final int activeIdx = findByToken(mPendingTransitions, transitionToken);          if (activeIdx < 0) {              throw new IllegalStateException("Got transitionReady for non-pending transition " @@ -1073,8 +1070,8 @@ public class Transitions implements RemoteCallable<Transitions>,      void requestStartTransition(@NonNull IBinder transitionToken,              @Nullable TransitionRequestInfo request) { -        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested: %s %s", -                transitionToken, request); +        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested (#%d): %s %s", +                request.getDebugId(), transitionToken, request);          if (isTransitionKnown(transitionToken)) {              throw new RuntimeException("Transition already started " + transitionToken);          } @@ -1475,4 +1472,68 @@ public class Transitions implements RemoteCallable<Transitions>,          pw.println(prefix + "tracing");          mTracer.printShellCommandHelp(pw, prefix + "  ");      } + +    private void dump(@NonNull PrintWriter pw, String prefix) { +        pw.println(prefix + TAG); + +        final String innerPrefix = prefix + "  "; +        pw.println(prefix + "Handlers:"); +        for (TransitionHandler handler : mHandlers) { +            pw.print(innerPrefix); +            pw.print(handler.getClass().getSimpleName()); +            pw.println(" (" + Integer.toHexString(System.identityHashCode(handler)) + ")"); +        } + +        pw.println(prefix + "Observers:"); +        for (TransitionObserver observer : mObservers) { +            pw.print(innerPrefix); +            pw.println(observer.getClass().getSimpleName()); +        } + +        pw.println(prefix + "Pending Transitions:"); +        for (ActiveTransition transition : mPendingTransitions) { +            pw.print(innerPrefix + "token="); +            pw.println(transition.mToken); +            pw.print(innerPrefix + "id="); +            pw.println(transition.mInfo != null +                    ? transition.mInfo.getDebugId() +                    : -1); +            pw.print(innerPrefix + "handler="); +            pw.println(transition.mHandler != null +                    ? transition.mHandler.getClass().getSimpleName() +                    : null); +        } +        if (mPendingTransitions.isEmpty()) { +            pw.println(innerPrefix + "none"); +        } + +        pw.println(prefix + "Ready-during-sync Transitions:"); +        for (ActiveTransition transition : mReadyDuringSync) { +            pw.print(innerPrefix + "token="); +            pw.println(transition.mToken); +            pw.print(innerPrefix + "id="); +            pw.println(transition.mInfo != null +                    ? transition.mInfo.getDebugId() +                    : -1); +            pw.print(innerPrefix + "handler="); +            pw.println(transition.mHandler != null +                    ? transition.mHandler.getClass().getSimpleName() +                    : null); +        } +        if (mReadyDuringSync.isEmpty()) { +            pw.println(innerPrefix + "none"); +        } + +        pw.println(prefix + "Tracks:"); +        for (int i = 0; i < mTracks.size(); i++) { +            final ActiveTransition transition = mTracks.get(i).mActiveTransition; +            pw.println(innerPrefix + "Track #" + i); +            pw.print(innerPrefix + "active="); +            pw.println(transition); +            if (transition != null) { +                pw.print(innerPrefix + "hander="); +                pw.println(transition.mHandler); +            } +        } +    }  } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java index 0548a8e751cc..d0e647b30a96 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java @@ -297,7 +297,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>          }          // Task surface itself -        float shadowRadius = loadDimension(resources, params.mShadowRadiusId); +        float shadowRadius;          final Point taskPosition = mTaskInfo.positionInParent;          if (isFullscreen) {              // Setting the task crop to the width/height stops input events from being sent to @@ -308,9 +308,12 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>              // drag-resized by the window decoration.              startT.setWindowCrop(mTaskSurface, null);              finishT.setWindowCrop(mTaskSurface, null); +            // Shadow is not needed for fullscreen tasks +            shadowRadius = 0;          } else {              startT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);              finishT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight); +            shadowRadius = loadDimension(resources, params.mShadowRadiusId);          }          startT.setShadowRadius(mTaskSurface, shadowRadius)                  .show(mTaskSurface); diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml index b13e9a248575..1df11369a049 100644 --- a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml +++ b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml @@ -81,9 +81,7 @@          <option name="shell-timeout" value="6600s"/>          <option name="test-timeout" value="6000s"/>          <option name="hidden-api-checks" value="false"/> -        <!-- TODO(b/288396763): re-enable when PerfettoListener is fixed          <option name="device-listeners" value="android.device.collectors.PerfettoListener"/> -        -->          <!-- PerfettoListener related arguments -->          <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true"/>          <option name="instrumentation-arg" diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java index b9c9049e08c4..da83d4c0a122 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java @@ -1169,7 +1169,7 @@ public class ShellTransitionTests extends ShellTestCase {      }      @Test -    public void testEmptyTransitionStillReportsKeyguardGoingAway() { +    public void testEmptyTransition_withKeyguardGoingAway_plays() {          Transitions transitions = createTestTransitions();          transitions.replaceDefaultHandlerForTest(mDefaultHandler); @@ -1188,6 +1188,65 @@ public class ShellTransitionTests extends ShellTestCase {      }      @Test +    public void testSleepTransition_withKeyguardGoingAway_plays(){ +        Transitions transitions = createTestTransitions(); +        transitions.replaceDefaultHandlerForTest(mDefaultHandler); + +        IBinder transitToken = new Binder(); +        transitions.requestStartTransition(transitToken, +                new TransitionRequestInfo(TRANSIT_SLEEP, null /* trigger */, null /* remote */)); + +        // Make a no-op transition +        TransitionInfo info = new TransitionInfoBuilder( +                TRANSIT_SLEEP, TRANSIT_FLAG_KEYGUARD_GOING_AWAY, true /* noOp */).build(); +        transitions.onTransitionReady(transitToken, info, new StubTransaction(), +                new StubTransaction()); + +        // If keyguard-going-away flag set, then it shouldn't be aborted. +        assertEquals(1, mDefaultHandler.activeCount()); +    } + +    @Test +    public void testSleepTransition_withChanges_plays(){ +        Transitions transitions = createTestTransitions(); +        transitions.replaceDefaultHandlerForTest(mDefaultHandler); + +        IBinder transitToken = new Binder(); +        transitions.requestStartTransition(transitToken, +                new TransitionRequestInfo(TRANSIT_SLEEP, null /* trigger */, null /* remote */)); + +        // Make a transition with some changes +        TransitionInfo info = new TransitionInfoBuilder(TRANSIT_SLEEP) +                .addChange(TRANSIT_OPEN).build(); +        info.setTrack(0); +        transitions.onTransitionReady(transitToken, info, new StubTransaction(), +                new StubTransaction()); + +        // If there is an actual change, then it shouldn't be aborted. +        assertEquals(1, mDefaultHandler.activeCount()); +    } + + +    @Test +    public void testSleepTransition_empty_SyncBySleepHandler() { +        Transitions transitions = createTestTransitions(); +        transitions.replaceDefaultHandlerForTest(mDefaultHandler); + +        IBinder transitToken = new Binder(); +        transitions.requestStartTransition(transitToken, +                new TransitionRequestInfo(TRANSIT_SLEEP, null /* trigger */, null /* remote */)); + +        // Make a no-op transition +        TransitionInfo info = new TransitionInfoBuilder( +                TRANSIT_SLEEP, 0x0, true /* noOp */).build(); +        transitions.onTransitionReady(transitToken, info, new StubTransaction(), +                new StubTransaction()); + +        // If there is nothing to actually play, it should not be offered to handlers. +        assertEquals(0, mDefaultHandler.activeCount()); +    } + +    @Test      public void testMultipleTracks() {          Transitions transitions = createTestTransitions();          transitions.replaceDefaultHandlerForTest(mDefaultHandler); diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig index d0d3c5e7ece1..e672b983d509 100644 --- a/libs/hwui/aconfig/hwui_flags.aconfig +++ b/libs/hwui/aconfig/hwui_flags.aconfig @@ -20,3 +20,10 @@ flag {    description: "APIs to help enable animations involving gainmaps"    bug: "296482289"  } + +flag { +  name: "gainmap_constructor_with_metadata" +  namespace: "core_graphics" +  description: "APIs to create a new gainmap with a bitmap for metadata." +  bug: "304478551" +} diff --git a/libs/hwui/api/current.txt b/libs/hwui/api/current.txt index 794082124344..c396a2032eed 100644 --- a/libs/hwui/api/current.txt +++ b/libs/hwui/api/current.txt @@ -1,6 +1,4 @@  // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704  package android.graphics {    public class ColorMatrix { diff --git a/libs/hwui/api/module-lib-current.txt b/libs/hwui/api/module-lib-current.txt index 14191ebcb080..d802177e249b 100644 --- a/libs/hwui/api/module-lib-current.txt +++ b/libs/hwui/api/module-lib-current.txt @@ -1,3 +1 @@  // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704 diff --git a/libs/hwui/api/module-lib-removed.txt b/libs/hwui/api/module-lib-removed.txt index 14191ebcb080..d802177e249b 100644 --- a/libs/hwui/api/module-lib-removed.txt +++ b/libs/hwui/api/module-lib-removed.txt @@ -1,3 +1 @@  // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704 diff --git a/libs/hwui/api/removed.txt b/libs/hwui/api/removed.txt index 14191ebcb080..d802177e249b 100644 --- a/libs/hwui/api/removed.txt +++ b/libs/hwui/api/removed.txt @@ -1,3 +1 @@  // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704 diff --git a/libs/hwui/api/system-current.txt b/libs/hwui/api/system-current.txt index 14191ebcb080..d802177e249b 100644 --- a/libs/hwui/api/system-current.txt +++ b/libs/hwui/api/system-current.txt @@ -1,3 +1 @@  // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704 diff --git a/libs/hwui/api/system-removed.txt b/libs/hwui/api/system-removed.txt index 14191ebcb080..d802177e249b 100644 --- a/libs/hwui/api/system-removed.txt +++ b/libs/hwui/api/system-removed.txt @@ -1,3 +1 @@  // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704 diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp index 90850d36b612..22c586248705 100644 --- a/libs/hwui/renderthread/VulkanManager.cpp +++ b/libs/hwui/renderthread/VulkanManager.cpp @@ -26,6 +26,7 @@  #include <gui/TraceUtils.h>  #include <include/gpu/ganesh/SkSurfaceGanesh.h>  #include <include/gpu/ganesh/vk/GrVkBackendSurface.h> +#include <include/gpu/ganesh/vk/GrVkDirectContext.h>  #include <ui/FatVector.h>  #include <vk/GrVkExtensions.h>  #include <vk/GrVkTypes.h> @@ -435,7 +436,7 @@ sk_sp<GrDirectContext> VulkanManager::createContext(GrContextOptions& options,      options.fContextDeleteContext = this;      options.fContextDeleteProc = onGrContextReleased; -    return GrDirectContext::MakeVulkan(backendContext, options); +    return GrDirectContexts::MakeVulkan(backendContext, options);  }  VkFunctorInitParams VulkanManager::getVkFunctorInitParams() const { diff --git a/location/java/android/location/GnssSignalType.java b/location/java/android/location/GnssSignalType.java index 16c3f2ed27d8..2dd67f0c12aa 100644 --- a/location/java/android/location/GnssSignalType.java +++ b/location/java/android/location/GnssSignalType.java @@ -34,8 +34,7 @@ public final class GnssSignalType implements Parcelable {      /**       * Creates a {@link GnssSignalType} with a full list of parameters.       * -     * @param constellationType the constellation type as defined in -     * {@link GnssStatus.ConstellationType} +     * @param constellationType the constellation type       * @param carrierFrequencyHz the carrier frequency in Hz       * @param codeType the code type as defined in {@link GnssMeasurement#getCodeType()}       */ @@ -66,7 +65,7 @@ public final class GnssSignalType implements Parcelable {          this.mCodeType = codeType;      } -    /** Returns the {@link GnssStatus.ConstellationType}. */ +    /** Returns the constellation type. */      @GnssStatus.ConstellationType      public int getConstellationType() {          return mConstellationType; diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java index ceb3858eb0b3..b002bbf20c08 100644 --- a/media/java/android/media/AudioFormat.java +++ b/media/java/android/media/AudioFormat.java @@ -1282,10 +1282,8 @@ public final class AudioFormat implements Parcelable {           *    {@link AudioFormat#CHANNEL_OUT_BACK_CENTER},           *    {@link AudioFormat#CHANNEL_OUT_SIDE_LEFT},           *    {@link AudioFormat#CHANNEL_OUT_SIDE_RIGHT}. -         *    <p> For a valid {@link AudioTrack} channel position mask, -         *    the following conditions apply: -         *    <br> (1) at most eight channel positions may be used; -         *    <br> (2) right/left pairs should be matched. +         *    <p> For output or {@link AudioTrack}, channel position masks which do not contain +         *    matched left/right pairs are invalid.           *    <p> For input or {@link AudioRecord}, the mask should be           *    {@link AudioFormat#CHANNEL_IN_MONO} or           *    {@link AudioFormat#CHANNEL_IN_STEREO}.  {@link AudioFormat#CHANNEL_IN_MONO} is diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index 8c635807022b..21690904fe42 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -463,7 +463,7 @@ public final class MediaRouter2 {      /**       * Returns the current {@link RouteListingPreference} of the target router.       * -     * <p>If this instance was created using {@link #getInstance(Context, String)}, then it returns +     * <p>If this instance was created using {@code #getInstance(Context, String)}, then it returns       * the last {@link RouteListingPreference} set by the process this router was created for.       *       * @see #setRouteListingPreference(RouteListingPreference) diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java index e9a6ed4dcf08..9ced2a45a6f7 100644 --- a/media/java/android/media/audiopolicy/AudioPolicy.java +++ b/media/java/android/media/audiopolicy/AudioPolicy.java @@ -16,6 +16,7 @@  package android.media.audiopolicy; +import android.annotation.FlaggedApi;  import android.annotation.IntDef;  import android.annotation.NonNull;  import android.annotation.Nullable; @@ -48,6 +49,7 @@ import android.util.Pair;  import android.util.Slog;  import com.android.internal.annotations.GuardedBy; +import com.android.media.audio.flags.Flags;  import java.lang.annotation.Retention;  import java.lang.annotation.RetentionPolicy; @@ -417,6 +419,7 @@ public class AudioPolicy {       * @return {@link AudioManager#SUCCESS} if the update was successful,       *  {@link AudioManager#ERROR} otherwise.       */ +    @FlaggedApi(Flags.FLAG_AUDIO_POLICY_UPDATE_MIXING_RULES_API)      @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)      public int updateMixingRules(              @NonNull List<Pair<AudioMix, AudioMixingRule>> mixingRuleUpdates) { diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig index a354f9180e09..c9cfa670cf02 100644 --- a/media/java/android/media/flags/media_better_together.aconfig +++ b/media/java/android/media/flags/media_better_together.aconfig @@ -27,3 +27,10 @@ flag {      description: "Disables the broadcast receiver that prevents scanning when the screen is off."      bug: "304234628"  } + +flag { +    namespace: "media_solutions" +    name: "fallback_to_default_handling_when_media_session_has_fixed_volume_handling" +    description: "Fallbacks to the default handling for volume adjustment when media session has fixed volume handling and its app is in the foreground and setting a media controller." +    bug: "293743975" +} diff --git a/media/java/android/media/midi/MidiUmpDeviceService.java b/media/java/android/media/midi/MidiUmpDeviceService.java index 6e2aaabf4b04..c54bfce840aa 100644 --- a/media/java/android/media/midi/MidiUmpDeviceService.java +++ b/media/java/android/media/midi/MidiUmpDeviceService.java @@ -16,6 +16,9 @@  package android.media.midi; +import static com.android.media.midi.flags.Flags.FLAG_VIRTUAL_UMP; + +import android.annotation.FlaggedApi;  import android.annotation.NonNull;  import android.annotation.Nullable;  import android.app.Service; @@ -38,7 +41,7 @@ import java.util.List;   * of {@link MidiReceiver}s for sending data out the output ports.   *   * Unlike traditional MIDI byte streams, only complete UMPs should be sent. - * Unlike with {@link #MidiDeviceService}, the number of input and output ports must be equal. + * Unlike with {@link MidiDeviceService}, the number of input and output ports must be equal.   *   * <p>To extend this class, you must declare the service in your manifest file with   * an intent filter with the {@link #SERVICE_INTERFACE} action @@ -54,9 +57,11 @@ import java.util.List;   *             android:resource="@xml/device_info" />   * </service></pre>   */ +@FlaggedApi(FLAG_VIRTUAL_UMP)  public abstract class MidiUmpDeviceService extends Service {      private static final String TAG = "MidiUmpDeviceService"; +    @FlaggedApi(FLAG_VIRTUAL_UMP)      public static final String SERVICE_INTERFACE = "android.media.midi.MidiUmpDeviceService";      private IMidiManager mMidiManager; @@ -75,6 +80,7 @@ public abstract class MidiUmpDeviceService extends Service {          }      }; +    @FlaggedApi(FLAG_VIRTUAL_UMP)      @Override      public void onCreate() {          mMidiManager = IMidiManager.Stub.asInterface( @@ -112,6 +118,7 @@ public abstract class MidiUmpDeviceService extends Service {       * The number of input and output ports must be equal and non-zero.       * @return list of MidiReceivers       */ +    @FlaggedApi(FLAG_VIRTUAL_UMP)      public abstract @NonNull List<MidiReceiver> onGetInputPortReceivers();      /** @@ -120,6 +127,7 @@ public abstract class MidiUmpDeviceService extends Service {       * The number of input and output ports must be equal and non-zero.       * @return the list of MidiReceivers       */ +    @FlaggedApi(FLAG_VIRTUAL_UMP)      public final @NonNull List<MidiReceiver> getOutputPortReceivers() {          if (mServer == null) {              return new ArrayList<MidiReceiver>(); @@ -132,6 +140,7 @@ public abstract class MidiUmpDeviceService extends Service {       * Returns the {@link MidiDeviceInfo} instance for this service       * @return the MidiDeviceInfo of the virtual MIDI device if it was successfully created       */ +    @FlaggedApi(FLAG_VIRTUAL_UMP)      public final @Nullable MidiDeviceInfo getDeviceInfo() {          return mDeviceInfo;      } @@ -140,6 +149,7 @@ public abstract class MidiUmpDeviceService extends Service {       * Called to notify when the {@link MidiDeviceStatus} has changed       * @param status the current status of the MIDI device       */ +    @FlaggedApi(FLAG_VIRTUAL_UMP)      public void onDeviceStatusChanged(@NonNull MidiDeviceStatus status) {      } @@ -147,9 +157,11 @@ public abstract class MidiUmpDeviceService extends Service {       * Called to notify when the virtual MIDI device running in this service has been closed by       * all its clients       */ +    @FlaggedApi(FLAG_VIRTUAL_UMP)      public void onClose() {      } +    @FlaggedApi(FLAG_VIRTUAL_UMP)      @Override      public @Nullable IBinder onBind(@NonNull Intent intent) {          if (SERVICE_INTERFACE.equals(intent.getAction()) && mServer != null) { diff --git a/media/java/android/media/projection/IMediaProjectionManager.aidl b/media/java/android/media/projection/IMediaProjectionManager.aidl index d294601b44cc..31e65eb13926 100644 --- a/media/java/android/media/projection/IMediaProjectionManager.aidl +++ b/media/java/android/media/projection/IMediaProjectionManager.aidl @@ -156,4 +156,24 @@ interface IMediaProjectionManager {              + ".permission.MANAGE_MEDIA_PROJECTION)")      void setUserReviewGrantedConsentResult(ReviewGrantedConsentResult consentResult,              in @nullable IMediaProjection projection); + +    /** +     * Notifies system server that we are handling a particular state during the consent flow. +     * +     * <p>Only used for emitting atoms. +     * +     * @param hostUid               The uid of the process requesting consent to capture, may be an app or +     *                              SystemUI. +     * @param state                 The state that SystemUI is handling during the consent flow. +     *                              Must be a valid +     *                              state defined in the MediaProjectionState enum. +     * @param sessionCreationSource Only set if the state is MEDIA_PROJECTION_STATE_INITIATED. +     *                              Indicates the entry point for requesting the permission. Must be +     *                              a valid state defined +     *                              in the SessionCreationSource enum. +     */ +    @EnforcePermission("android.Manifest.permission.MANAGE_MEDIA_PROJECTION") +    @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest" +            + ".permission.MANAGE_MEDIA_PROJECTION)") +    oneway void notifyPermissionRequestStateChange(int hostUid, int state, int sessionCreationSource);  } diff --git a/media/tests/MediaFrameworkTest/AndroidManifest.xml b/media/tests/MediaFrameworkTest/AndroidManifest.xml index e88655894156..7d79a6c266e4 100644 --- a/media/tests/MediaFrameworkTest/AndroidManifest.xml +++ b/media/tests/MediaFrameworkTest/AndroidManifest.xml @@ -20,6 +20,7 @@      <uses-permission android:name="android.permission.RECORD_AUDIO"/>      <uses-permission android:name="android.permission.CAMERA"/>      <uses-permission android:name="android.permission.INTERNET"/> +    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />      <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>      <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>      <uses-permission android:name="android.permission.WAKE_LOCK"/> diff --git a/media/tests/MediaFrameworkTest/AndroidTest.xml b/media/tests/MediaFrameworkTest/AndroidTest.xml index 132028ce98dc..91c92cc13d1b 100644 --- a/media/tests/MediaFrameworkTest/AndroidTest.xml +++ b/media/tests/MediaFrameworkTest/AndroidTest.xml @@ -23,5 +23,6 @@          <option name="package" value="com.android.mediaframeworktest" />          <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />          <option name="hidden-api-checks" value="false"/> +        <option name="isolated-storage" value="false"/>      </test>  </configuration> diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java index 9be7004c5701..30edfa40802b 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java @@ -44,7 +44,6 @@ public class MediaFrameworkUnitTestRunner extends InstrumentationTestRunner {      @Override      public TestSuite getAllTests() {          TestSuite suite = new InstrumentationTestSuite(this); -        addMediaMetadataRetrieverStateUnitTests(suite);          addMediaRecorderStateUnitTests(suite);          addMediaPlayerStateUnitTests(suite);          addMediaScannerUnitTests(suite); @@ -70,11 +69,6 @@ public class MediaFrameworkUnitTestRunner extends InstrumentationTestRunner {      }      // Running all unit tests checking the state machine may be time-consuming. -    private void addMediaMetadataRetrieverStateUnitTests(TestSuite suite) { -        suite.addTestSuite(MediaMetadataRetrieverTest.class); -    } - -    // Running all unit tests checking the state machine may be time-consuming.      private void addMediaRecorderStateUnitTests(TestSuite suite) {          suite.addTestSuite(MediaRecorderPrepareStateUnitTest.class);          suite.addTestSuite(MediaRecorderResetStateUnitTest.class); diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java index bdca4744ff74..f70d2d1f8ae7 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java @@ -16,26 +16,34 @@  package com.android.mediaframeworktest.unit; +import static org.junit.Assert.assertTrue; +  import android.graphics.Bitmap;  import android.media.MediaMetadataRetriever; -import android.test.AndroidTestCase;  import android.test.suitebuilder.annotation.LargeTest;  import android.test.suitebuilder.annotation.MediumTest;  import android.util.Log; +import androidx.test.runner.AndroidJUnit4; +  import com.android.mediaframeworktest.MediaNames;  import com.android.mediaframeworktest.MediaProfileReader; +import org.junit.Test; +import org.junit.runner.RunWith; +  import java.io.FileOutputStream;  import java.io.IOException; -public class MediaMetadataRetrieverTest extends AndroidTestCase { +@RunWith(AndroidJUnit4.class) +public class MediaMetadataRetrieverTest {      private static final String TAG = "MediaMetadataRetrieverTest";      // Test album art extraction.      @MediumTest -    public static void testGetEmbeddedPicture() throws Exception { +    @Test +    public void testGetEmbeddedPicture() throws Exception {          Log.v(TAG, "testGetEmbeddedPicture starts.");          MediaMetadataRetriever retriever = new MediaMetadataRetriever();          boolean supportWMA = MediaProfileReader.getWMAEnable(); @@ -78,7 +86,8 @@ public class MediaMetadataRetrieverTest extends AndroidTestCase {      // Test frame capture      @LargeTest -    public static void testThumbnailCapture() throws Exception { +    @Test +    public void testThumbnailCapture() throws Exception {          MediaMetadataRetriever retriever = new MediaMetadataRetriever();          boolean supportWMA = MediaProfileReader.getWMAEnable();          boolean supportWMV = MediaProfileReader.getWMVEnable(); @@ -134,7 +143,8 @@ public class MediaMetadataRetrieverTest extends AndroidTestCase {      }      @LargeTest -    public static void testMetadataRetrieval() throws Exception { +    @Test +    public void testMetadataRetrieval() throws Exception {          boolean supportWMA = MediaProfileReader.getWMAEnable();          boolean supportWMV = MediaProfileReader.getWMVEnable();          boolean hasFailed = false; @@ -169,7 +179,8 @@ public class MediaMetadataRetrieverTest extends AndroidTestCase {      // If the specified call order and valid media file is used, no exception      // should be thrown.      @MediumTest -    public static void testBasicNormalMethodCallSequence() throws Exception { +    @Test +    public void testBasicNormalMethodCallSequence() throws Exception {          boolean hasFailed = false;          MediaMetadataRetriever retriever = new MediaMetadataRetriever();          try { @@ -197,7 +208,8 @@ public class MediaMetadataRetrieverTest extends AndroidTestCase {      // If setDataSource() has not been called, both getFrameAtTime() and extractMetadata() must      // return null.      @MediumTest -    public static void testBasicAbnormalMethodCallSequence() { +    @Test +    public void testBasicAbnormalMethodCallSequence() {          boolean hasFailed = false;          MediaMetadataRetriever retriever = new MediaMetadataRetriever();          if (retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM) != null) { @@ -213,7 +225,8 @@ public class MediaMetadataRetrieverTest extends AndroidTestCase {      // Test setDataSource()      @MediumTest -    public static void testSetDataSource() throws IOException { +    @Test +    public void testSetDataSource() throws IOException {          MediaMetadataRetriever retriever = new MediaMetadataRetriever();          boolean hasFailed = false; diff --git a/omapi/aidl/Android.bp b/omapi/aidl/Android.bp index 58bcd1d49c69..e71597a27a39 100644 --- a/omapi/aidl/Android.bp +++ b/omapi/aidl/Android.bp @@ -24,6 +24,11 @@ aidl_interface {      backend: {          java: {              sdk_version: "module_current", +            apex_available: [ +                "//apex_available:platform", +                "com.android.nfcservices", +            ], +          },          rust: {              enabled: true, diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java index 4c313b22f71e..3409c29d3c2c 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java @@ -16,6 +16,8 @@  package com.android.externalstorage; +import static java.util.regex.Pattern.CASE_INSENSITIVE; +  import android.annotation.NonNull;  import android.annotation.Nullable;  import android.app.usage.StorageStatsManager; @@ -64,7 +66,19 @@ import java.util.List;  import java.util.Locale;  import java.util.Objects;  import java.util.UUID; - +import java.util.regex.Pattern; + +/** + * Presents content of the shared (a.k.a. "external") storage. + * <p> + * Starting with Android 11 (R), restricts access to the certain sections of the shared storage: + * {@code Android/data/}, {@code Android/obb/} and {@code Android/sandbox/}, that will be hidden in + * the DocumentsUI by default. + * See <a href="https://developer.android.com/about/versions/11/privacy/storage"> + * Storage updates in Android 11</a>. + * <p> + * Documents ID format: {@code root:path/to/file}. + */  public class ExternalStorageProvider extends FileSystemProvider {      private static final String TAG = "ExternalStorage"; @@ -75,7 +89,12 @@ public class ExternalStorageProvider extends FileSystemProvider {      private static final Uri BASE_URI =              new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY).build(); -    // docId format: root:path/to/file +    /** +     * Regex for detecting {@code /Android/data/}, {@code /Android/obb/} and +     * {@code /Android/sandbox/} along with all their subdirectories and content. +     */ +    private static final Pattern PATTERN_RESTRICTED_ANDROID_SUBTREES = +            Pattern.compile("^Android/(?:data|obb|sandbox)(?:/.+)?", CASE_INSENSITIVE);      private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {              Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE, @@ -278,76 +297,91 @@ public class ExternalStorageProvider extends FileSystemProvider {          return projection != null ? projection : DEFAULT_ROOT_PROJECTION;      } +    /** +     * Mark {@code Android/data/}, {@code Android/obb/} and {@code Android/sandbox/} on the +     * integrated shared ("external") storage along with all their content and subdirectories as +     * hidden. +     */      @Override -    public Cursor queryChildDocumentsForManage( -            String parentDocId, String[] projection, String sortOrder) -            throws FileNotFoundException { -        return queryChildDocumentsShowAll(parentDocId, projection, sortOrder); +    protected boolean shouldHideDocument(@NonNull String documentId) { +        // Don't need to hide anything on USB drives. +        if (isOnRemovableUsbStorage(documentId)) { +            return false; +        } + +        final String path = getPathFromDocId(documentId); +        return PATTERN_RESTRICTED_ANDROID_SUBTREES.matcher(path).matches();      }      /**       * Check that the directory is the root of storage or blocked file from tree. +     * <p> +     * Note, that this is different from hidden documents: blocked documents <b>WILL</b> appear +     * the UI, but the user <b>WILL NOT</b> be able to select them.       * -     * @param docId the docId of the directory to be checked +     * @param documentId the docId of the directory to be checked       * @return true, should be blocked from tree. Otherwise, false. +     * +     * @see Document#FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE       */      @Override -    protected boolean shouldBlockFromTree(@NonNull String docId) { -        try { -            final File dir = getFileForDocId(docId, false /* visible */); - -            // the file is null or it is not a directory -            if (dir == null || !dir.isDirectory()) { -                return false; -            } +    protected boolean shouldBlockDirectoryFromTree(@NonNull String documentId) +            throws FileNotFoundException { +        final File dir = getFileForDocId(documentId, false); +        // The file is null or it is not a directory +        if (dir == null || !dir.isDirectory()) { +            return false; +        } -            // Allow all directories on USB, including the root. -            try { -                RootInfo rootInfo = getRootFromDocId(docId); -                if ((rootInfo.flags & Root.FLAG_REMOVABLE_USB) == Root.FLAG_REMOVABLE_USB) { -                    return false; -                } -            } catch (FileNotFoundException e) { -                Log.e(TAG, "Failed to determine rootInfo for docId"); -            } +        // Allow all directories on USB, including the root. +        if (isOnRemovableUsbStorage(documentId)) { +            return false; +        } -            final String path = getPathFromDocId(docId); +        // Get canonical(!) path. Note that this path will have neither leading nor training "/". +        // This the root's path will be just an empty string. +        final String path = getPathFromDocId(documentId); -            // Block the root of the storage -            if (path.isEmpty()) { -                return true; -            } +        // Block the root of the storage +        if (path.isEmpty()) { +            return true; +        } -            // Block Download folder from tree -            if (TextUtils.equals(Environment.DIRECTORY_DOWNLOADS.toLowerCase(Locale.ROOT), -                    path.toLowerCase(Locale.ROOT))) { -                return true; -            } +        // Block /Download/ and /Android/ folders from the tree. +        if (equalIgnoringCase(path, Environment.DIRECTORY_DOWNLOADS) || +                equalIgnoringCase(path, Environment.DIRECTORY_ANDROID)) { +            return true; +        } -            // Block /Android -            if (TextUtils.equals(Environment.DIRECTORY_ANDROID.toLowerCase(Locale.ROOT), -                    path.toLowerCase(Locale.ROOT))) { -                return true; -            } +        // This shouldn't really make a difference, but just in case - let's block hidden +        // directories as well. +        if (shouldHideDocument(documentId)) { +            return true; +        } -            // Block /Android/data, /Android/obb, /Android/sandbox and sub dirs -            if (shouldHide(dir)) { -                return true; -            } +        return false; +    } +    private boolean isOnRemovableUsbStorage(@NonNull String documentId) { +        final RootInfo rootInfo; +        try { +            rootInfo = getRootFromDocId(documentId); +        } catch (FileNotFoundException e) { +            Log.e(TAG, "Failed to determine rootInfo for docId\"" + documentId + '"');              return false; -        } catch (IOException e) { -            throw new IllegalArgumentException( -                    "Failed to determine if " + docId + " should block from tree " + ": " + e);          } + +        return (rootInfo.flags & Root.FLAG_REMOVABLE_USB) != 0;      } +    @NonNull      @Override -    protected String getDocIdForFile(File file) throws FileNotFoundException { +    protected String getDocIdForFile(@NonNull File file) throws FileNotFoundException {          return getDocIdForFileMaybeCreate(file, false);      } -    private String getDocIdForFileMaybeCreate(File file, boolean createNewDir) +    @NonNull +    private String getDocIdForFileMaybeCreate(@NonNull File file, boolean createNewDir)              throws FileNotFoundException {          String path = file.getAbsolutePath(); @@ -417,31 +451,33 @@ public class ExternalStorageProvider extends FileSystemProvider {      private File getFileForDocId(String docId, boolean visible, boolean mustExist)              throws FileNotFoundException {          RootInfo root = getRootFromDocId(docId); -        return buildFile(root, docId, visible, mustExist); +        return buildFile(root, docId, mustExist);      } -    private Pair<RootInfo, File> resolveDocId(String docId, boolean visible) -            throws FileNotFoundException { +    private Pair<RootInfo, File> resolveDocId(String docId) throws FileNotFoundException {          RootInfo root = getRootFromDocId(docId); -        return Pair.create(root, buildFile(root, docId, visible, true)); +        return Pair.create(root, buildFile(root, docId, /* mustExist */ true));      }      @VisibleForTesting -    static String getPathFromDocId(String docId) throws IOException { +    static String getPathFromDocId(String docId) {          final int splitIndex = docId.indexOf(':', 1);          final String docIdPath = docId.substring(splitIndex + 1); -        // Get CanonicalPath and remove the first "/" -        final String canonicalPath = new File(docIdPath).getCanonicalPath().substring(1); -        if (canonicalPath.isEmpty()) { -            return canonicalPath; +        // Canonicalize path and strip the leading "/" +        final String path; +        try { +            path = new File(docIdPath).getCanonicalPath().substring(1); +        } catch (IOException e) { +            Log.w(TAG, "Could not canonicalize \"" + docIdPath + '"'); +            return "";          } -        // remove trailing "/" -        if (canonicalPath.charAt(canonicalPath.length() - 1) == '/') { -            return canonicalPath.substring(0, canonicalPath.length() - 1); +        // Remove the trailing "/" as well. +        if (!path.isEmpty() && path.charAt(path.length() - 1) == '/') { +            return path.substring(0, path.length() - 1);          } else { -            return canonicalPath; +            return path;          }      } @@ -460,7 +496,7 @@ public class ExternalStorageProvider extends FileSystemProvider {          return root;      } -    private File buildFile(RootInfo root, String docId, boolean visible, boolean mustExist) +    private File buildFile(RootInfo root, String docId, boolean mustExist)              throws FileNotFoundException {          final int splitIndex = docId.indexOf(':', 1);          final String path = docId.substring(splitIndex + 1); @@ -544,7 +580,7 @@ public class ExternalStorageProvider extends FileSystemProvider {      @Override      public Path findDocumentPath(@Nullable String parentDocId, String childDocId)              throws FileNotFoundException { -        final Pair<RootInfo, File> resolvedDocId = resolveDocId(childDocId, false); +        final Pair<RootInfo, File> resolvedDocId = resolveDocId(childDocId);          final RootInfo root = resolvedDocId.first;          File child = resolvedDocId.second; @@ -648,6 +684,13 @@ public class ExternalStorageProvider extends FileSystemProvider {          }      } +    /** +     * Print the state into the given stream. +     * Gets invoked when you run: +     * <pre> +     * adb shell dumpsys activity provider com.android.externalstorage/.ExternalStorageProvider +     * </pre> +     */      @Override      public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {          final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ", 160); @@ -731,4 +774,8 @@ public class ExternalStorageProvider extends FileSystemProvider {          }          return bundle;      } + +    private static boolean equalIgnoringCase(@NonNull String a, @NonNull String b) { +        return TextUtils.equals(a.toLowerCase(Locale.ROOT), b.toLowerCase(Locale.ROOT)); +    }  } diff --git a/packages/ExternalStorageProvider/tests/src/com/android/externalstorage/ExternalStorageProviderTest.java b/packages/ExternalStorageProvider/tests/src/com/android/externalstorage/ExternalStorageProviderTest.java index 18a8edc5e447..0144b6ea9040 100644 --- a/packages/ExternalStorageProvider/tests/src/com/android/externalstorage/ExternalStorageProviderTest.java +++ b/packages/ExternalStorageProvider/tests/src/com/android/externalstorage/ExternalStorageProviderTest.java @@ -16,47 +16,64 @@  package com.android.externalstorage; +import static android.provider.DocumentsContract.EXTERNAL_STORAGE_PRIMARY_EMULATED_ROOT_ID; +  import static com.android.externalstorage.ExternalStorageProvider.AUTHORITY;  import static com.android.externalstorage.ExternalStorageProvider.getPathFromDocId;  import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse;  import static org.junit.Assert.assertTrue;  import static org.mockito.Mockito.atLeast;  import static org.mockito.Mockito.spy;  import static org.mockito.Mockito.verify; +import android.app.Instrumentation; +import android.content.Context;  import android.content.pm.ProviderInfo; +import androidx.annotation.NonNull;  import androidx.test.InstrumentationRegistry;  import androidx.test.runner.AndroidJUnit4; +import org.junit.Before;  import org.junit.Test;  import org.junit.runner.RunWith;  @RunWith(AndroidJUnit4.class)  public class ExternalStorageProviderTest { + +    @NonNull +    private static final Instrumentation sInstrumentation = +            InstrumentationRegistry.getInstrumentation(); +    @NonNull +    private static final Context sTargetContext = sInstrumentation.getTargetContext(); + +    private ExternalStorageProvider mExternalStorageProvider; + +    @Before +    public void setUp() { +        mExternalStorageProvider = new ExternalStorageProvider(); +    } + +      @Test -    public void onCreate_shouldUpdateVolumes() throws Exception { -        ExternalStorageProvider externalStorageProvider = new ExternalStorageProvider(); -        ExternalStorageProvider spyProvider = spy(externalStorageProvider); -        ProviderInfo providerInfo = new ProviderInfo(); +    public void onCreate_shouldUpdateVolumes() { +        final ExternalStorageProvider spyProvider = spy(mExternalStorageProvider); + +        final ProviderInfo providerInfo = new ProviderInfo();          providerInfo.authority = AUTHORITY;          providerInfo.grantUriPermissions = true;          providerInfo.exported = true; -        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { -            @Override -            public void run() { -                spyProvider.attachInfoForTesting( -                        InstrumentationRegistry.getTargetContext(), providerInfo); -            } -        }); +        sInstrumentation.runOnMainSync(() -> +                spyProvider.attachInfoForTesting(sTargetContext, providerInfo));          verify(spyProvider, atLeast(1)).updateVolumes();      }      @Test -    public void testGetPathFromDocId() throws Exception { +    public void test_getPathFromDocId() {          final String root = "root";          final String path = "abc/def/ghi";          String docId = root + ":" + path; @@ -79,4 +96,62 @@ public class ExternalStorageProviderTest {          docId = root + ":" + twoDotPath;          assertEquals(getPathFromDocId(docId), path);      } + +    @Test +    public void test_shouldHideDocument() { +        // Should hide "Android/data", "Android/obb", "Android/sandbox" and all their +        // "subtrees". +        final String[] shouldHide = { +                // "Android/data" and all its subdirectories +                "Android/data", +                "Android/data/com.my.app", +                "Android/data/com.my.app/cache", +                "Android/data/com.my.app/cache/image.png", +                "Android/data/mydata", + +                // "Android/obb" and all its subdirectories +                "Android/obb", +                "Android/obb/com.my.app", +                "Android/obb/com.my.app/file.blob", + +                // "Android/sandbox" and all its subdirectories +                "Android/sandbox", +                "Android/sandbox/com.my.app", + +                // Also make sure we are not allowing path traversals +                "Android/./data", +                "Android/Download/../data", +        }; +        for (String path : shouldHide) { +            final String docId = buildDocId(path); +            assertTrue("ExternalStorageProvider should hide \"" + docId + "\", but it didn't", +                    mExternalStorageProvider.shouldHideDocument(docId)); +        } + +        // Should NOT hide anything else. +        final String[] shouldNotHide = { +                "Android", +                "Android/datadir", +                "Documents", +                "Download", +                "Music", +                "Pictures", +        }; +        for (String path : shouldNotHide) { +            final String docId = buildDocId(path); +            assertFalse("ExternalStorageProvider should NOT hide \"" + docId + "\", but it did", +                    mExternalStorageProvider.shouldHideDocument(docId)); +        } +    } + +    @NonNull +    private static String buildDocId(@NonNull String path) { +        return buildDocId(EXTERNAL_STORAGE_PRIMARY_EMULATED_ROOT_ID, path); +    } + +    @NonNull +    private static String buildDocId(@NonNull String root, @NonNull String path) { +        // docId format: root:path/to/file +        return root + ':' + path; +    }  } diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts index 1cc2867ecf0e..0b7a568dd2f2 100644 --- a/packages/SettingsLib/Spa/build.gradle.kts +++ b/packages/SettingsLib/Spa/build.gradle.kts @@ -26,7 +26,7 @@ plugins {  }  allprojects { -    extra["jetpackComposeVersion"] = "1.6.0-alpha02" +    extra["jetpackComposeVersion"] = "1.6.0-alpha07"  }  subprojects { diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonsPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonPageProvider.kt index df1d7d18555a..b001caddd000 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonsPage.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonPageProvider.kt @@ -18,8 +18,8 @@ package com.android.settingslib.spa.gallery.button  import android.os.Bundle  import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.outlined.Launch  import androidx.compose.material.icons.outlined.Delete -import androidx.compose.material.icons.outlined.Launch  import androidx.compose.material.icons.outlined.WarningAmber  import androidx.compose.runtime.Composable  import androidx.compose.ui.tooling.preview.Preview @@ -47,7 +47,7 @@ object ActionButtonPageProvider : SettingsPageProvider {      override fun Page(arguments: Bundle?) {          RegularScaffold(title = TITLE) {              val actionButtons = listOf( -                ActionButton(text = "Open", imageVector = Icons.Outlined.Launch) {}, +                ActionButton(text = "Open", imageVector = Icons.AutoMirrored.Outlined.Launch) {},                  ActionButton(text = "Uninstall", imageVector = Icons.Outlined.Delete) {},                  ActionButton(text = "Force stop", imageVector = Icons.Outlined.WarningAmber) {},              ) diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml index 8b563362a77b..aafae5f2129b 100644 --- a/packages/SettingsLib/Spa/gradle/libs.versions.toml +++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml @@ -15,11 +15,11 @@  #  [versions] -agp = "8.1.1" +agp = "8.1.2"  compose-compiler = "1.5.1"  dexmaker-mockito = "2.28.3"  kotlin = "1.9.0" -truth = "1.1" +truth = "1.1.5"  [libraries]  dexmaker-mockito = { module = "com.linkedin.dexmaker:dexmaker-mockito", version.ref = "dexmaker-mockito" } diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties index da04f426c68f..ce89de6ffd65 100644 --- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties +++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties @@ -16,7 +16,7 @@  distributionBase=GRADLE_USER_HOME  distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip  networkTimeout=10000  validateDistributionUrl=true  zipStoreBase=GRADLE_USER_HOME diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts index b8105113af7b..b73bbd8b80e1 100644 --- a/packages/SettingsLib/Spa/spa/build.gradle.kts +++ b/packages/SettingsLib/Spa/spa/build.gradle.kts @@ -57,13 +57,13 @@ dependencies {      api("androidx.slice:slice-builders:1.1.0-alpha02")      api("androidx.slice:slice-core:1.1.0-alpha02")      api("androidx.slice:slice-view:1.1.0-alpha02") -    api("androidx.compose.material3:material3:1.2.0-alpha04") +    api("androidx.compose.material3:material3:1.2.0-alpha09")      api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion")      api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion")      api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")      api("androidx.lifecycle:lifecycle-livedata-ktx")      api("androidx.lifecycle:lifecycle-runtime-compose") -    api("androidx.navigation:navigation-compose:2.7.1") +    api("androidx.navigation:navigation-compose:2.7.4")      api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha")      api("com.google.android.material:material:1.7.0-alpha03")      debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion") diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt index c66e20a335ef..8c862d401c6b 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt @@ -22,5 +22,5 @@ import androidx.compose.ui.unit.dp  object SettingsShape {      val CornerMedium = RoundedCornerShape(12.dp) -    val CornerLarge = RoundedCornerShape(24.dp) +    val CornerExtraLarge = RoundedCornerShape(28.dp)  } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt index 1ad075c11985..979cf3bddae6 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt @@ -65,7 +65,7 @@ fun ActionButtons(actionButtons: List<ActionButton>) {      Row(          Modifier              .padding(SettingsDimension.buttonPadding) -            .clip(SettingsShape.CornerLarge) +            .clip(SettingsShape.CornerExtraLarge)              .height(IntrinsicSize.Min)      ) {          for ((index, actionButton) in actionButtons.withIndex()) { diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt index 3e04b16f08cf..0c16c8bc7229 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt @@ -39,7 +39,7 @@ fun MainSwitchPreference(model: SwitchPreferenceModel) {                  true -> MaterialTheme.colorScheme.primaryContainer                  else -> MaterialTheme.colorScheme.secondaryContainer              }, -            shape = SettingsShape.CornerLarge, +            shape = SettingsShape.CornerExtraLarge,          ) {              InternalSwitchPreference(                  title = model.title, diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt index 6ef45900a103..5f320f7ade3f 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt @@ -18,19 +18,14 @@ package com.android.settingslib.spa.widget.scaffold  import androidx.appcompat.R  import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.outlined.ArrowBack  import androidx.compose.material.icons.outlined.Clear  import androidx.compose.material.icons.outlined.FindInPage  import androidx.compose.material3.Icon  import androidx.compose.material3.IconButton  import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.composed -import androidx.compose.ui.draw.scale -import androidx.compose.ui.platform.LocalLayoutDirection  import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.LayoutDirection  import com.android.settingslib.spa.framework.compose.LocalNavController -import androidx.compose.material.icons.automirrored.outlined.ArrowBack  /** Action that navigates back to last page. */  @Composable @@ -55,7 +50,6 @@ private fun BackAction(contentDescription: String, onClick: () -> Unit) {          Icon(              imageVector = Icons.AutoMirrored.Outlined.ArrowBack,              contentDescription = contentDescription, -            modifier = Modifier.autoMirrored(),          )      }  } @@ -81,10 +75,3 @@ internal fun ClearAction(onClick: () -> Unit) {          )      }  } - -private fun Modifier.autoMirrored() = composed { -    when (LocalLayoutDirection.current) { -        LayoutDirection.Rtl -> scale(scaleX = -1f, scaleY = 1f) -        else -> this -    } -} diff --git a/packages/SettingsLib/Spa/testutils/build.gradle.kts b/packages/SettingsLib/Spa/testutils/build.gradle.kts index 50243dcd8c9b..cce82354a51f 100644 --- a/packages/SettingsLib/Spa/testutils/build.gradle.kts +++ b/packages/SettingsLib/Spa/testutils/build.gradle.kts @@ -41,7 +41,7 @@ dependencies {      api("androidx.arch.core:core-testing:2.2.0-alpha01")      api("androidx.compose.ui:ui-test-junit4:$jetpackComposeVersion")      api("androidx.lifecycle:lifecycle-runtime-testing") -    api("org.mockito.kotlin:mockito-kotlin:5.1.0") +    api("org.mockito.kotlin:mockito-kotlin:2.2.11")      api("org.mockito:mockito-core") {          version {              strictly("2.28.2") diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt index f54de1514fcf..09cb98e22af3 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt @@ -22,6 +22,8 @@ import android.os.UserHandle  import android.os.UserManager  import androidx.compose.runtime.Composable  import androidx.compose.runtime.State +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext  import androidx.lifecycle.compose.collectAsStateWithLifecycle  import com.android.settingslib.RestrictedLockUtils  import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin @@ -32,15 +34,15 @@ import kotlinx.coroutines.flow.flowOn  import com.android.settingslib.widget.restricted.R  data class Restrictions( -    val userId: Int, +    val userId: Int = UserHandle.myUserId(),      val keys: List<String>,  )  sealed interface RestrictedMode -object NoRestricted : RestrictedMode +data object NoRestricted : RestrictedMode -object BaseUserRestricted : RestrictedMode +data object BaseUserRestricted : RestrictedMode  interface BlockedByAdmin : RestrictedMode {      fun getSummary(checked: Boolean?): String @@ -79,6 +81,17 @@ interface RestrictionsProvider {  typealias RestrictionsProviderFactory = (Context, Restrictions) -> RestrictionsProvider +@Composable +internal fun RestrictionsProviderFactory.rememberRestrictedMode( +    restrictions: Restrictions, +): State<RestrictedMode?> { +    val context = LocalContext.current +    val restrictionsProvider = remember(restrictions) { +        this(context, restrictions) +    } +    return restrictionsProvider.restrictedModeState() +} +  internal class RestrictionsProviderImpl(      private val context: Context,      private val restrictions: Restrictions, diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt index 1fa854a4c09e..17e970845f58 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt @@ -45,6 +45,7 @@ import com.android.settingslib.spaprivileged.model.app.userId  import com.android.settingslib.spaprivileged.model.enterprise.Restrictions  import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory  import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl +import com.android.settingslib.spaprivileged.model.enterprise.rememberRestrictedMode  import com.android.settingslib.spaprivileged.template.preference.RestrictedSwitchPreference  import kotlinx.coroutines.flow.Flow @@ -149,14 +150,13 @@ internal class TogglePermissionInternalAppListModel<T : AppRecord>(      @Composable      fun getSummary(record: T): State<String> { -        val restrictionsProvider = remember(record.app.userId) { -            val restrictions = Restrictions( +        val restrictions = remember(record.app.userId) { +            Restrictions(                  userId = record.app.userId,                  keys = listModel.switchRestrictionKeys,              ) -            restrictionsProviderFactory(context, restrictions)          } -        val restrictedMode = restrictionsProvider.restrictedModeState() +        val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions)          val allowed = listModel.isAllowed(record)          return remember {              derivedStateOf { diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreference.kt new file mode 100644 index 000000000000..50490c0b887d --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreference.kt @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spaprivileged.template.preference + +import androidx.annotation.VisibleForTesting +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.semantics.Role +import com.android.settingslib.spa.framework.compose.stateOf +import com.android.settingslib.spa.widget.preference.Preference +import com.android.settingslib.spa.widget.preference.PreferenceModel +import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted +import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdmin +import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted +import com.android.settingslib.spaprivileged.model.enterprise.RestrictedMode +import com.android.settingslib.spaprivileged.model.enterprise.Restrictions +import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory +import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl +import com.android.settingslib.spaprivileged.model.enterprise.rememberRestrictedMode + +@Composable +fun RestrictedPreference( +    model: PreferenceModel, +    restrictions: Restrictions, +) { +    RestrictedPreference(model, restrictions, ::RestrictionsProviderImpl) +} + +@VisibleForTesting +@Composable +internal fun RestrictedPreference( +    model: PreferenceModel, +    restrictions: Restrictions, +    restrictionsProviderFactory: RestrictionsProviderFactory, +) { +    if (restrictions.keys.isEmpty()) { +        Preference(model) +        return +    } +    val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions).value +    val restrictedSwitchModel = remember(restrictedMode) { +        RestrictedPreferenceModel(model, restrictedMode) +    } +    restrictedSwitchModel.RestrictionWrapper { +        Preference(restrictedSwitchModel) +    } +} + +private class RestrictedPreferenceModel( +    model: PreferenceModel, +    private val restrictedMode: RestrictedMode?, +) : PreferenceModel { +    override val title = model.title +    override val summary = model.summary +    override val icon = model.icon + +    override val enabled = when (restrictedMode) { +        NoRestricted -> model.enabled +        else -> stateOf(false) +    } + +    override val onClick = when (restrictedMode) { +        NoRestricted -> model.onClick +        // Need to passthrough onClick for clickable semantics, although since enabled is false so +        // this will not be called. +        BaseUserRestricted -> model.onClick +        else -> null +    } + +    @Composable +    fun RestrictionWrapper(content: @Composable () -> Unit) { +        if (restrictedMode !is BlockedByAdmin) { +            content() +            return +        } +        Box( +            Modifier +                .clickable( +                    role = Role.Button, +                    onClick = { restrictedMode.sendShowAdminSupportDetailsIntent() }, +                ) +        ) { content() } +    } +} diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt index e77dcd4d9cc4..2129403c2d85 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt @@ -17,6 +17,7 @@  package com.android.settingslib.spaprivileged.template.preference  import android.content.Context +import androidx.annotation.VisibleForTesting  import androidx.compose.foundation.clickable  import androidx.compose.foundation.layout.Box  import androidx.compose.runtime.Composable @@ -40,22 +41,29 @@ import com.android.settingslib.spaprivileged.model.enterprise.RestrictedMode  import com.android.settingslib.spaprivileged.model.enterprise.Restrictions  import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory  import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl +import com.android.settingslib.spaprivileged.model.enterprise.rememberRestrictedMode  @Composable  fun RestrictedSwitchPreference(      model: SwitchPreferenceModel,      restrictions: Restrictions, -    restrictionsProviderFactory: RestrictionsProviderFactory = ::RestrictionsProviderImpl, +) { +    RestrictedSwitchPreference(model, restrictions, ::RestrictionsProviderImpl) +} + +@VisibleForTesting +@Composable +internal fun RestrictedSwitchPreference( +    model: SwitchPreferenceModel, +    restrictions: Restrictions, +    restrictionsProviderFactory: RestrictionsProviderFactory,  ) {      if (restrictions.keys.isEmpty()) {          SwitchPreference(model)          return      }      val context = LocalContext.current -    val restrictionsProvider = remember(restrictions) { -        restrictionsProviderFactory(context, restrictions) -    } -    val restrictedMode = restrictionsProvider.restrictedModeState().value +    val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions).value      val restrictedSwitchModel = remember(restrictedMode) {          RestrictedSwitchPreferenceModel(context, model, restrictedMode)      } @@ -112,8 +120,8 @@ private class RestrictedSwitchPreferenceModel(      override val onCheckedChange = when (restrictedMode) {          null -> null          is NoRestricted -> model.onCheckedChange -        // Need to pass a non null onCheckedChange to enable semantics ToggleableState, although -        // since changeable is false this will not be called. +        // Need to passthrough onCheckedChange for toggleable semantics, although since changeable +        // is false so this will not be called.          is BaseUserRestricted -> model.onCheckedChange          // Pass null since semantics ToggleableState is provided in RestrictionWrapper.          is BlockedByAdmin -> null diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt index 86b6f027997d..f9abefc11e24 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt @@ -16,15 +16,15 @@  package com.android.settingslib.spaprivileged.template.scaffold +import androidx.annotation.VisibleForTesting  import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.platform.LocalContext  import com.android.settingslib.spa.widget.scaffold.MoreOptionsScope  import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted  import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdmin  import com.android.settingslib.spaprivileged.model.enterprise.Restrictions  import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory  import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl +import com.android.settingslib.spaprivileged.model.enterprise.rememberRestrictedMode  @Composable  fun MoreOptionsScope.RestrictedMenuItem( @@ -35,6 +35,7 @@ fun MoreOptionsScope.RestrictedMenuItem(      RestrictedMenuItemImpl(text, restrictions, onClick, ::RestrictionsProviderImpl)  } +@VisibleForTesting  @Composable  internal fun MoreOptionsScope.RestrictedMenuItemImpl(      text: String, @@ -42,12 +43,8 @@ internal fun MoreOptionsScope.RestrictedMenuItemImpl(      onClick: () -> Unit,      restrictionsProviderFactory: RestrictionsProviderFactory,  ) { -    val context = LocalContext.current -    val restrictionsProvider = remember(restrictions) { -        restrictionsProviderFactory(context, restrictions) -    } -    val restrictedMode = restrictionsProvider.restrictedModeState().value -    MenuItem(text = text, enabled = restrictedMode !is BaseUserRestricted) { +    val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions).value +    MenuItem(text = text, enabled = restrictedMode !== BaseUserRestricted) {          when (restrictedMode) {              is BlockedByAdmin -> restrictedMode.sendShowAdminSupportDetailsIntent()              else -> onClick() diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreferenceTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreferenceTest.kt new file mode 100644 index 000000000000..eadf0ca0686d --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedPreferenceTest.kt @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spaprivileged.template.preference + +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.assertIsEnabled +import androidx.compose.ui.test.assertIsNotEnabled +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.onRoot +import androidx.compose.ui.test.performClick +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settingslib.spa.widget.preference.PreferenceModel +import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted +import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted +import com.android.settingslib.spaprivileged.model.enterprise.Restrictions +import com.android.settingslib.spaprivileged.tests.testutils.FakeBlockedByAdmin +import com.android.settingslib.spaprivileged.tests.testutils.FakeRestrictionsProvider +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class RestrictedPreferenceTest { +    @get:Rule +    val composeTestRule = createComposeRule() + +    private val fakeBlockedByAdmin = FakeBlockedByAdmin() + +    private val fakeRestrictionsProvider = FakeRestrictionsProvider() + +    private var clicked = false + +    private val preferenceModel = object : PreferenceModel { +        override val title = TITLE +        override val onClick = { clicked = true } +    } + +    @Test +    fun whenRestrictionsKeysIsEmpty_enabled() { +        val restrictions = Restrictions(userId = USER_ID, keys = emptyList()) + +        setContent(restrictions) + +        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled() +    } + +    @Test +    fun whenRestrictionsKeysIsEmpty_clickable() { +        val restrictions = Restrictions(userId = USER_ID, keys = emptyList()) + +        setContent(restrictions) +        composeTestRule.onRoot().performClick() + +        assertThat(clicked).isTrue() +    } + +    @Test +    fun whenNoRestricted_enabled() { +        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY)) +        fakeRestrictionsProvider.restrictedMode = NoRestricted + +        setContent(restrictions) + +        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled() +    } + +    @Test +    fun whenNoRestricted_clickable() { +        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY)) +        fakeRestrictionsProvider.restrictedMode = NoRestricted + +        setContent(restrictions) +        composeTestRule.onRoot().performClick() + +        assertThat(clicked).isTrue() +    } + +    @Test +    fun whenBaseUserRestricted_disabled() { +        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY)) +        fakeRestrictionsProvider.restrictedMode = BaseUserRestricted + +        setContent(restrictions) + +        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsNotEnabled() +    } + +    @Test +    fun whenBaseUserRestricted_notClickable() { +        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY)) +        fakeRestrictionsProvider.restrictedMode = BaseUserRestricted + +        setContent(restrictions) +        composeTestRule.onRoot().performClick() + +        assertThat(clicked).isFalse() +    } + +    @Test +    fun whenBlockedByAdmin_widgetInEnableStateToAllowClick() { +        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY)) +        fakeRestrictionsProvider.restrictedMode = fakeBlockedByAdmin + +        setContent(restrictions) + +        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled() +    } + +    @Test +    fun whenBlockedByAdmin_click() { +        val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY)) +        fakeRestrictionsProvider.restrictedMode = fakeBlockedByAdmin + +        setContent(restrictions) +        composeTestRule.onRoot().performClick() + +        assertThat(fakeBlockedByAdmin.sendShowAdminSupportDetailsIntentIsCalled).isTrue() +    } + +    private fun setContent(restrictions: Restrictions) { +        composeTestRule.setContent { +            RestrictedPreference(preferenceModel, restrictions) { _, _ -> +                fakeRestrictionsProvider +            } +        } +    } + +    private companion object { +        const val TITLE = "Title" +        const val USER_ID = 0 +        const val RESTRICTION_KEY = "restriction_key" +    } +} diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowColorDisplayManager.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowColorDisplayManager.java new file mode 100644 index 000000000000..a9fd380c2733 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowColorDisplayManager.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.testutils.shadow; + +import android.Manifest; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.hardware.display.ColorDisplayManager; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +@Implements(ColorDisplayManager.class) +public class ShadowColorDisplayManager extends org.robolectric.shadows.ShadowColorDisplayManager { + +    private boolean mIsReduceBrightColorsActivated; + +    @Implementation +    @SystemApi +    @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) +    public boolean setReduceBrightColorsActivated(boolean activated) { +        mIsReduceBrightColorsActivated = activated; +        return true; +    } + +    @Implementation +    @SystemApi +    public boolean isReduceBrightColorsActivated() { +        return mIsReduceBrightColorsActivated; +    } + +} diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index f65f5a3c9297..9d3200dc340d 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -62,6 +62,7 @@ systemui_compose_java_defaults {              static_libs: [                  "CommunalLayoutLib",                  "PlatformComposeCore", +                "PlatformComposeSceneTransitionLayout",                  "androidx.compose.runtime_runtime",                  "androidx.compose.material3_material3", @@ -164,7 +165,6 @@ android_library {          "SystemUISharedLib",          "SystemUI-statsd",          "SettingsLib", -        "com_android_systemui_communal_flags_lib",          "com_android_systemui_flags_lib",          "androidx.core_core-ktx",          "androidx.viewpager2_viewpager2", @@ -243,9 +243,6 @@ filegroup {          "tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt",          "tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt", -        /* Log fakes */ -        "tests/src/com/android/systemui/log/core/FakeLogBuffer.kt", -          /* QS fakes */          "tests/src/com/android/systemui/qs/pipeline/domain/interactor/FakeQSTile.kt",      ], @@ -437,6 +434,7 @@ android_library {          "SystemUI-statsd",          "SettingsLib",          "com_android_systemui_flags_lib", +        "flag-junit-base",          "androidx.viewpager2_viewpager2",          "androidx.legacy_legacy-support-v4",          "androidx.recyclerview_recyclerview", diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 9bfc4be0f30d..58816310d495 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -83,6 +83,7 @@      <uses-permission android:name="android.permission.PEERS_MAC_ADDRESS"/>      <uses-permission android:name="android.permission.READ_WIFI_CREDENTIAL"/>      <uses-permission android:name="android.permission.LOCATION_HARDWARE" /> +    <uses-permission android:name="android.permission.NETWORK_FACTORY" />      <!-- Physical hardware -->      <uses-permission android:name="android.permission.MANAGE_USB" />      <uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS" /> @@ -1062,5 +1063,9 @@              <meta-data android:name="androidx.emoji2.text.EmojiCompatInitializer"                  tools:node="remove" />          </provider> + +        <!-- Allow SystemUI to listen for the capabilities defined in the linked xml --> +        <property android:name="android.net.PROPERTY_SELF_CERTIFIED_CAPABILITIES" +                  android:value="@xml/self_certified_network_capabilities_both" />      </application>  </manifest> diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING index 0623d4a4b7fb..03f7c9968a1d 100644 --- a/packages/SystemUI/TEST_MAPPING +++ b/packages/SystemUI/TEST_MAPPING @@ -144,9 +144,32 @@            "exclude-annotation": "androidx.test.filters.FlakyTest"          },          { +          "exclude-annotation": "android.platform.test.annotations.FlakyTest" +        }, +        {            "exclude-annotation": "android.platform.test.annotations.Postsubmit"          }        ]      } +  ], +  // v2/sysui/suite/test-mapping-sysui-screenshot-test-staged +  "sysui-screenshot-test-staged": [ +    { +      "name": "SystemUIGoogleScreenshotTests", +      "options": [ +        { +          "exclude-annotation": "org.junit.Ignore" +        }, +        { +          "include-annotation": "androidx.test.filters.FlakyTest" +        }, +        { +          "include-annotation": "android.platform.test.annotations.FlakyTest" +        }, +        { +          "include-annotation": "android.platform.test.annotations.Postsubmit" +        } +      ] +    }    ]  } diff --git a/packages/SystemUI/aconfig/Android.bp b/packages/SystemUI/aconfig/Android.bp index b18c7900a168..dc4208e87207 100644 --- a/packages/SystemUI/aconfig/Android.bp +++ b/packages/SystemUI/aconfig/Android.bp @@ -2,8 +2,7 @@ aconfig_declarations {      name: "com_android_systemui_flags",      package: "com.android.systemui",      srcs: [ -        "systemui.aconfig", -        "accessibility.aconfig", +        "*.aconfig",      ],  } @@ -11,16 +10,3 @@ java_aconfig_library {      name: "com_android_systemui_flags_lib",      aconfig_declarations: "com_android_systemui_flags",  } - -aconfig_declarations { -    name: "com_android_systemui_communal_flags", -    package: "com.android.systemui.communal", -    srcs: [ -        "communal.aconfig", -    ], -} - -java_aconfig_library { -    name: "com_android_systemui_communal_flags_lib", -    aconfig_declarations: "com_android_systemui_communal_flags", -} diff --git a/packages/SystemUI/aconfig/communal.aconfig b/packages/SystemUI/aconfig/communal.aconfig index 8ecb9842ddc5..2c6ff979cc7f 100644 --- a/packages/SystemUI/aconfig/communal.aconfig +++ b/packages/SystemUI/aconfig/communal.aconfig @@ -1,4 +1,4 @@ -package: "com.android.systemui.communal" +package: "com.android.systemui"  flag {      name: "communal_hub" diff --git a/packages/SystemUI/communal/layout/tests/Android.bp b/packages/SystemUI/communal/layout/tests/Android.bp index a60b1de5b501..9a05504cad8b 100644 --- a/packages/SystemUI/communal/layout/tests/Android.bp +++ b/packages/SystemUI/communal/layout/tests/Android.bp @@ -32,7 +32,7 @@ android_test {          "mockito-target-extended-minus-junit4",          "platform-test-annotations",          "testables", -        "truth-prebuilt", +        "truth",      ],      libs: [          "android.test.mock", diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt index 3d670b809d15..c6e429a79e86 100644 --- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt +++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt @@ -22,6 +22,7 @@ import android.view.View  import android.view.WindowInsets  import androidx.activity.ComponentActivity  import androidx.lifecycle.LifecycleOwner +import com.android.systemui.communal.ui.viewmodel.CommunalViewModel  import com.android.systemui.people.ui.viewmodel.PeopleViewModel  import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel  import com.android.systemui.scene.shared.model.Scene @@ -66,6 +67,7 @@ object ComposeFacade : BaseComposeFacade {      override fun createCommunalView(          context: Context, +        viewModel: CommunalViewModel,      ): View {          throwComposeUnavailableError()      } diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt index 7b11ac7f4e1e..1722685f4287 100644 --- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt +++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt @@ -31,6 +31,7 @@ import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation  import com.android.systemui.common.ui.compose.windowinsets.DisplayCutout  import com.android.systemui.common.ui.compose.windowinsets.DisplayCutoutProvider  import com.android.systemui.communal.ui.compose.CommunalHub +import com.android.systemui.communal.ui.viewmodel.CommunalViewModel  import com.android.systemui.people.ui.compose.PeopleScreen  import com.android.systemui.people.ui.viewmodel.PeopleViewModel  import com.android.systemui.qs.footer.ui.compose.FooterActions @@ -96,8 +97,11 @@ object ComposeFacade : BaseComposeFacade {      override fun createCommunalView(          context: Context, +        viewModel: CommunalViewModel,      ): View { -        return ComposeView(context).apply { setContent { PlatformTheme { CommunalHub() } } } +        return ComposeView(context).apply { +            setContent { PlatformTheme { CommunalHub(viewModel = viewModel) } } +        }      }      // TODO(b/298525212): remove once Compose exposes window inset bounds. diff --git a/packages/SystemUI/compose/features/Android.bp b/packages/SystemUI/compose/features/Android.bp index e4426fe97859..16c24375d14f 100644 --- a/packages/SystemUI/compose/features/Android.bp +++ b/packages/SystemUI/compose/features/Android.bp @@ -31,8 +31,10 @@ android_library {      ],      static_libs: [ +        "CommunalLayoutLib",          "SystemUI-core",          "PlatformComposeCore", +        "PlatformComposeSceneTransitionLayout",          "androidx.compose.runtime_runtime",          "androidx.compose.animation_animation-graphics", diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt index 4d2978df7b1b..3d827fb5c9a6 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt @@ -3,20 +3,63 @@ package com.android.systemui.communal.ui.compose  import androidx.compose.foundation.background  import androidx.compose.foundation.layout.Box  import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material3.Text +import androidx.compose.material3.Card  import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue  import androidx.compose.ui.Alignment  import androidx.compose.ui.Modifier  import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.integerResource +import com.android.systemui.communal.layout.ui.compose.CommunalGridLayout +import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutCard +import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutConfig +import com.android.systemui.communal.ui.viewmodel.CommunalViewModel +import com.android.systemui.res.R  @Composable -fun CommunalHub(modifier: Modifier = Modifier) { +fun CommunalHub( +    modifier: Modifier = Modifier, +    viewModel: CommunalViewModel, +) { +    val showTutorial by viewModel.showTutorialContent.collectAsState(initial = false)      Box(          modifier = modifier.fillMaxSize().background(Color.White),      ) { -        Text( -            modifier = Modifier.align(Alignment.Center), -            text = "Hello Communal!", +        CommunalGridLayout( +            modifier = Modifier.align(Alignment.CenterStart), +            layoutConfig = +                CommunalGridLayoutConfig( +                    gridColumnSize = dimensionResource(R.dimen.communal_grid_column_size), +                    gridGutter = dimensionResource(R.dimen.communal_grid_gutter_size), +                    gridHeight = dimensionResource(R.dimen.communal_grid_height), +                    gridColumnsPerCard = integerResource(R.integer.communal_grid_columns_per_card), +                ), +            communalCards = if (showTutorial) tutorialContent else emptyList(),          )      }  } + +private val tutorialContent = +    listOf( +        tutorialCard(CommunalGridLayoutCard.Size.FULL), +        tutorialCard(CommunalGridLayoutCard.Size.THIRD), +        tutorialCard(CommunalGridLayoutCard.Size.THIRD), +        tutorialCard(CommunalGridLayoutCard.Size.THIRD), +        tutorialCard(CommunalGridLayoutCard.Size.HALF), +        tutorialCard(CommunalGridLayoutCard.Size.HALF), +        tutorialCard(CommunalGridLayoutCard.Size.HALF), +        tutorialCard(CommunalGridLayoutCard.Size.HALF), +    ) + +private fun tutorialCard(size: CommunalGridLayoutCard.Size): CommunalGridLayoutCard { +    return object : CommunalGridLayoutCard() { +        override val supportedSizes = listOf(size) + +        @Composable +        override fun Content(modifier: Modifier) { +            Card(modifier = modifier, content = {}) +        } +    } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt index d1c12ac85cc5..f3bef7bf47da 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt @@ -19,6 +19,7 @@ package com.android.systemui.communal.ui.compose  import androidx.compose.runtime.Composable  import androidx.compose.ui.Modifier  import com.android.compose.animation.scene.SceneScope +import com.android.systemui.communal.ui.viewmodel.CommunalViewModel  import com.android.systemui.dagger.SysUISingleton  import com.android.systemui.scene.shared.model.Direction  import com.android.systemui.scene.shared.model.SceneKey @@ -32,7 +33,11 @@ import kotlinx.coroutines.flow.asStateFlow  /** The communal scene shows glanceable hub when the device is locked and docked. */  @SysUISingleton -class CommunalScene @Inject constructor() : ComposableScene { +class CommunalScene +@Inject +constructor( +    private val viewModel: CommunalViewModel, +) : ComposableScene {      override val key = SceneKey.Communal      override val destinationScenes: StateFlow<Map<UserAction, SceneModel>> = @@ -45,6 +50,6 @@ class CommunalScene @Inject constructor() : ComposableScene {      @Composable      override fun SceneScope.Content(modifier: Modifier) { -        CommunalHub(modifier) +        CommunalHub(modifier, viewModel)      }  } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt index 591fa76f423f..4bbb78b69392 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt @@ -371,7 +371,8 @@ private fun StatusIcons(              val iconContainer = StatusIconContainer(context, null)              val iconManager = createTintedIconManager(iconContainer, StatusBarLocation.QS)              iconManager.setTint( -                Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary) +                Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary), +                Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimaryInverse),              )              statusBarIconController.addIconGroup(iconManager) diff --git a/packages/SystemUI/compose/scene/Android.bp b/packages/SystemUI/compose/scene/Android.bp new file mode 100644 index 000000000000..050d1d5651ad --- /dev/null +++ b/packages/SystemUI/compose/scene/Android.bp @@ -0,0 +1,39 @@ +// Copyright (C) 2023 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +//      http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { +    // See: http://go/android-license-faq +    // A large-scale-change added 'default_applicable_licenses' to import +    // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license" +    // to get the below license kinds: +    //   SPDX-license-identifier-Apache-2.0 +    default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], +} + +android_library { +    name: "PlatformComposeSceneTransitionLayout", +    manifest: "AndroidManifest.xml", + +    srcs: [ +        "src/**/*.kt", +    ], + +    static_libs: [ +        "androidx.compose.runtime_runtime", +        "androidx.compose.material3_material3", +    ], + +    kotlincflags: ["-Xjvm-default=all"], +    use_resource_processor: true, +} diff --git a/packages/SystemUI/compose/scene/AndroidManifest.xml b/packages/SystemUI/compose/scene/AndroidManifest.xml new file mode 100644 index 000000000000..81131bb689e4 --- /dev/null +++ b/packages/SystemUI/compose/scene/AndroidManifest.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +     Copyright (C) 2023 The Android Open Source Project + +     Licensed under the Apache License, Version 2.0 (the "License"); +     you may not use this file except in compliance with the License. +     You may obtain a copy of the License at + +          http://www.apache.org/licenses/LICENSE-2.0 + +     Unless required by applicable law or agreed to in writing, software +     distributed under the License is distributed on an "AS IS" BASIS, +     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +     See the License for the specific language governing permissions and +     limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" +    package="com.android.compose.animation.scene"> + + +</manifest> diff --git a/packages/SystemUI/compose/scene/OWNERS b/packages/SystemUI/compose/scene/OWNERS new file mode 100644 index 000000000000..33a59c2bcab3 --- /dev/null +++ b/packages/SystemUI/compose/scene/OWNERS @@ -0,0 +1,13 @@ +set noparent + +# Bug component: 1184816 + +jdemeulenaere@google.com +omarmt@google.com + +# SysUI Dr No's. +# Don't send reviews here. +dsandler@android.com +cinek@google.com +juliacr@google.com +pixel@google.com
\ No newline at end of file diff --git a/packages/SystemUI/compose/scene/TEST_MAPPING b/packages/SystemUI/compose/scene/TEST_MAPPING new file mode 100644 index 000000000000..f644a23ba0a3 --- /dev/null +++ b/packages/SystemUI/compose/scene/TEST_MAPPING @@ -0,0 +1,48 @@ +{ +  "presubmit": [ +    { +      "name": "PlatformComposeSceneTransitionLayoutTests", +      "options": [ +        { +          "exclude-annotation": "org.junit.Ignore" +        }, +        { +          "exclude-annotation": "androidx.test.filters.FlakyTest" +        } +      ] +    }, +    { +      "name": "PlatformComposeCoreTests", +      "options": [ +        { +          "exclude-annotation": "org.junit.Ignore" +        }, +        { +          "exclude-annotation": "androidx.test.filters.FlakyTest" +        } +      ] +    }, +    { +      "name": "SystemUIComposeFeaturesTests", +      "options": [ +        { +          "exclude-annotation": "org.junit.Ignore" +        }, +        { +          "exclude-annotation": "androidx.test.filters.FlakyTest" +        } +      ] +    }, +    { +      "name": "SystemUIComposeGalleryTests", +      "options": [ +        { +          "exclude-annotation": "org.junit.Ignore" +        }, +        { +          "exclude-annotation": "androidx.test.filters.FlakyTest" +        } +      ] +    } +  ] +}
\ No newline at end of file diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateSharedAsState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt index 566967f920d3..041fc48dd09e 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateSharedAsState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt @@ -17,12 +17,10 @@  package com.android.compose.animation.scene  import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.DisposableEffectResult -import androidx.compose.runtime.DisposableEffectScope  import androidx.compose.runtime.State  import androidx.compose.runtime.derivedStateOf  import androidx.compose.runtime.remember +import androidx.compose.runtime.snapshots.Snapshot  import androidx.compose.ui.graphics.Color  import androidx.compose.ui.graphics.lerp  import androidx.compose.ui.unit.Dp @@ -45,6 +43,20 @@ fun SceneScope.animateSharedIntAsState(  }  /** + * Animate a shared Int value. + * + * @see MovableElementScope.animateSharedValueAsState + */ +@Composable +fun MovableElementScope.animateSharedIntAsState( +    value: Int, +    debugName: String, +    canOverflow: Boolean = true, +): State<Int> { +    return animateSharedValueAsState(value, debugName, ::lerp, canOverflow) +} + +/**   * Animate a shared Float value.   *   * @see SceneScope.animateSharedValueAsState @@ -60,6 +72,20 @@ fun SceneScope.animateSharedFloatAsState(  }  /** + * Animate a shared Float value. + * + * @see MovableElementScope.animateSharedValueAsState + */ +@Composable +fun MovableElementScope.animateSharedFloatAsState( +    value: Float, +    debugName: String, +    canOverflow: Boolean = true, +): State<Float> { +    return animateSharedValueAsState(value, debugName, ::lerp, canOverflow) +} + +/**   * Animate a shared Dp value.   *   * @see SceneScope.animateSharedValueAsState @@ -75,6 +101,20 @@ fun SceneScope.animateSharedDpAsState(  }  /** + * Animate a shared Dp value. + * + * @see MovableElementScope.animateSharedValueAsState + */ +@Composable +fun MovableElementScope.animateSharedDpAsState( +    value: Dp, +    debugName: String, +    canOverflow: Boolean = true, +): State<Dp> { +    return animateSharedValueAsState(value, debugName, ::lerp, canOverflow) +} + +/**   * Animate a shared Color value.   *   * @see SceneScope.animateSharedValueAsState @@ -88,6 +128,19 @@ fun SceneScope.animateSharedColorAsState(      return animateSharedValueAsState(value, key, element, ::lerp, canOverflow = false)  } +/** + * Animate a shared Color value. + * + * @see MovableElementScope.animateSharedValueAsState + */ +@Composable +fun MovableElementScope.animateSharedColorAsState( +    value: Color, +    debugName: String, +): State<Color> { +    return animateSharedValueAsState(value, debugName, ::lerp, canOverflow = false) +} +  @Composable  internal fun <T> animateSharedValueAsState(      layoutImpl: SceneTransitionLayoutImpl, @@ -98,33 +151,22 @@ internal fun <T> animateSharedValueAsState(      lerp: (T, T, Float) -> T,      canOverflow: Boolean,  ): State<T> { -    val sharedValue = remember(key) { Element.SharedValue(key, value) } +    val sharedValue = +        Snapshot.withoutReadObservation { +            element.sceneValues.getValue(scene.key).sharedValues.getOrPut(key) { +                Element.SharedValue(key, value) +            } as Element.SharedValue<T> +        } +      if (value != sharedValue.value) {          sharedValue.value = value      } -    DisposableEffect(element, scene, sharedValue) { -        addSharedValueToElement(element, scene, sharedValue) -    } -      return remember(layoutImpl, element, sharedValue, lerp, canOverflow) {          derivedStateOf { computeValue(layoutImpl, element, sharedValue, lerp, canOverflow) }      }  } -private fun <T> DisposableEffectScope.addSharedValueToElement( -    element: Element, -    scene: Scene, -    sharedValue: Element.SharedValue<T>, -): DisposableEffectResult { -    val sceneValues = -        element.sceneValues[scene.key] ?: error("Element $element is not present in $scene") -    val sharedValues = sceneValues.sharedValues - -    sharedValues[sharedValue.key] = sharedValue -    return onDispose { sharedValues.remove(sharedValue.key) } -} -  private fun <T> computeValue(      layoutImpl: SceneTransitionLayoutImpl,      element: Element, diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt index 88944f10eab9..88944f10eab9 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateToScene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt index ce96bbfc7976..abc62c4682cc 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Element.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt @@ -43,6 +43,7 @@ import androidx.compose.ui.unit.Constraints  import androidx.compose.ui.unit.IntSize  import androidx.compose.ui.unit.round  import com.android.compose.animation.scene.transformation.PropertyTransformation +import com.android.compose.animation.scene.transformation.SharedElementTransformation  import com.android.compose.modifiers.thenIf  import com.android.compose.ui.util.lerp @@ -196,29 +197,44 @@ private fun shouldDrawElement(              state.fromScene == state.toScene ||              !layoutImpl.isTransitionReady(state) ||              state.fromScene !in element.sceneValues || -            state.toScene !in element.sceneValues || -            !isSharedElementEnabled(layoutImpl, state, element.key) +            state.toScene !in element.sceneValues      ) {          return true      } -    val otherScene = -        layoutImpl.scenes.getValue( -            if (scene.key == state.fromScene) { -                state.toScene -            } else { -                state.fromScene -            } -        ) - -    // When the element is shared, draw the one in the highest scene unless it is a background, i.e. -    // it is usually drawn below everything else. -    val isHighestScene = scene.zIndex > otherScene.zIndex -    return if (element.key.isBackground) { -        !isHighestScene -    } else { -        isHighestScene +    val sharedTransformation = sharedElementTransformation(layoutImpl, state, element.key) +    if (sharedTransformation?.enabled == false) { +        return true      } + +    return shouldDrawOrComposeSharedElement( +        layoutImpl, +        state, +        scene.key, +        element.key, +        sharedTransformation, +    ) +} + +internal fun shouldDrawOrComposeSharedElement( +    layoutImpl: SceneTransitionLayoutImpl, +    transition: TransitionState.Transition, +    scene: SceneKey, +    element: ElementKey, +    sharedTransformation: SharedElementTransformation? +): Boolean { +    val scenePicker = sharedTransformation?.scenePicker ?: DefaultSharedElementScenePicker +    val fromScene = transition.fromScene +    val toScene = transition.toScene + +    return scenePicker.sceneDuringTransition( +        element = element, +        fromScene = fromScene, +        toScene = toScene, +        progress = transition::progress, +        fromSceneZIndex = layoutImpl.scenes.getValue(fromScene).zIndex, +        toSceneZIndex = layoutImpl.scenes.getValue(toScene).zIndex, +    ) == scene  }  private fun isSharedElementEnabled( @@ -226,6 +242,14 @@ private fun isSharedElementEnabled(      transition: TransitionState.Transition,      element: ElementKey,  ): Boolean { +    return sharedElementTransformation(layoutImpl, transition, element)?.enabled ?: true +} + +internal fun sharedElementTransformation( +    layoutImpl: SceneTransitionLayoutImpl, +    transition: TransitionState.Transition, +    element: ElementKey, +): SharedElementTransformation? {      val spec = layoutImpl.transitions.transitionSpec(transition.fromScene, transition.toScene)      val sharedInFromScene = spec.transformations(element, transition.fromScene).shared      val sharedInToScene = spec.transformations(element, transition.toScene).shared @@ -238,7 +262,7 @@ private fun isSharedElementEnabled(          )      } -    return sharedInFromScene?.enabled ?: true +    return sharedInFromScene  }  /** diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ElementMatcher.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ElementMatcher.kt index 98dbb67d7c66..98dbb67d7c66 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ElementMatcher.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ElementMatcher.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt index bc015eedb1b4..bc015eedb1b4 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/MovableElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt index 11bbf2aa987e..fa385d014ccb 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/MovableElement.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt @@ -21,6 +21,9 @@ import android.util.Log  import androidx.compose.foundation.layout.Box  import androidx.compose.foundation.layout.Spacer  import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue  import androidx.compose.runtime.remember  import androidx.compose.runtime.snapshots.Snapshot  import androidx.compose.ui.Modifier @@ -36,8 +39,6 @@ import androidx.compose.ui.unit.IntSize  private const val TAG = "MovableElement" -private object MovableElementScopeImpl : MovableElementScope -  @Composable  internal fun MovableElement(      layoutImpl: SceneTransitionLayoutImpl, @@ -51,13 +52,26 @@ internal fun MovableElement(          // every time an element is added/removed from SceneTransitionLayoutImpl.elements, so we          // disable read observation during the look-up in that map.          val element = Snapshot.withoutReadObservation { layoutImpl.elements.getValue(key) } +        val movableElementScope = +            remember(layoutImpl, element, scene) { +                MovableElementScopeImpl(layoutImpl, element, scene) +            }          // The [Picture] to which we save the last drawing commands of this element. This is          // necessary because the content of this element might not be composed in this scene, in          // which case we still need to draw it.          val picture = remember { Picture() } -        if (shouldComposeMovableElement(layoutImpl, scene.key, element)) { +        // Whether we should compose the movable element here. The scene picker logic to know in +        // which scene we should compose/draw a movable element might depend on the current +        // transition progress, so we put this in a derivedStateOf to prevent many recompositions +        // during the transition. +        val shouldComposeMovableElement by +            remember(layoutImpl, scene.key, element) { +                derivedStateOf { shouldComposeMovableElement(layoutImpl, scene.key, element) } +            } + +        if (shouldComposeMovableElement) {              Box(                  Modifier.drawWithCache {                      val width = size.width.toInt() @@ -77,7 +91,7 @@ internal fun MovableElement(                      }                  }              ) { -                element.movableContent { MovableElementScopeImpl.content() } +                element.movableContent { movableElementScope.content() }              }          } else {              // If we are not composed, we draw the previous drawing commands at the same size as the @@ -169,12 +183,28 @@ private fun shouldComposeMovableElement(          return scene == fromScene      } -    // If we are ready in both scenes, then compose in the scene that has the highest zIndex (unless -    // it is a background) given that this is the one that is going to be drawn. -    val isHighestScene = layoutImpl.scene(scene).zIndex > layoutImpl.scene(otherScene).zIndex -    return if (element.key.isBackground) { -        !isHighestScene -    } else { -        isHighestScene +    return shouldDrawOrComposeSharedElement( +        layoutImpl, +        transitionState, +        scene, +        element.key, +        sharedElementTransformation(layoutImpl, transitionState, element.key), +    ) +} + +private class MovableElementScopeImpl( +    private val layoutImpl: SceneTransitionLayoutImpl, +    private val element: Element, +    private val scene: Scene, +) : MovableElementScope { +    @Composable +    override fun <T> animateSharedValueAsState( +        value: T, +        debugName: String, +        lerp: (start: T, stop: T, fraction: Float) -> T, +        canOverflow: Boolean, +    ): State<T> { +        val key = remember { ValueKey(debugName) } +        return animateSharedValueAsState(layoutImpl, scene, element, key, value, lerp, canOverflow)      }  } diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ObservableTransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt index ccdec6ea8c5e..ccdec6ea8c5e 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/ObservableTransitionState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt index 3fd6828fca6b..3fd6828fca6b 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Scene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt index 4283c0e61df8..74e66d2a9949 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayout.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt @@ -160,7 +160,16 @@ interface SceneScope {  // TODO(b/291053742): Add animateSharedValueAsState(targetValue) without any ValueKey and ElementKey  // arguments to allow sharing values inside a movable element. -@ElementDsl interface MovableElementScope +@ElementDsl +interface MovableElementScope { +    @Composable +    fun <T> animateSharedValueAsState( +        value: T, +        debugName: String, +        lerp: (start: T, stop: T, fraction: Float) -> T, +        canOverflow: Boolean, +    ): State<T> +}  /** An action performed by the user. */  sealed interface UserAction diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt index 4952270cb5f2..4952270cb5f2 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt index 7a21211c3dde..7a21211c3dde 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt index 75dcb2e44c13..75dcb2e44c13 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt index 1cbfe3057ff0..1cbfe3057ff0 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SwipeToScene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt index 49669775fedd..7b7ddfa5ec4e 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDsl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt @@ -120,8 +120,14 @@ interface TransitionBuilder : PropertyTransformationBuilder {       *       * @param enabled whether the matched element(s) should actually be shared in this transition.       *   Defaults to true. +     * @param scenePicker the [SharedElementScenePicker] to use when deciding in which scene we +     *   should draw or compose this shared element.       */ -    fun sharedElement(matcher: ElementMatcher, enabled: Boolean = true) +    fun sharedElement( +        matcher: ElementMatcher, +        enabled: Boolean = true, +        scenePicker: SharedElementScenePicker = DefaultSharedElementScenePicker, +    )      /**       * Punch a hole in the element(s) matching [matcher] that has the same bounds as [bounds] and @@ -144,6 +150,44 @@ interface TransitionBuilder : PropertyTransformationBuilder {      fun reversed(builder: TransitionBuilder.() -> Unit)  } +interface SharedElementScenePicker { +    /** +     * Return the scene in which [element] should be drawn (when using `Modifier.element(key)`) or +     * composed (when using `MovableElement(key)`) during the transition from [fromScene] to +     * [toScene]. +     */ +    fun sceneDuringTransition( +        element: ElementKey, +        fromScene: SceneKey, +        toScene: SceneKey, +        progress: () -> Float, +        fromSceneZIndex: Float, +        toSceneZIndex: Float, +    ): SceneKey +} + +object DefaultSharedElementScenePicker : SharedElementScenePicker { +    override fun sceneDuringTransition( +        element: ElementKey, +        fromScene: SceneKey, +        toScene: SceneKey, +        progress: () -> Float, +        fromSceneZIndex: Float, +        toSceneZIndex: Float +    ): SceneKey { +        // By default shared elements are drawn in the highest scene possible, unless it is a +        // background. +        return if ( +            (fromSceneZIndex > toSceneZIndex && !element.isBackground) || +                (fromSceneZIndex < toSceneZIndex && element.isBackground) +        ) { +            fromScene +        } else { +            toScene +        } +    } +} +  @TransitionDsl  interface PropertyTransformationBuilder {      /** diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt index f1c27178391c..d2bfd91842ae 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt @@ -111,8 +111,12 @@ internal class TransitionBuilderImpl : TransitionBuilder {          range = null      } -    override fun sharedElement(matcher: ElementMatcher, enabled: Boolean) { -        transformations.add(SharedElementTransformation(matcher, enabled)) +    override fun sharedElement( +        matcher: ElementMatcher, +        enabled: Boolean, +        scenePicker: SharedElementScenePicker, +    ) { +        transformations.add(SharedElementTransformation(matcher, enabled, scenePicker))      }      override fun timestampRange( diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt index 95385d51cb25..95385d51cb25 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt index a1d63193bc73..a1d63193bc73 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt index 840800d838db..840800d838db 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Fade.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt index 17032dc288e0..17032dc288e0 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Fade.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/PunchHole.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/PunchHole.kt index 62d67f03f1d0..62d67f03f1d0 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/PunchHole.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/PunchHole.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/ScaleSize.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt index 233ae597090b..233ae597090b 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/ScaleSize.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Transformation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt index 2ef8d56c6bc6..0db8469466ef 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Transformation.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt @@ -21,6 +21,7 @@ import com.android.compose.animation.scene.Element  import com.android.compose.animation.scene.ElementMatcher  import com.android.compose.animation.scene.Scene  import com.android.compose.animation.scene.SceneTransitionLayoutImpl +import com.android.compose.animation.scene.SharedElementScenePicker  import com.android.compose.animation.scene.TransitionState  /** A transformation applied to one or more elements during a transition. */ @@ -48,6 +49,7 @@ sealed interface Transformation {  internal class SharedElementTransformation(      override val matcher: ElementMatcher,      internal val enabled: Boolean, +    internal val scenePicker: SharedElementScenePicker,  ) : Transformation  /** A transformation that is applied on the element during the whole transition. */ diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Translate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt index 864b937a3fe0..864b937a3fe0 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/transformation/Translate.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/grid/Grids.kt b/packages/SystemUI/compose/scene/src/com/android/compose/grid/Grids.kt index 27f0948d5377..27f0948d5377 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/grid/Grids.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/grid/Grids.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/modifiers/ConditionalModifiers.kt b/packages/SystemUI/compose/scene/src/com/android/compose/modifiers/ConditionalModifiers.kt index 135a6e4ec4e4..135a6e4ec4e4 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/modifiers/ConditionalModifiers.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/modifiers/ConditionalModifiers.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt index cea8d9a65b43..cea8d9a65b43 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt index 793a9a59405a..793a9a59405a 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/ui/util/ListUtils.kt b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/ListUtils.kt index 741f00d9f19b..741f00d9f19b 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/ui/util/ListUtils.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/ListUtils.kt diff --git a/packages/SystemUI/compose/core/src/com/android/compose/ui/util/MathHelpers.kt b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/MathHelpers.kt index eb1a634ff491..eb1a634ff491 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/ui/util/MathHelpers.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/ui/util/MathHelpers.kt diff --git a/packages/SystemUI/compose/scene/tests/Android.bp b/packages/SystemUI/compose/scene/tests/Android.bp new file mode 100644 index 000000000000..b53fae24865c --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/Android.bp @@ -0,0 +1,50 @@ +// Copyright (C) 2023 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +//      http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { +    // See: http://go/android-license-faq +    // A large-scale-change added 'default_applicable_licenses' to import +    // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license" +    // to get the below license kinds: +    //   SPDX-license-identifier-Apache-2.0 +    default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], +} + +android_test { +    name: "PlatformComposeSceneTransitionLayoutTests", +    manifest: "AndroidManifest.xml", +    test_suites: ["device-tests"], +    sdk_version: "current", +    certificate: "platform", + +    srcs: [ +        "src/**/*.kt", +    ], + +    static_libs: [ +        "PlatformComposeSceneTransitionLayout", + +        "androidx.test.runner", +        "androidx.test.ext.junit", + +        "androidx.compose.runtime_runtime", +        "androidx.compose.ui_ui-test-junit4", +        "androidx.compose.ui_ui-test-manifest", + +        "truth", +    ], + +    kotlincflags: ["-Xjvm-default=all"], +    use_resource_processor: true, +} diff --git a/packages/SystemUI/compose/scene/tests/AndroidManifest.xml b/packages/SystemUI/compose/scene/tests/AndroidManifest.xml new file mode 100644 index 000000000000..1a9172ee20e0 --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/AndroidManifest.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2023 The Android Open Source Project + +    Licensed under the Apache License, Version 2.0 (the "License"); +    you may not use this file except in compliance with the License. +    You may obtain a copy of the License at + +         http://www.apache.org/licenses/LICENSE-2.0 + +    Unless required by applicable law or agreed to in writing, software +    distributed under the License is distributed on an "AS IS" BASIS, +    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +    See the License for the specific language governing permissions and +    limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" +    package="com.android.compose.animation.scene.tests" > + +    <application> +        <uses-library android:name="android.test.runner" /> +    </application> + +    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" +                     android:targetPackage="com.android.compose.animation.scene.tests" +                     android:label="Tests for SceneTransitionLayout"/> + +</manifest>
\ No newline at end of file diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt new file mode 100644 index 000000000000..7b7695eebd2f --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.compose.animation.scene + +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.tween +import androidx.compose.foundation.layout.Box +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.lerp +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.lerp +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.compose.ui.util.lerp +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class AnimatedSharedAsStateTest { +    @get:Rule val rule = createComposeRule() + +    private data class Values( +        val int: Int, +        val float: Float, +        val dp: Dp, +        val color: Color, +    ) + +    private fun lerp(start: Values, stop: Values, fraction: Float): Values { +        return Values( +            int = lerp(start.int, stop.int, fraction), +            float = lerp(start.float, stop.float, fraction), +            dp = lerp(start.dp, stop.dp, fraction), +            color = lerp(start.color, stop.color, fraction), +        ) +    } + +    @Composable +    private fun SceneScope.Foo( +        targetValues: Values, +        onCurrentValueChanged: (Values) -> Unit, +    ) { +        val key = TestElements.Foo +        Box(Modifier.element(key)) { +            val int by animateSharedIntAsState(targetValues.int, TestValues.Value1, key) +            val float by animateSharedFloatAsState(targetValues.float, TestValues.Value2, key) +            val dp by animateSharedDpAsState(targetValues.dp, TestValues.Value3, key) +            val color by animateSharedColorAsState(targetValues.color, TestValues.Value4, key) + +            // Make sure we read the values during composition, so that we recompose and call +            // onCurrentValueChanged() with the latest values. +            val currentValues = Values(int, float, dp, color) +            SideEffect { onCurrentValueChanged(currentValues) } +        } +    } + +    @Composable +    private fun SceneScope.MovableFoo( +        targetValues: Values, +        onCurrentValueChanged: (Values) -> Unit, +    ) { +        val key = TestElements.Foo +        MovableElement(key = key, Modifier) { +            val int by +                animateSharedIntAsState(targetValues.int, debugName = TestValues.Value1.debugName) +            val float by +                animateSharedFloatAsState( +                    targetValues.float, +                    debugName = TestValues.Value2.debugName +                ) +            val dp by +                animateSharedDpAsState(targetValues.dp, debugName = TestValues.Value3.debugName) +            val color by +                animateSharedColorAsState( +                    targetValues.color, +                    debugName = TestValues.Value4.debugName +                ) + +            // Make sure we read the values during composition, so that we recompose and call +            // onCurrentValueChanged() with the latest values. +            val currentValues = Values(int, float, dp, color) +            SideEffect { onCurrentValueChanged(currentValues) } +        } +    } + +    @Test +    fun animateSharedValues() { +        val fromValues = Values(int = 0, float = 0f, dp = 0.dp, color = Color.Red) +        val toValues = Values(int = 100, float = 100f, dp = 100.dp, color = Color.Blue) + +        var lastValueInFrom = fromValues +        var lastValueInTo = toValues + +        rule.testTransition( +            fromSceneContent = { +                Foo(targetValues = fromValues, onCurrentValueChanged = { lastValueInFrom = it }) +            }, +            toSceneContent = { +                Foo(targetValues = toValues, onCurrentValueChanged = { lastValueInTo = it }) +            }, +            transition = { +                // The transition lasts 64ms = 4 frames. +                spec = tween(durationMillis = 16 * 4, easing = LinearEasing) +            }, +            fromScene = TestScenes.SceneA, +            toScene = TestScenes.SceneB, +        ) { +            before { +                assertThat(lastValueInFrom).isEqualTo(fromValues) + +                // to was not composed yet, so lastValueInTo was not set yet. +                assertThat(lastValueInTo).isEqualTo(toValues) +            } + +            at(16) { +                // Given that we use Modifier.element() here, animateSharedXAsState is composed in +                // both scenes and values should be interpolated with the transition fraction. +                val expectedValues = lerp(fromValues, toValues, fraction = 0.25f) +                assertThat(lastValueInFrom).isEqualTo(expectedValues) +                assertThat(lastValueInTo).isEqualTo(expectedValues) +            } + +            at(32) { +                val expectedValues = lerp(fromValues, toValues, fraction = 0.5f) +                assertThat(lastValueInFrom).isEqualTo(expectedValues) +                assertThat(lastValueInTo).isEqualTo(expectedValues) +            } + +            at(48) { +                val expectedValues = lerp(fromValues, toValues, fraction = 0.75f) +                assertThat(lastValueInFrom).isEqualTo(expectedValues) +                assertThat(lastValueInTo).isEqualTo(expectedValues) +            } + +            after { +                assertThat(lastValueInFrom).isEqualTo(toValues) +                assertThat(lastValueInTo).isEqualTo(toValues) +            } +        } +    } + +    @Test +    fun movableAnimateSharedValues() { +        val fromValues = Values(int = 0, float = 0f, dp = 0.dp, color = Color.Red) +        val toValues = Values(int = 100, float = 100f, dp = 100.dp, color = Color.Blue) + +        var lastValueInFrom = fromValues +        var lastValueInTo = toValues + +        rule.testTransition( +            fromSceneContent = { +                MovableFoo( +                    targetValues = fromValues, +                    onCurrentValueChanged = { lastValueInFrom = it } +                ) +            }, +            toSceneContent = { +                MovableFoo(targetValues = toValues, onCurrentValueChanged = { lastValueInTo = it }) +            }, +            transition = { +                // The transition lasts 64ms = 4 frames. +                spec = tween(durationMillis = 16 * 4, easing = LinearEasing) +            }, +            fromScene = TestScenes.SceneA, +            toScene = TestScenes.SceneB, +        ) { +            before { +                assertThat(lastValueInFrom).isEqualTo(fromValues) + +                // to was not composed yet, so lastValueInTo was not set yet. +                assertThat(lastValueInTo).isEqualTo(toValues) +            } + +            at(16) { +                // Given that we use MovableElement here, animateSharedXAsState is composed only +                // once, in the highest scene (in this case, in toScene). +                assertThat(lastValueInFrom).isEqualTo(fromValues) +                assertThat(lastValueInTo).isEqualTo(lerp(fromValues, toValues, fraction = 0.25f)) +            } + +            at(32) { +                assertThat(lastValueInFrom).isEqualTo(fromValues) +                assertThat(lastValueInTo).isEqualTo(lerp(fromValues, toValues, fraction = 0.5f)) +            } + +            at(48) { +                assertThat(lastValueInFrom).isEqualTo(fromValues) +                assertThat(lastValueInTo).isEqualTo(lerp(fromValues, toValues, fraction = 0.75f)) +            } + +            after { +                assertThat(lastValueInFrom).isEqualTo(fromValues) +                assertThat(lastValueInTo).isEqualTo(toValues) +            } +        } +    } +} diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/MovableElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt index 4204cd5f0da0..83af630ab098 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/MovableElementTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt @@ -144,7 +144,36 @@ class MovableElementTest {          rule.testTransition(              fromSceneContent = { MovableCounter(TestElements.Foo, Modifier.size(50.dp)) },              toSceneContent = { MovableCounter(TestElements.Foo, Modifier.size(100.dp)) }, -            transition = { spec = tween(durationMillis = 16 * 4, easing = LinearEasing) }, +            transition = { +                spec = tween(durationMillis = 16 * 4, easing = LinearEasing) +                sharedElement( +                    TestElements.Foo, +                    scenePicker = +                        object : SharedElementScenePicker { +                            override fun sceneDuringTransition( +                                element: ElementKey, +                                fromScene: SceneKey, +                                toScene: SceneKey, +                                progress: () -> Float, +                                fromSceneZIndex: Float, +                                toSceneZIndex: Float +                            ): SceneKey { +                                assertThat(fromScene).isEqualTo(TestScenes.SceneA) +                                assertThat(toScene).isEqualTo(TestScenes.SceneB) +                                assertThat(fromSceneZIndex).isEqualTo(0) +                                assertThat(toSceneZIndex).isEqualTo(1) + +                                // Compose Foo in Scene A if progress < 0.65f, otherwise compose it +                                // in Scene B. +                                return if (progress() < 0.65f) { +                                    TestScenes.SceneA +                                } else { +                                    TestScenes.SceneB +                                } +                            } +                        } +                ) +            },              fromScene = TestScenes.SceneA,              toScene = TestScenes.SceneB,          ) { @@ -170,9 +199,12 @@ class MovableElementTest {              at(32) {                  // During the transition, there is a single counter that is moved, with the current -                // value. +                // value. Given that progress = 0.5f, it is currently composed in SceneA.                  rule -                    .onNode(hasText("count: 3")) +                    .onNode( +                        hasText("count: 3") and +                            hasParent(isElement(TestElements.Foo, scene = TestScenes.SceneA)) +                    )                      .assertIsDisplayed()                      .assertSizeIsEqualTo(75.dp, 75.dp) @@ -186,6 +218,26 @@ class MovableElementTest {                      .isEqualTo(1)              } +            at(48) { +                // During the transition, there is a single counter that is moved, with the current +                // value. Given that progress = 0.75f, it is currently composed in SceneB. +                rule +                    .onNode( +                        hasText("count: 3") and +                            hasParent(isElement(TestElements.Foo, scene = TestScenes.SceneB)) +                    ) +                    .assertIsDisplayed() + +                // There are no other counters. +                assertThat( +                        rule +                            .onAllNodesWithText("count: ", substring = true) +                            .fetchSemanticsNodes() +                            .size +                    ) +                    .isEqualTo(1) +            } +              after {                  // At the end of the transition, the counter still has the current value.                  rule diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt index 04b3f8a1dfe7..04b3f8a1dfe7 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt index 5afd420a5e16..5afd420a5e16 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt index df3b72aa5533..df3b72aa5533 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestTransition.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TestTransition.kt index e0ae1be69aaf..e0ae1be69aaf 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestTransition.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TestTransition.kt diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestValues.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TestValues.kt index 83572620c88a..b4c393e9bfbe 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TestValues.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TestValues.kt @@ -37,6 +37,9 @@ object TestElements {  /** Value keys that can be reused by tests. */  object TestValues {      val Value1 = ValueKey("Value1") +    val Value2 = ValueKey("Value2") +    val Value3 = ValueKey("Value3") +    val Value4 = ValueKey("Value4")  }  // We use a transition duration of 480ms here because it is a multiple of 16, the time of a frame in diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt index fa94b25028a2..fa94b25028a2 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt index 8ef6757d33bd..8ef6757d33bd 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt index d1205e727cf9..d1205e727cf9 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredTranslateTest.kt diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt index 2a27763f1d5c..2a27763f1d5c 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/ScaleSizeTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/ScaleSizeTest.kt index 384355ca951f..384355ca951f 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/ScaleSizeTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/ScaleSizeTest.kt diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt index 2af363860272..e94eff32c30c 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt @@ -32,7 +32,6 @@ import com.android.compose.animation.scene.TestElements  import com.android.compose.animation.scene.TestScenes  import com.android.compose.animation.scene.inScene  import com.android.compose.animation.scene.testTransition -import com.android.compose.modifiers.size  import com.android.compose.test.assertSizeIsEqualTo  import com.android.compose.test.onEach  import org.junit.Rule diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/TranslateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/TranslateTest.kt index 1d559fd6bd8a..1d559fd6bd8a 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/animation/scene/transformation/TranslateTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/TranslateTest.kt diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt index 03d231a7fcc6..03d231a7fcc6 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt index 8e2b77a2f2a0..8e2b77a2f2a0 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/test/Selectors.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/Selectors.kt index d6f64bfe4974..d6f64bfe4974 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/test/Selectors.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/Selectors.kt diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/test/SizeAssertions.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/SizeAssertions.kt index fbd1b512c50a..fbd1b512c50a 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/test/SizeAssertions.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/SizeAssertions.kt diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt index bf7bf98878e6..bf7bf98878e6 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/subjects/DpOffsetSubject.kt diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java index a5e5aaa499f1..a9d2ee3cdfe6 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java @@ -71,7 +71,11 @@ public interface DarkIconDispatcher {        */      void applyDark(DarkReceiver object); +    /** The default tint (applicable for dark backgrounds) is white */      int DEFAULT_ICON_TINT = Color.WHITE; +    /** To support an icon which wants to create contrast, the default tint is black-on-white. */ +    int DEFAULT_INVERSE_ICON_TINT = Color.BLACK; +      Rect sTmpRect = new Rect();      int[] sTmpInt2 = new int[2]; @@ -88,6 +92,18 @@ public interface DarkIconDispatcher {      }      /** +     * @return the tint to apply to a foreground, given that the background is tinted +     *         per {@link #getTint} +     */ +    static int getInverseTint(Collection<Rect> tintAreas, View view, int inverseColor) { +        if (isInAreas(tintAreas, view)) { +            return inverseColor; +        } else { +            return DEFAULT_INVERSE_ICON_TINT; +        } +    } + +    /**       * @return true if more than half of the view area are in any of the given       *         areas, false otherwise       */ @@ -129,7 +145,40 @@ public interface DarkIconDispatcher {       */      @ProvidesInterface(version = DarkReceiver.VERSION)      interface DarkReceiver { -        int VERSION = 2; +        int VERSION = 3; + +        /** +         * @param areas list of regions on screen where the tint applies +         * @param darkIntensity float representing the level of tint. In the range [0,1] +         * @param tint the tint applicable as a foreground contrast to the dark regions. This value +         *             is interpolated between a default light and dark tone, and is therefore +         *             usable as-is, as long as the view is in one of the areas defined in +         *             {@code areas}. +         * +         * @see DarkIconDispatcher#isInArea(Rect, View) for utilizing {@code areas} +         * +         * Note: only one of {@link #onDarkChanged(ArrayList, float, int)} or +         * {@link #onDarkChangedWithContrast(ArrayList, int, int)} need to be implemented, as both +         * will be called in the same circumstances. +         */          void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint); + +        /** +         * New version of onDarkChanged, which describes a tint plus an optional contrastTint +         * that can be used if the tint is applied to the background of an icon. +         * +         * We use the 2 here to avoid the case where an existing override of onDarkChanged +         * might pass in parameters as bare numbers (e.g. 0 instead of 0f) which might get +         * mistakenly cast to (int) and therefore trigger this method. +         * +         * @param areas list of areas where dark tint applies +         * @param tint int describing the tint color to use +         * @param contrastTint if desired, a contrasting color that can be used for a foreground +         * +         * Note: only one of {@link #onDarkChanged(ArrayList, float, int)} or +         * {@link #onDarkChangedWithContrast(ArrayList, int, int)} need to be implemented, as both +         * will be called in the same circumstances. +         */ +        default void onDarkChangedWithContrast(ArrayList<Rect> areas, int tint, int contrastTint) {}      }  } diff --git a/packages/SystemUI/res-keyguard/drawable/progress_bar.xml b/packages/SystemUI/res-keyguard/drawable/progress_bar.xml new file mode 100644 index 000000000000..910a74ad5faf --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/progress_bar.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +  ~ Copyright (C) 2023 The Android Open Source Project +  ~ +  ~ Licensed under the Apache License, Version 2.0 (the "License"); +  ~ you may not use this file except in compliance with the License. +  ~ You may obtain a copy of the License at +  ~ +  ~      http://www.apache.org/licenses/LICENSE-2.0 +  ~ +  ~ Unless required by applicable law or agreed to in writing, software +  ~ distributed under the License is distributed on an "AS IS" BASIS, +  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +  ~ See the License for the specific language governing permissions and +  ~ limitations under the License. +  --> + +<layer-list xmlns:android="http://schemas.android.com/apk/res/android" +    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" +    android:paddingMode="stack"> +    <item +        android:id="@android:id/background" +        android:gravity="center_vertical|fill_horizontal"> +        <shape +            android:layout_width="match_parent" +            android:layout_height="match_parent" +            android:shape="rectangle"> +            <corners android:radius="30dp" /> +            <solid android:color="?androidprv:attr/materialColorSurfaceContainerHighest" /> +        </shape> +    </item> +    <item +        android:id="@android:id/progress" +        android:gravity="center_vertical|fill_horizontal"> +        <clip> +            <shape +                android:layout_width="match_parent" +                android:layout_height="match_parent" +                android:shape="rectangle"> +                <corners android:radius="30dp" /> +                <solid android:color="?androidprv:attr/textColorPrimary" /> +            </shape> +        </clip> +    </item> +</layer-list>
\ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/layout/sidefps_progress_bar.xml b/packages/SystemUI/res-keyguard/layout/sidefps_progress_bar.xml new file mode 100644 index 000000000000..183f0e591c91 --- /dev/null +++ b/packages/SystemUI/res-keyguard/layout/sidefps_progress_bar.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?><!-- +  ~ Copyright (C) 2023 The Android Open Source Project +  ~ +  ~ Licensed under the Apache License, Version 2.0 (the "License"); +  ~ you may not use this file except in compliance with the License. +  ~ You may obtain a copy of the License at +  ~ +  ~      http://www.apache.org/licenses/LICENSE-2.0 +  ~ +  ~ Unless required by applicable law or agreed to in writing, software +  ~ distributed under the License is distributed on an "AS IS" BASIS, +  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +  ~ See the License for the specific language governing permissions and +  ~ limitations under the License. +  ~ +  --> + +<LinearLayout android:layout_height="match_parent" +    android:layout_width="match_parent" +    android:orientation="vertical" +    android:layoutDirection="ltr" +    android:gravity="center" +    xmlns:android="http://schemas.android.com/apk/res/android"> +    <ProgressBar +        android:id="@+id/side_fps_progress_bar" +        android:layout_width="55dp" +        android:layout_height="10dp" +        android:indeterminateOnly="false" +        android:min="0" +        android:max="100" +        android:progressDrawable="@drawable/progress_bar" /> +</LinearLayout> diff --git a/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml index a1e2dc36278b..a8017f65c810 100644 --- a/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml +++ b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml @@ -52,15 +52,22 @@                  android:visibility="gone"                  />          </FrameLayout> -        <ImageView -            android:id="@+id/mobile_type" -            android:layout_height="@dimen/status_bar_mobile_type_size" +        <FrameLayout +            android:id="@+id/mobile_type_container" +            android:layout_height="@dimen/status_bar_mobile_container_height"              android:layout_width="wrap_content" -            android:layout_gravity="center_vertical" -            android:adjustViewBounds="true" -            android:paddingStart="2.5sp" -            android:paddingEnd="1sp" -            android:visibility="gone" /> +            android:layout_marginStart="2.5sp" +            android:layout_marginEnd="1sp" +            android:visibility="gone" +            > +            <ImageView +                android:id="@+id/mobile_type" +                android:layout_height="@dimen/status_bar_mobile_type_size" +                android:layout_width="wrap_content" +                android:layout_gravity="center_vertical" +                android:adjustViewBounds="true" +                /> +        </FrameLayout>          <Space              android:id="@+id/mobile_roaming_space"              android:layout_height="match_parent" diff --git a/packages/SystemUI/res/drawable/mobile_network_type_background.xml b/packages/SystemUI/res/drawable/mobile_network_type_background.xml new file mode 100644 index 000000000000..db25c89d1fa5 --- /dev/null +++ b/packages/SystemUI/res/drawable/mobile_network_type_background.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?><!-- +  ~ Copyright (C) 2023 The Android Open Source Project +  ~ +  ~ Licensed under the Apache License, Version 2.0 (the "License"); +  ~ you may not use this file except in compliance with the License. +  ~ You may obtain a copy of the License at +  ~ +  ~      http://www.apache.org/licenses/LICENSE-2.0 +  ~ +  ~ Unless required by applicable law or agreed to in writing, software +  ~ distributed under the License is distributed on an "AS IS" BASIS, +  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +  ~ See the License for the specific language governing permissions and +  ~ limitations under the License. +  ~ +  --> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" +    android:shape="rectangle" +    > +    <corners +        android:topLeftRadius="0dp" +        android:topRightRadius="@dimen/status_bar_mobile_container_corner_radius" +        android:bottomRightRadius="0dp" +        android:bottomLeftRadius="@dimen/status_bar_mobile_container_corner_radius"/> +    <solid android:color="#FFF" /> +    <padding +        android:left="2sp" +        android:right="2sp"/> +</shape> diff --git a/packages/SystemUI/res/layout/connected_display_dialog.xml b/packages/SystemUI/res/layout/connected_display_dialog.xml index a51c55ee965f..8cfcb689eced 100644 --- a/packages/SystemUI/res/layout/connected_display_dialog.xml +++ b/packages/SystemUI/res/layout/connected_display_dialog.xml @@ -15,8 +15,9 @@    -->  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"      xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" +    android:id="@+id/cd_bottom_sheet"      android:layout_width="match_parent" -    android:layout_height="match_parent" +    android:layout_height="wrap_content"      android:gravity="center_horizontal"      android:orientation="vertical"      android:paddingHorizontal="@dimen/dialog_side_padding" @@ -26,11 +27,14 @@      <ImageView          android:id="@+id/connected_display_dialog_icon" -        android:layout_width="@dimen/screenrecord_logo_size" -        android:layout_height="@dimen/screenrecord_logo_size" +        android:layout_width="@dimen/connected_display_dialog_logo_size" +        android:layout_height="@dimen/connected_display_dialog_logo_size" +        android:background="@drawable/circular_background" +        android:backgroundTint="?androidprv:attr/materialColorPrimary"          android:importantForAccessibility="no" +        android:padding="6dp"          android:src="@drawable/stat_sys_connected_display" -        android:tint="?androidprv:attr/materialColorPrimary" /> +        android:tint="?androidprv:attr/materialColorOnPrimary" />      <TextView          android:id="@+id/connected_display_dialog_title" diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml index ea3c012afc43..1f671ac4c875 100644 --- a/packages/SystemUI/res/values-sw600dp/config.xml +++ b/packages/SystemUI/res/values-sw600dp/config.xml @@ -37,6 +37,9 @@      <bool name="config_use_large_screen_shade_header">true</bool> +    <!-- Whether to show bottom sheets edge to edge --> +    <bool name="config_edgeToEdgeBottomSheetDialog">false</bool> +      <!-- A collection of defaults for the quick affordances on the lock screen. Each item must be a      string with two parts: the ID of the slot and the comma-delimited list of affordance IDs,      separated by a colon ':' character. For example: <item>bottom_end:home,wallet</item>. The diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 1add90ff4083..9ac1e9f58dbc 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -730,6 +730,9 @@      <!-- Whether the communal service should be enabled -->      <bool name="config_communalServiceEnabled">false</bool> +    <!-- Component names of allowed communal widgets --> +    <string-array name="config_communalWidgetAllowlist" translatable="false" /> +      <!-- Component name of communal source service -->      <string name="config_communalSourceComponent" translatable="false">@null</string> @@ -947,6 +950,9 @@      <!-- Flag controlling whether visual query attention detection has been enabled. -->      <bool name="config_enableVisualQueryAttentionDetection">false</bool> +    <!-- Whether to show bottom sheets edge to edge --> +    <bool name="config_edgeToEdgeBottomSheetDialog">true</bool> +      <!--      Whether the scene container framework is enabled. @@ -954,4 +960,15 @@      bouncer, lockscreen, shade, and quick settings.      -->      <bool name="config_sceneContainerFrameworkEnabled">true</bool> + +    <!-- +    Time in milliseconds the user has to touch the side FPS sensor to successfully authenticate +    TODO(b/302332976) Get this value from the HAL if they can provide an API for it. +    --> +    <integer name="config_restToUnlockDuration">300</integer> + +    <!-- +    Width in pixels of the Side FPS sensor. +    --> +    <integer name="config_sfpsSensorWidth">200</integer>  </resources> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 5a83c7d2dc2a..10fd8b579b16 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -177,6 +177,12 @@      <!-- Size of the view displaying the mobile signal icon in the status bar. This value should          match the viewport height of mobile signal drawables such as ic_lte_mobiledata -->      <dimen name="status_bar_mobile_type_size">16sp</dimen> +    <!-- Size of the view that contains the network type. Should be equal to +    status_bar_mobile_type_size + 2, to account for 1sp top and bottom padding --> +    <dimen name="status_bar_mobile_container_height">18sp</dimen> +    <!-- Corner radius for the background of the network type indicator. Should be equal to +        status_bar_mobile_container_height / 2 --> +    <dimen name="status_bar_mobile_container_corner_radius">9sp</dimen>      <!-- Size of the view displaying the mobile roam icon in the status bar. This value should          match the viewport size of drawable stat_sys_roaming -->      <dimen name="status_bar_mobile_roam_size">8sp</dimen> @@ -760,6 +766,8 @@      <dimen name="keyguard_clock_switch_y_shift">14dp</dimen>      <!-- When large clock is showing, offset the smartspace by this amount -->      <dimen name="keyguard_smartspace_top_offset">12dp</dimen> +    <!-- The amount to translate lockscreen elements on the GONE->AOD transition --> +    <dimen name="keyguard_enter_from_top_translation_y">-100dp</dimen>      <dimen name="notification_scrim_corner_radius">32dp</dimen> @@ -1355,6 +1363,9 @@      <dimen name="screenrecord_options_padding_bottom">16dp</dimen>      <dimen name="screenrecord_buttons_margin_top">20dp</dimen> +    <!-- Connected display dialog --> +    <dimen name="connected_display_dialog_logo_size">48dp</dimen> +      <!-- Keyguard user switcher -->      <dimen name="kg_user_switcher_text_size">16sp</dimen> @@ -1658,6 +1669,15 @@      <!-- Height percentage of the parent container occupied by the communal view -->      <item name="communal_source_height_percentage" format="float" type="dimen">0.80</item> +    <!-- Size of each communal grid column --> +    <dimen name="communal_grid_column_size">64dp</dimen> +    <!-- Size of each communal grid gutter between columns --> +    <dimen name="communal_grid_gutter_size">16dp</dimen> +    <!-- Height of the communal grid layout --> +    <dimen name="communal_grid_height">630dp</dimen> +    <!-- Number of columns for each communal card --> +    <integer name="communal_grid_columns_per_card">6</integer> +      <dimen name="drag_and_drop_icon_size">70dp</dimen>      <dimen name="qs_tile_service_request_dialog_width">304dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 321594f41479..7a6d29ab3c1f 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -209,6 +209,8 @@      <string name="screenshot_saved_title">Screenshot saved</string>      <!-- Notification title displayed when we fail to take a screenshot. [CHAR LIMIT=50] -->      <string name="screenshot_failed_title">Couldn\'t save screenshot</string> +    <!-- Appended to the notification content when a screenshot failure happens on an external display. [CHAR LIMIT=50] --> +    <string name="screenshot_failed_external_display_indication">External Display</string>      <!-- Notification text displayed when we fail to save a screenshot due to locked storage. [CHAR LIMIT=100] -->      <string name="screenshot_failed_to_save_user_locked_text">Device must be unlocked before screenshot can be saved</string>      <!-- Notification text displayed when we fail to save a screenshot for unknown reasons. [CHAR LIMIT=100] --> @@ -711,8 +713,8 @@       <!-- QuickSettings: Cast detail panel, default device description [CHAR LIMIT=NONE] -->      <!-- QuickSettings: Cast detail panel, text when there are no items [CHAR LIMIT=NONE] -->      <string name="quick_settings_cast_detail_empty_text">No devices available</string> -    <!-- QuickSettings: Cast unavailable, text when not connected to WiFi [CHAR LIMIT=NONE] --> -    <string name="quick_settings_cast_no_wifi">Wi\u2011Fi not connected</string> +    <!-- QuickSettings: Cast unavailable, text when not connected to WiFi or ethernet[CHAR LIMIT=NONE] --> +    <string name="quick_settings_cast_no_network">No Wi\u2011Fi or Ethernet connection</string>      <!-- QuickSettings: Brightness dialog title [CHAR LIMIT=NONE] -->      <string name="quick_settings_brightness_dialog_title">Brightness</string>      <!-- QuickSettings: Label for the toggle that controls whether display inversion is enabled. [CHAR LIMIT=NONE] --> diff --git a/packages/SystemUI/res/xml/self_certified_network_capabilities_both.xml b/packages/SystemUI/res/xml/self_certified_network_capabilities_both.xml new file mode 100644 index 000000000000..4b430e146439 --- /dev/null +++ b/packages/SystemUI/res/xml/self_certified_network_capabilities_both.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +  ~ Copyright (C) 2023 The Android Open Source Project +  ~ +  ~ Licensed under the Apache License, Version 2.0 (the "License"); +  ~ you may not use this file except in compliance with the License. +  ~ You may obtain a copy of the License at +  ~ +  ~      http://www.apache.org/licenses/LICENSE-2.0 +  ~ +  ~ Unless required by applicable law or agreed to in writing, software +  ~ distributed under the License is distributed on an "AS IS" BASIS, +  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +  ~ See the License for the specific language governing permissions and +  ~ limitations under the License. +  ~ +  --> +<network-capabilities-declaration xmlns:android="http://schemas.android.com/apk/res/android"> +    <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_LATENCY"/> +</network-capabilities-declaration> diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt index 3360c967a182..aef83710c17b 100644 --- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt +++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt @@ -24,7 +24,7 @@ object FlagsFactory {      val knownFlags: Map<String, Flag<*>>          get() {              // We need to access Flags in order to initialize our map. -            assert(flagMap.contains(Flags.TEAMFOOD.name)) { "Where is teamfood?" } +            assert(flagMap.contains(Flags.NULL_FLAG.name)) { "Where is the null flag?" }              return flagMap          } diff --git a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt index 75465c27724d..f4b429659d8a 100644 --- a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt +++ b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt @@ -24,7 +24,7 @@ object FlagsFactory {      val knownFlags: Map<String, Flag<*>>          get() {              // We need to access Flags in order to initialize our map. -            assert(flagMap.contains(Flags.TEAMFOOD.name)) { "Where is teamfood?" } +            assert(flagMap.contains(Flags.NULL_FLAG.name)) { "Where is the null flag?" }              return flagMap          } diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index 01a75d9e0f16..e47d36f76a14 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -15,6 +15,8 @@   */  package com.android.keyguard +import com.android.systemui.keyguard.shared.model.KeyguardState.AOD +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN  import android.content.BroadcastReceiver  import android.content.Context  import android.content.Intent @@ -37,7 +39,7 @@ import com.android.systemui.dagger.qualifiers.Background  import com.android.systemui.dagger.qualifiers.DisplaySpecific  import com.android.systemui.dagger.qualifiers.Main  import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags.DOZING_MIGRATION_1 +import com.android.systemui.flags.Flags.MIGRATE_KEYGUARD_STATUS_VIEW  import com.android.systemui.flags.Flags.REGION_SAMPLING  import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor  import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor @@ -61,6 +63,7 @@ import kotlinx.coroutines.DisposableHandle  import kotlinx.coroutines.Job  import kotlinx.coroutines.flow.combine  import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.merge  import kotlinx.coroutines.launch  import java.util.Locale  import java.util.TimeZone @@ -297,7 +300,7 @@ constructor(          object : KeyguardUpdateMonitorCallback() {              override fun onKeyguardVisibilityChanged(visible: Boolean) {                  isKeyguardVisible = visible -                if (!featureFlags.isEnabled(DOZING_MIGRATION_1)) { +                if (!featureFlags.isEnabled(MIGRATE_KEYGUARD_STATUS_VIEW)) {                      if (!isKeyguardVisible) {                          clock?.run {                              smallClock.animations.doze(if (isDozing) 1f else 0f) @@ -342,9 +345,9 @@ constructor(          keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)          disposableHandle =              parent.repeatWhenAttached { -                repeatOnLifecycle(Lifecycle.State.STARTED) { +                repeatOnLifecycle(Lifecycle.State.CREATED) {                      listenForDozing(this) -                    if (featureFlags.isEnabled(DOZING_MIGRATION_1)) { +                    if (featureFlags.isEnabled(MIGRATE_KEYGUARD_STATUS_VIEW)) {                          listenForDozeAmountTransition(this)                          listenForAnyStateToAodTransition(this)                      } else { @@ -454,8 +457,9 @@ constructor(      @VisibleForTesting      internal fun listenForAnyStateToAodTransition(scope: CoroutineScope): Job {          return scope.launch { -            keyguardTransitionInteractor.anyStateToAodTransition -                .filter { it.transitionState == TransitionState.FINISHED } +            keyguardTransitionInteractor.transitionStepsToState(AOD) +                .filter { it.transitionState == TransitionState.STARTED } +                .filter { it.from != LOCKSCREEN }                  .collect { handleDoze(1f) }          }      } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java index 959cf6fb8565..01fc03516edc 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java @@ -51,7 +51,6 @@ import java.util.List;  public class KeyguardPasswordViewController          extends KeyguardAbsKeyInputViewController<KeyguardPasswordView> { -    private static final int DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON = 500;  // 500ms      private final KeyguardSecurityCallback mKeyguardSecurityCallback;      private final DevicePostureController mPostureController;      private final DevicePostureController.Callback mPostureCallback = posture -> @@ -164,13 +163,6 @@ public class KeyguardPasswordViewController          // If there's more than one IME, enable the IME switcher button          updateSwitchImeButton(); - -        // When we the current user is switching, InputMethodManagerService sometimes has not -        // switched internal state yet here. As a quick workaround, we check the keyboard state -        // again. -        // TODO: Remove this workaround by ensuring such a race condition never happens. -        mMainExecutor.executeDelayed( -                this::updateSwitchImeButton, DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON);      }      @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java index 79642bdae1ce..758d1fef6e2e 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java @@ -54,6 +54,9 @@ import com.android.systemui.dump.DumpManager;  import com.android.systemui.flags.FeatureFlags;  import com.android.systemui.flags.Flags;  import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; +import com.android.systemui.keyguard.shared.model.TransitionState; +import com.android.systemui.keyguard.shared.model.TransitionStep;  import com.android.systemui.plugins.ClockController;  import com.android.systemui.power.domain.interactor.PowerInteractor;  import com.android.systemui.power.shared.model.ScreenPowerState; @@ -102,10 +105,11 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV      private final Rect mClipBounds = new Rect();      private final KeyguardInteractor mKeyguardInteractor;      private final PowerInteractor mPowerInteractor; +    private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;      private Boolean mSplitShadeEnabled = false;      private Boolean mStatusViewCentered = true; - +    private boolean mGoneToAodTransitionRunning = false;      private DumpManager mDumpManager;      private final TransitionListenerAdapter mKeyguardStatusAlignmentTransitionListener = @@ -135,6 +139,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV              FeatureFlags featureFlags,              InteractionJankMonitor interactionJankMonitor,              KeyguardInteractor keyguardInteractor, +            KeyguardTransitionInteractor keyguardTransitionInteractor,              DumpManager dumpManager,              PowerInteractor powerInteractor) {          super(keyguardStatusView); @@ -144,12 +149,13 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV          mConfigurationController = configurationController;          mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController,                  dozeParameters, screenOffAnimationController, /* animateYPos= */ true, -                logger.getBuffer()); +                featureFlags, logger.getBuffer());          mInteractionJankMonitor = interactionJankMonitor;          mFeatureFlags = featureFlags;          mDumpManager = dumpManager;          mKeyguardInteractor = keyguardInteractor;          mPowerInteractor = powerInteractor; +        mKeyguardTransitionInteractor = keyguardTransitionInteractor;      }      @Override @@ -199,6 +205,15 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV                          dozeTimeTick();                      }                  }, context); + +        collectFlow(mView, mKeyguardTransitionInteractor.getGoneToAodTransition(), +                (TransitionStep step) -> { +                    if (step.getTransitionState() == TransitionState.RUNNING) { +                        mGoneToAodTransitionRunning = true; +                    } else { +                        mGoneToAodTransitionRunning = false; +                    } +                }, context);      }      public KeyguardStatusView getView() { @@ -266,7 +281,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV       * Set keyguard status view alpha.       */      public void setAlpha(float alpha) { -        if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) { +        if (!mKeyguardVisibilityHelper.isVisibilityAnimating() && !mGoneToAodTransitionRunning) {              mView.setAlpha(alpha);          }      } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index b7bb35eb6783..205c297cebc4 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -153,7 +153,6 @@ import com.android.settingslib.Utils;  import com.android.settingslib.WirelessUtils;  import com.android.settingslib.fuelgauge.BatteryStatus;  import com.android.systemui.Dumpable; -import com.android.systemui.res.R;  import com.android.systemui.biometrics.AuthController;  import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;  import com.android.systemui.broadcast.BroadcastDispatcher; @@ -177,6 +176,7 @@ import com.android.systemui.keyguard.shared.model.SysUiFaceAuthenticateOptions;  import com.android.systemui.log.SessionTracker;  import com.android.systemui.plugins.WeatherData;  import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.res.R;  import com.android.systemui.settings.DisplayTracker;  import com.android.systemui.settings.UserTracker;  import com.android.systemui.shared.system.TaskStackChangeListener; @@ -1984,6 +1984,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab                  @Override                  public void onAuthenticationAcquired(int acquireInfo) {                      Trace.beginSection("KeyguardUpdateMonitor#onAuthenticationAcquired"); +                    mLogger.logFingerprintAcquired(acquireInfo);                      handleFingerprintAcquired(acquireInfo);                      Trace.endSection();                  } @@ -3462,7 +3463,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab      @Deprecated      private boolean isUnlockWithFacePossible(int userId) {          if (isFaceAuthInteractorEnabled()) { -            return getFaceAuthInteractor().canFaceAuthRun(); +            return getFaceAuthInteractor() != null +                    && getFaceAuthInteractor().isFaceAuthEnabledAndEnrolled();          }          return isFaceAuthEnabledForUser(userId) && !isFaceDisabled(userId);      } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java index c64ae0106b4a..d524e4a8c408 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java @@ -23,6 +23,8 @@ import android.util.Property;  import android.view.View;  import com.android.app.animation.Interpolators; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags;  import com.android.systemui.log.LogBuffer;  import com.android.systemui.log.core.LogLevel;  import com.android.systemui.statusbar.StatusBarState; @@ -53,6 +55,7 @@ public class KeyguardVisibilityHelper {      private boolean mKeyguardViewVisibilityAnimating;      private boolean mLastOccludedState = false;      private final AnimationProperties mAnimationProperties = new AnimationProperties(); +    private final FeatureFlags mFeatureFlags;      private final LogBuffer mLogBuffer;      public KeyguardVisibilityHelper(View view, @@ -60,12 +63,14 @@ public class KeyguardVisibilityHelper {              DozeParameters dozeParameters,              ScreenOffAnimationController screenOffAnimationController,              boolean animateYPos, +            FeatureFlags featureFlags,              LogBuffer logBuffer) {          mView = view;          mKeyguardStateController = keyguardStateController;          mDozeParameters = dozeParameters;          mScreenOffAnimationController = screenOffAnimationController;          mAnimateYPos = animateYPos; +        mFeatureFlags = featureFlags;          mLogBuffer = logBuffer;      } @@ -162,13 +167,17 @@ public class KeyguardVisibilityHelper {                          animProps,                          true /* animate */);              } else if (mScreenOffAnimationController.shouldAnimateInKeyguard()) { -                log("ScreenOff transition"); -                mKeyguardViewVisibilityAnimating = true; +                if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { +                    log("Using GoneToAodTransition"); +                    mKeyguardViewVisibilityAnimating = false; +                } else { +                    log("ScreenOff transition"); +                    mKeyguardViewVisibilityAnimating = true; -                // Ask the screen off animation controller to animate the keyguard visibility for us -                // since it may need to be cancelled due to keyguard lifecycle events. -                mScreenOffAnimationController.animateInKeyguard( -                        mView, mSetVisibleEndRunnable); +                    // Ask the screen off animation controller to animate the keyguard visibility +                    // for us since it may need to be cancelled due to keyguard lifecycle events. +                    mScreenOffAnimationController.animateInKeyguard(mView, mSetVisibleEndRunnable); +                }              } else {                  log("Direct set Visibility to VISIBLE");                  mView.setVisibility(View.VISIBLE); diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt index fe19616cef1d..fa07072b7fe1 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt @@ -44,6 +44,7 @@ import com.google.errorprone.annotations.CompileTimeConstant  import javax.inject.Inject  private const val TAG = "KeyguardUpdateMonitorLog" +private const val FP_LOG_TAG = "KeyguardFingerprintLog"  /** Helper class for logging for [com.android.keyguard.KeyguardUpdateMonitor] */  class KeyguardUpdateMonitorLogger @@ -157,7 +158,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {      fun logFingerprintAuthForWrongUser(authUserId: Int) {          logBuffer.log( -            TAG, +            FP_LOG_TAG,              DEBUG,              { int1 = authUserId },              { "Fingerprint authenticated for wrong user: $int1" } @@ -166,7 +167,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {      fun logFingerprintDisabledForUser(userId: Int) {          logBuffer.log( -            TAG, +            FP_LOG_TAG,              DEBUG,              { int1 = userId },              { "Fingerprint disabled by DPM for userId: $int1" } @@ -174,12 +175,17 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {      }      fun logFingerprintLockoutReset(@LockoutMode mode: Int) { -        logBuffer.log(TAG, DEBUG, { int1 = mode }, { "handleFingerprintLockoutReset: $int1" }) +        logBuffer.log( +            FP_LOG_TAG, +            DEBUG, +            { int1 = mode }, +            { "handleFingerprintLockoutReset: $int1" } +        )      }      fun logFingerprintRunningState(fingerprintRunningState: Int) {          logBuffer.log( -            TAG, +            FP_LOG_TAG,              DEBUG,              { int1 = fingerprintRunningState },              { "fingerprintRunningState: $int1" } @@ -188,7 +194,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {      fun logFingerprintSuccess(userId: Int, isStrongBiometric: Boolean) {          logBuffer.log( -            TAG, +            FP_LOG_TAG,              DEBUG,              {                  int1 = userId @@ -212,7 +218,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {      fun logFingerprintDetected(userId: Int, isStrongBiometric: Boolean) {          logBuffer.log( -            TAG, +            FP_LOG_TAG,              DEBUG,              {                  int1 = userId @@ -224,7 +230,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {      fun logFingerprintError(msgId: Int, originalErrMsg: String) {          logBuffer.log( -            TAG, +            FP_LOG_TAG,              DEBUG,              {                  str1 = originalErrMsg @@ -751,4 +757,25 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {              { "userSwitchComplete: $str1, userId: $int1" }          )      } + +    fun logFingerprintHelp(helpMsgId: Int, helpString: CharSequence) { +        logBuffer.log( +            FP_LOG_TAG, +            DEBUG, +            { +                int1 = helpMsgId +                str1 = "$helpString" +            }, +            { "fingerprint help message: $int1, $str1" } +        ) +    } + +    fun logFingerprintAcquired(acquireInfo: Int) { +        logBuffer.log( +            FP_LOG_TAG, +            DEBUG, +            { int1 = acquireInfo }, +            { "fingerprint acquire message: $int1" } +        ) +    }  } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt index c1f6259d94f7..40f229b7004a 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt @@ -41,6 +41,8 @@ import android.view.LayoutInflater  import android.view.Surface  import android.view.View  import android.view.View.AccessibilityDelegate +import android.view.View.INVISIBLE +import android.view.View.VISIBLE  import android.view.ViewPropertyAnimator  import android.view.WindowManager  import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION @@ -54,13 +56,13 @@ import com.android.app.animation.Interpolators  import com.android.internal.annotations.VisibleForTesting  import com.android.keyguard.KeyguardPINView  import com.android.systemui.Dumpable -import com.android.systemui.res.R  import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor  import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor  import com.android.systemui.dagger.SysUISingleton  import com.android.systemui.dagger.qualifiers.Application  import com.android.systemui.dagger.qualifiers.Main  import com.android.systemui.dump.DumpManager +import com.android.systemui.res.R  import com.android.systemui.util.boundsOnScreen  import com.android.systemui.util.concurrency.DelayableExecutor  import com.android.systemui.util.traceSection @@ -229,6 +231,20 @@ constructor(          }      } +    /** Hide the arrow indicator. */ +    fun hideIndicator() { +        val lottieAnimationView = +            overlayView?.findViewById(R.id.sidefps_animation) as LottieAnimationView? +        lottieAnimationView?.visibility = INVISIBLE +    } + +    /** Show the arrow indicator. */ +    fun showIndicator() { +        val lottieAnimationView = +            overlayView?.findViewById(R.id.sidefps_animation) as LottieAnimationView? +        lottieAnimationView?.visibility = VISIBLE +    } +      override fun dump(pw: PrintWriter, args: Array<out String>) {          pw.println("requests:")          for (requestSource in requests) { @@ -247,6 +263,10 @@ constructor(          pw.println("     displayId=${displayInfo.uniqueId}")          pw.println("     sensorType=${sensorProps?.sensorType}")          pw.println("     location=${sensorProps?.getLocation(displayInfo.uniqueId)}") +        pw.println("lottieAnimationView:") +        pw.println( +            "     visibility=${overlayView?.findViewById<View>(R.id.sidefps_animation)?.visibility}" +        )          pw.println("overlayOffsets=$overlayOffsets")          pw.println("isReverseDefaultRotation=$isReverseDefaultRotation") @@ -498,5 +518,5 @@ enum class SideFpsUiRequestSource {      AUTO_SHOW,      /** Pin, pattern or password bouncer */      PRIMARY_BOUNCER, -    ALTERNATE_BOUNCER +    ALTERNATE_BOUNCER,  } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/BiometricsDomainLayerModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/BiometricsDomainLayerModule.kt index a590dccd5318..b9b2fd8875d9 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/BiometricsDomainLayerModule.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/BiometricsDomainLayerModule.kt @@ -23,8 +23,6 @@ import com.android.systemui.biometrics.domain.interactor.LogContextInteractor  import com.android.systemui.biometrics.domain.interactor.LogContextInteractorImpl  import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor  import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractorImpl -import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractor -import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractorImpl  import com.android.systemui.dagger.SysUISingleton  import dagger.Binds  import dagger.Module @@ -49,10 +47,4 @@ interface BiometricsDomainLayerModule {      @Binds      @SysUISingleton      fun bindsLogContextInteractor(impl: LogContextInteractorImpl): LogContextInteractor - -    @Binds -    @SysUISingleton -    fun providesSideFpsOverlayInteractor( -        impl: SideFpsOverlayInteractorImpl -    ): SideFpsOverlayInteractor  } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt index f36a3ec89e3f..a317a0684055 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt @@ -54,6 +54,9 @@ interface DisplayStateInteractor {      /** Current rotation of the display */      val currentRotation: StateFlow<DisplayRotation> +    /** Display change event indicating a change to the given displayId has occurred. */ +    val displayChanges: Flow<Int> +      /** Called on configuration changes, used to keep the display state in sync */      fun onConfigurationChanged(newConfig: Configuration)  } @@ -74,6 +77,8 @@ constructor(          screenSizeFoldProvider = foldProvider      } +    override val displayChanges = displayRepository.displayChangeEvent +      override val isFolded: Flow<Boolean> =          conflatedCallbackFlow {                  val sendFoldStateUpdate = { state: Boolean -> diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt deleted file mode 100644 index 75ae061f8cce..000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - *      http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.biometrics.domain.interactor - -import android.hardware.biometrics.SensorLocationInternal -import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository -import com.android.systemui.dagger.SysUISingleton -import javax.inject.Inject -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.combine - -/** Business logic for SideFps overlay offsets. */ -interface SideFpsOverlayInteractor { - -    /** The displayId of the current display. */ -    val displayId: Flow<String> - -    /** Overlay offsets corresponding to given displayId. */ -    val overlayOffsets: Flow<SensorLocationInternal> - -    /** Called on display changes, used to keep the display state in sync */ -    fun onDisplayChanged(displayId: String) -} - -@SysUISingleton -class SideFpsOverlayInteractorImpl -@Inject -constructor(fingerprintPropertyRepository: FingerprintPropertyRepository) : -    SideFpsOverlayInteractor { - -    private val _displayId: MutableStateFlow<String> = MutableStateFlow("") -    override val displayId: Flow<String> = _displayId.asStateFlow() - -    override val overlayOffsets: Flow<SensorLocationInternal> = -        combine(displayId, fingerprintPropertyRepository.sensorLocations) { displayId, offsets -> -            offsets[displayId] ?: SensorLocationInternal.DEFAULT -        } - -    override fun onDisplayChanged(displayId: String) { -        _displayId.value = displayId -    } - -    companion object { -        private const val TAG = "SideFpsOverlayInteractorImpl" -    } -} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt new file mode 100644 index 000000000000..f85203ea2076 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics.domain.interactor + +import android.content.Context +import android.hardware.biometrics.SensorLocationInternal +import android.view.WindowManager +import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository +import com.android.systemui.biometrics.domain.model.SideFpsSensorLocation +import com.android.systemui.biometrics.shared.model.DisplayRotation +import com.android.systemui.biometrics.shared.model.FingerprintSensorType +import com.android.systemui.biometrics.shared.model.isDefaultOrientation +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.flags.FeatureFlagsClassic +import com.android.systemui.flags.Flags +import com.android.systemui.res.R +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map + +@OptIn(ExperimentalCoroutinesApi::class) +@SysUISingleton +class SideFpsSensorInteractor +@Inject +constructor( +    private val context: Context, +    fingerprintPropertyRepository: FingerprintPropertyRepository, +    windowManager: WindowManager, +    displayStateInteractor: DisplayStateInteractor, +    featureFlags: FeatureFlagsClassic, +) { + +    private val sensorForCurrentDisplay = +        combine( +                displayStateInteractor.displayChanges, +                fingerprintPropertyRepository.sensorLocations, +                ::Pair +            ) +            .map { (_, locations) -> locations[context.display?.uniqueId] } +            .filterNotNull() + +    val isAvailable: Flow<Boolean> = +        fingerprintPropertyRepository.sensorType.map { it == FingerprintSensorType.POWER_BUTTON } + +    val authenticationDuration: Flow<Long> = +        flowOf(context.resources?.getInteger(R.integer.config_restToUnlockDuration)?.toLong() ?: 0L) + +    val isProlongedTouchRequiredForAuthentication: Flow<Boolean> = +        isAvailable.flatMapLatest { sfpsAvailable -> +            if (sfpsAvailable) { +                // todo (b/305236201) also add the settings check here. +                flowOf(featureFlags.isEnabled(Flags.REST_TO_UNLOCK)) +            } else { +                flowOf(false) +            } +        } + +    val sensorLocation: Flow<SideFpsSensorLocation> = +        combine(displayStateInteractor.currentRotation, sensorForCurrentDisplay, ::Pair).map { +            (rotation, sensorLocation: SensorLocationInternal) -> +            val isSensorVerticalInDefaultOrientation = sensorLocation.sensorLocationY != 0 +            // device dimensions in the current rotation +            val size = windowManager.maximumWindowMetrics.bounds +            val isDefaultOrientation = rotation.isDefaultOrientation() +            // Width and height are flipped is device is not in rotation_0 or rotation_180 +            // Flipping it to the width and height of the device in default orientation. +            val displayWidth = if (isDefaultOrientation) size.width() else size.height() +            val displayHeight = if (isDefaultOrientation) size.height() else size.width() +            val sensorWidth = context.resources?.getInteger(R.integer.config_sfpsSensorWidth) ?: 0 + +            val (sensorLeft, sensorTop) = +                if (isSensorVerticalInDefaultOrientation) { +                    when (rotation) { +                        DisplayRotation.ROTATION_0 -> { +                            Pair(displayWidth, sensorLocation.sensorLocationY) +                        } +                        DisplayRotation.ROTATION_90 -> { +                            Pair(sensorLocation.sensorLocationY, 0) +                        } +                        DisplayRotation.ROTATION_180 -> { +                            Pair(0, displayHeight - sensorLocation.sensorLocationY - sensorWidth) +                        } +                        DisplayRotation.ROTATION_270 -> { +                            Pair( +                                displayHeight - sensorLocation.sensorLocationY - sensorWidth, +                                displayWidth +                            ) +                        } +                    } +                } else { +                    when (rotation) { +                        DisplayRotation.ROTATION_0 -> { +                            Pair(sensorLocation.sensorLocationX, 0) +                        } +                        DisplayRotation.ROTATION_90 -> { +                            Pair(0, displayWidth - sensorLocation.sensorLocationX - sensorWidth) +                        } +                        DisplayRotation.ROTATION_180 -> { +                            Pair( +                                displayWidth - sensorLocation.sensorLocationX - sensorWidth, +                                displayHeight +                            ) +                        } +                        DisplayRotation.ROTATION_270 -> { +                            Pair(displayHeight, sensorLocation.sensorLocationX) +                        } +                    } +                } + +            SideFpsSensorLocation( +                left = sensorLeft, +                top = sensorTop, +                width = sensorWidth, +                isSensorVerticalInDefaultOrientation = isSensorVerticalInDefaultOrientation +            ) +        } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/SideFpsSensorLocation.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/SideFpsSensorLocation.kt new file mode 100644 index 000000000000..35f8e3bb461f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/SideFpsSensorLocation.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics.domain.model + +data class SideFpsSensorLocation( +    /** Pixel offset from the left of the screen */ +    val left: Int, +    /** Pixel offset from the top of the screen */ +    val top: Int, +    /** Width in pixels of the SFPS sensor */ +    val width: Int, +    /** +     * Whether the sensor is vertical when the device is in its default orientation (Rotation_0 or +     * Rotation_180) +     */ +    val isSensorVerticalInDefaultOrientation: Boolean +) diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/DisplayRotation.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/DisplayRotation.kt index 10a3e915fe80..336404c04dbf 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/DisplayRotation.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/DisplayRotation.kt @@ -10,6 +10,9 @@ enum class DisplayRotation {      ROTATION_270,  } +fun DisplayRotation.isDefaultOrientation() = +    this == DisplayRotation.ROTATION_0 || this == DisplayRotation.ROTATION_180 +  /** Converts [Surface.Rotation] to corresponding [DisplayRotation] */  fun Int.toDisplayRotation(): DisplayRotation =      when (this) { @@ -19,3 +22,12 @@ fun Int.toDisplayRotation(): DisplayRotation =          Surface.ROTATION_270 -> DisplayRotation.ROTATION_270          else -> throw IllegalArgumentException("Invalid DisplayRotation value: $this")      } + +/** Converts [DisplayRotation] to corresponding [Surface.Rotation] */ +fun DisplayRotation.toRotation(): Int = +    when (this) { +        DisplayRotation.ROTATION_0 -> Surface.ROTATION_0 +        DisplayRotation.ROTATION_90 -> Surface.ROTATION_90 +        DisplayRotation.ROTATION_180 -> Surface.ROTATION_180 +        DisplayRotation.ROTATION_270 -> Surface.ROTATION_270 +    } diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt new file mode 100644 index 000000000000..b8e2de404628 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.dagger + +import com.android.systemui.communal.data.repository.CommunalRepositoryModule +import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule +import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule +import dagger.Module + +@Module( +    includes = +        [ +            CommunalRepositoryModule::class, +            CommunalTutorialRepositoryModule::class, +            CommunalWidgetRepositoryModule::class, +        ] +) +class CommunalModule diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetMetadata.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetMetadata.kt new file mode 100644 index 000000000000..f9c4f29afee9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetMetadata.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.data.model + +import com.android.systemui.communal.shared.CommunalContentSize + +/** Metadata for the default widgets */ +data class CommunalWidgetMetadata( +    /* Widget provider component name */ +    val componentName: String, + +    /* Defines the order in which the widget will be rendered in the grid. */ +    val priority: Int, + +    /* Supported sizes */ +    val sizes: List<CommunalContentSize> +) diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt index e2a7d077a32c..f13b62fbfeb9 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt @@ -27,13 +27,17 @@ import android.content.pm.PackageManager  import android.os.UserManager  import com.android.systemui.broadcast.BroadcastDispatcher  import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging +import com.android.systemui.communal.data.model.CommunalWidgetMetadata  import com.android.systemui.communal.shared.CommunalAppWidgetInfo +import com.android.systemui.communal.shared.CommunalContentSize  import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application  import com.android.systemui.flags.FeatureFlags  import com.android.systemui.flags.Flags  import com.android.systemui.log.LogBuffer  import com.android.systemui.log.core.Logger  import com.android.systemui.log.dagger.CommunalLog +import com.android.systemui.res.R  import com.android.systemui.settings.UserTracker  import javax.inject.Inject  import kotlinx.coroutines.channels.awaitClose @@ -45,15 +49,20 @@ import kotlinx.coroutines.flow.map  interface CommunalWidgetRepository {      /** A flow of provider info for the stopwatch widget, or null if widget is unavailable. */      val stopwatchAppWidgetInfo: Flow<CommunalAppWidgetInfo?> + +    /** Widgets that are allowed to render in the glanceable hub */ +    val communalWidgetAllowlist: List<CommunalWidgetMetadata>  }  @SysUISingleton  class CommunalWidgetRepositoryImpl  @Inject  constructor( +    @Application private val applicationContext: Context,      private val appWidgetManager: AppWidgetManager,      private val appWidgetHost: AppWidgetHost,      broadcastDispatcher: BroadcastDispatcher, +    communalRepository: CommunalRepository,      private val packageManager: PackageManager,      private val userManager: UserManager,      private val userTracker: UserTracker, @@ -64,12 +73,18 @@ constructor(          const val TAG = "CommunalWidgetRepository"          const val WIDGET_LABEL = "Stopwatch"      } +    override val communalWidgetAllowlist: List<CommunalWidgetMetadata>      private val logger = Logger(logBuffer, TAG)      // Whether the [AppWidgetHost] is listening for updates.      private var isHostListening = false +    init { +        communalWidgetAllowlist = +            if (communalRepository.isCommunalEnabled) getWidgetAllowlist() else emptyList() +    } +      // Widgets that should be rendered in communal mode.      private val widgets: HashMap<Int, CommunalAppWidgetInfo> = hashMapOf() @@ -129,6 +144,18 @@ constructor(              return@map addWidget(providerInfo)          } +    private fun getWidgetAllowlist(): List<CommunalWidgetMetadata> { +        val componentNames = +            applicationContext.resources.getStringArray(R.array.config_communalWidgetAllowlist) +        return componentNames.mapIndexed { index, name -> +            CommunalWidgetMetadata( +                componentName = name, +                priority = componentNames.size - index, +                sizes = listOf(CommunalContentSize.HALF) +            ) +        } +    } +      private fun startListening() {          if (isHostListening) {              return diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalContentSize.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalContentSize.kt new file mode 100644 index 000000000000..0bd7d86c972d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalContentSize.kt @@ -0,0 +1,8 @@ +package com.android.systemui.communal.shared + +/** Supported sizes for communal content in the layout grid. */ +enum class CommunalContentSize { +    FULL, +    HALF, +    THIRD, +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalHubSection.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalHubSection.kt index 932dbfb093ce..ad02f6280a64 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalHubSection.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalHubSection.kt @@ -2,6 +2,7 @@ package com.android.systemui.communal.ui.view.layout.sections  import androidx.constraintlayout.widget.ConstraintLayout  import androidx.constraintlayout.widget.ConstraintSet +import com.android.systemui.communal.ui.viewmodel.CommunalViewModel  import com.android.systemui.compose.ComposeFacade  import com.android.systemui.keyguard.shared.model.KeyguardSection  import com.android.systemui.keyguard.ui.view.layout.sections.removeView @@ -9,14 +10,20 @@ import com.android.systemui.res.R  import javax.inject.Inject  /** A keyguard section that hosts the communal hub. */ -class DefaultCommunalHubSection @Inject constructor() : KeyguardSection() { +class DefaultCommunalHubSection +@Inject +constructor( +    private val viewModel: CommunalViewModel, +) : KeyguardSection() {      private val communalHubViewId = R.id.communal_hub      override fun addViews(constraintLayout: ConstraintLayout) {          constraintLayout.addView( -            ComposeFacade.createCommunalView(constraintLayout.context).apply { -                id = communalHubViewId -            }, +            ComposeFacade.createCommunalView( +                    context = constraintLayout.context, +                    viewModel = viewModel, +                ) +                .apply { id = communalHubViewId },          )      } diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt new file mode 100644 index 000000000000..ddeb1d67b945 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.ui.viewmodel + +import com.android.systemui.communal.domain.interactor.CommunalTutorialInteractor +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow + +@SysUISingleton +class CommunalViewModel +@Inject +constructor( +    tutorialInteractor: CommunalTutorialInteractor, +) { +    /** Whether communal hub should show tutorial content. */ +    val showTutorialContent: Flow<Boolean> = tutorialInteractor.isTutorialAvailable +} diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt index 5c1539a7fcf3..9221832b4ba1 100644 --- a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt +++ b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt @@ -22,6 +22,7 @@ import android.view.View  import android.view.WindowInsets  import androidx.activity.ComponentActivity  import androidx.lifecycle.LifecycleOwner +import com.android.systemui.communal.ui.viewmodel.CommunalViewModel  import com.android.systemui.people.ui.viewmodel.PeopleViewModel  import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel  import com.android.systemui.scene.shared.model.Scene @@ -73,8 +74,9 @@ interface BaseComposeFacade {          sceneByKey: Map<SceneKey, Scene>,      ): View -    /** Create a [View] that represents the communal hub. */ +    /** Create a [View] to represent [viewModel] on screen. */      fun createCommunalView(          context: Context, +        viewModel: CommunalViewModel,      ): View  } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java index 04a9cae31382..d57f31f91df1 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java @@ -67,6 +67,7 @@ import android.hardware.input.InputManager;  import android.media.AudioManager;  import android.media.IAudioService;  import android.media.MediaRouter2Manager; +import android.media.projection.IMediaProjectionManager;  import android.media.projection.MediaProjectionManager;  import android.media.session.MediaSessionManager;  import android.net.ConnectivityManager; @@ -414,6 +415,13 @@ public class FrameworkServicesModule {      }      @Provides +    @Singleton +    static IMediaProjectionManager provideIMediaProjectionManager() { +        return IMediaProjectionManager.Stub.asInterface( +                ServiceManager.getService(Context.MEDIA_PROJECTION_SERVICE)); +    } + +    @Provides      static MediaRouter2Manager provideMediaRouter2Manager(Context context) {          return MediaRouter2Manager.getInstance(context);      } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 7d4e1a1011db..f0d7592d8940 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -42,6 +42,7 @@ import com.android.systemui.bouncer.ui.BouncerViewModule;  import com.android.systemui.classifier.FalsingModule;  import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule;  import com.android.systemui.common.ui.data.repository.CommonRepositoryModule; +import com.android.systemui.communal.dagger.CommunalModule;  import com.android.systemui.complication.dagger.ComplicationComponent;  import com.android.systemui.controls.dagger.ControlsModule;  import com.android.systemui.dagger.qualifiers.Main; @@ -170,6 +171,7 @@ import javax.inject.Named;          ClipboardOverlayModule.class,          ClockRegistryModule.class,          CommonRepositoryModule.class, +        CommunalModule.class,          ConnectivityModule.class,          ControlsModule.class,          CoroutinesModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt index e7f835f7b858..c3aaef76cb2f 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt @@ -1,5 +1,6 @@  package com.android.systemui.deviceentry +import com.android.systemui.deviceentry.data.repository.DeviceEntryHapticsRepositoryModule  import com.android.systemui.deviceentry.data.repository.DeviceEntryRepositoryModule  import dagger.Module @@ -7,6 +8,7 @@ import dagger.Module      includes =          [              DeviceEntryRepositoryModule::class, +            DeviceEntryHapticsRepositoryModule::class,          ],  )  object DeviceEntryModule diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepository.kt new file mode 100644 index 000000000000..1458404446e6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepository.kt @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.deviceentry.data.repository + +import com.android.systemui.dagger.SysUISingleton +import dagger.Binds +import dagger.Module +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow + +/** Interface for classes that can access device-entry haptics application state. */ +interface DeviceEntryHapticsRepository { +    /** +     * Whether a successful biometric haptic has been requested. Has not yet been handled if true. +     */ +    val successHapticRequest: Flow<Boolean> + +    /** Whether an error biometric haptic has been requested. Has not yet been handled if true. */ +    val errorHapticRequest: Flow<Boolean> + +    fun requestSuccessHaptic() +    fun handleSuccessHaptic() +    fun requestErrorHaptic() +    fun handleErrorHaptic() +} + +/** Encapsulates application state for device entry haptics. */ +@SysUISingleton +class DeviceEntryHapticsRepositoryImpl @Inject constructor() : DeviceEntryHapticsRepository { +    private val _successHapticRequest = MutableStateFlow(false) +    override val successHapticRequest: Flow<Boolean> = _successHapticRequest.asStateFlow() + +    private val _errorHapticRequest = MutableStateFlow(false) +    override val errorHapticRequest: Flow<Boolean> = _errorHapticRequest.asStateFlow() + +    override fun requestSuccessHaptic() { +        _successHapticRequest.value = true +    } + +    override fun handleSuccessHaptic() { +        _successHapticRequest.value = false +    } + +    override fun requestErrorHaptic() { +        _errorHapticRequest.value = true +    } + +    override fun handleErrorHaptic() { +        _errorHapticRequest.value = false +    } +} + +@Module +interface DeviceEntryHapticsRepositoryModule { +    @Binds fun repository(impl: DeviceEntryHapticsRepositoryImpl): DeviceEntryHapticsRepository +} diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt new file mode 100644 index 000000000000..53d6f737af8d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.deviceentry.domain.interactor + +import com.android.keyguard.logging.BiometricUnlockLogger +import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository +import com.android.systemui.biometrics.shared.model.FingerprintSensorType +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.deviceentry.data.repository.DeviceEntryHapticsRepository +import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor +import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository +import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.power.shared.model.WakeSleepReason +import com.android.systemui.util.kotlin.sample +import com.android.systemui.util.time.SystemClock +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.combineTransform +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart + +/** + * Business logic for device entry haptic events. Determines whether the haptic should play. In + * particular, there are extra guards for whether device entry error and successes hatpics should + * play when the physical fingerprint sensor is located on the power button. + */ +@ExperimentalCoroutinesApi +@SysUISingleton +class DeviceEntryHapticsInteractor +@Inject +constructor( +    private val repository: DeviceEntryHapticsRepository, +    fingerprintPropertyRepository: FingerprintPropertyRepository, +    biometricSettingsRepository: BiometricSettingsRepository, +    keyEventInteractor: KeyEventInteractor, +    powerInteractor: PowerInteractor, +    private val systemClock: SystemClock, +    private val logger: BiometricUnlockLogger, +) { +    private val powerButtonSideFpsEnrolled = +        combineTransform( +                fingerprintPropertyRepository.sensorType, +                biometricSettingsRepository.isFingerprintEnrolledAndEnabled, +            ) { sensorType, enrolledAndEnabled -> +                if (sensorType == FingerprintSensorType.POWER_BUTTON) { +                    emit(enrolledAndEnabled) +                } else { +                    emit(false) +                } +            } +            .distinctUntilChanged() +    private val powerButtonDown: Flow<Boolean> = keyEventInteractor.isPowerButtonDown +    private val lastPowerButtonWakeup: Flow<Long> = +        powerInteractor.detailedWakefulness +            .filter { it.isAwakeFrom(WakeSleepReason.POWER_BUTTON) } +            .map { systemClock.uptimeMillis() } +            .onStart { +                // If the power button hasn't been pressed, we still want this to evaluate to true: +                // `uptimeMillis() - lastPowerButtonWakeup > recentPowerButtonPressThresholdMs` +                emit(recentPowerButtonPressThresholdMs * -1L - 1L) +            } + +    val playSuccessHaptic: Flow<Boolean> = +        repository.successHapticRequest +            .filter { it } +            .sample( +                combine( +                    powerButtonSideFpsEnrolled, +                    powerButtonDown, +                    lastPowerButtonWakeup, +                    ::Triple +                ) +            ) +            .map { (sideFpsEnrolled, powerButtonDown, lastPowerButtonWakeup) -> +                val sideFpsAllowsHaptic = +                    !powerButtonDown && +                        systemClock.uptimeMillis() - lastPowerButtonWakeup > +                            recentPowerButtonPressThresholdMs +                val allowHaptic = !sideFpsEnrolled || sideFpsAllowsHaptic +                if (!allowHaptic) { +                    logger.d("Skip success haptic. Recent power button press or button is down.") +                    handleSuccessHaptic() // immediately handle, don't vibrate +                } +                allowHaptic +            } +    val playErrorHaptic: Flow<Boolean> = +        repository.errorHapticRequest +            .filter { it } +            .sample(combine(powerButtonSideFpsEnrolled, powerButtonDown, ::Pair)) +            .map { (sideFpsEnrolled, powerButtonDown) -> +                val allowHaptic = !sideFpsEnrolled || !powerButtonDown +                if (!allowHaptic) { +                    logger.d("Skip error haptic. Power button is down.") +                    handleErrorHaptic() // immediately handle, don't vibrate +                } +                allowHaptic +            } + +    fun vibrateSuccess() { +        repository.requestSuccessHaptic() +    } + +    fun vibrateError() { +        repository.requestErrorHaptic() +    } + +    fun handleSuccessHaptic() { +        repository.handleSuccessHaptic() +    } + +    fun handleErrorHaptic() { +        repository.handleErrorHaptic() +    } + +    private val recentPowerButtonPressThresholdMs = 400L +} diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt index 7510cf6ce04c..d19efbdd8026 100644 --- a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt @@ -15,14 +15,12 @@   */  package com.android.systemui.display.ui.view -import android.app.Dialog  import android.content.Context  import android.os.Bundle -import android.view.Gravity  import android.view.View -import android.view.WindowManager  import android.widget.TextView  import com.android.systemui.res.R +import com.android.systemui.statusbar.phone.SystemUIBottomSheetDialog  /**   * Dialog used to decide what to do with a connected display. @@ -35,7 +33,7 @@ class MirroringConfirmationDialog(      private val onStartMirroringClickListener: View.OnClickListener,      private val onCancelMirroring: View.OnClickListener,      theme: Int = R.style.Theme_SystemUI_Dialog, -) : Dialog(context, theme) { +) : SystemUIBottomSheetDialog(context, theme) {      private lateinit var mirrorButton: TextView      private lateinit var dismissButton: TextView @@ -43,13 +41,8 @@ class MirroringConfirmationDialog(      override fun onCreate(savedInstanceState: Bundle?) {          super.onCreate(savedInstanceState) -        window?.apply { -            setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL) -            addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS) -            setGravity(Gravity.BOTTOM) -        }          setContentView(R.layout.connected_display_dialog) -        setCanceledOnTouchOutside(true) +          mirrorButton =              requireViewById<TextView>(R.id.enable_display).apply {                  setOnClickListener(onStartMirroringClickListener) diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java index 694695017efd..1c2ff4b1b42c 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java +++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java @@ -36,6 +36,7 @@ import android.util.Log;  import androidx.annotation.NonNull;  import androidx.annotation.Nullable; +import com.android.systemui.FeatureFlags;  import com.android.systemui.dagger.SysUISingleton;  import com.android.systemui.dagger.qualifiers.Main;  import com.android.systemui.util.settings.GlobalSettings; @@ -80,6 +81,7 @@ public class FeatureFlagsClassicDebug implements FeatureFlagsClassic {      private final Map<String, Boolean> mBooleanFlagCache = new ConcurrentHashMap<>();      private final Map<String, String> mStringFlagCache = new ConcurrentHashMap<>();      private final Map<String, Integer> mIntFlagCache = new ConcurrentHashMap<>(); +    private final FeatureFlags mGantryFlags;      private final Restarter mRestarter;      private final ServerFlagReader.ChangeListener mOnPropertiesChanged = @@ -124,6 +126,7 @@ public class FeatureFlagsClassicDebug implements FeatureFlagsClassic {              @Main Resources resources,              ServerFlagReader serverFlagReader,              @Named(ALL_FLAGS) Map<String, Flag<?>> allFlags, +            FeatureFlags gantryFlags,              Restarter restarter) {          mFlagManager = flagManager;          mContext = context; @@ -132,6 +135,7 @@ public class FeatureFlagsClassicDebug implements FeatureFlagsClassic {          mSystemProperties = systemProperties;          mServerFlagReader = serverFlagReader;          mAllFlags = allFlags; +        mGantryFlags = gantryFlags;          mRestarter = restarter;      } @@ -259,9 +263,8 @@ public class FeatureFlagsClassicDebug implements FeatureFlagsClassic {          if (!hasServerOverride                  && !defaultValue                  && result == null -                && !flag.getName().equals(Flags.TEAMFOOD.getName())                  && flag.getTeamfood()) { -            return isEnabled(Flags.TEAMFOOD); +            return mGantryFlags.sysuiTeamfood();          }          return result == null ? mServerFlagReader.readServerOverride( @@ -534,7 +537,7 @@ public class FeatureFlagsClassicDebug implements FeatureFlagsClassic {      @Override      public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {          pw.println("can override: true"); - +        pw.println("teamfood: " + mGantryFlags.sysuiTeamfood());          pw.println("booleans: " + mBooleanFlagCache.size());          // Sort our flags for dumping          TreeMap<String, Boolean> dumpBooleanMap = new TreeMap<>(mBooleanFlagCache); diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 11ac39ff867b..472cc24080d5 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -36,7 +36,10 @@ import com.android.systemui.flags.FlagsFactory.unreleasedFlag   * See [FeatureFlagsClassicDebug] for instructions on flipping the flags via adb.   */  object Flags { -    @JvmField val TEAMFOOD = unreleasedFlag("teamfood") +    // IGNORE ME! +    // Because flags are static, we need an ever-present flag to reference in some of the internal +    // code that ensure that other flags are referenced and available. +    @JvmField val NULL_FLAG = unreleasedFlag("null_flag")      // 100 - notification      // TODO(b/297792660): Tracking Bug @@ -256,7 +259,7 @@ object Flags {      // TODO(b/290652751): Tracking bug.      @JvmField      val MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA = -        unreleasedFlag("migrate_split_keyguard_bottom_area", teamfood = true) +        releasedFlag("migrate_split_keyguard_bottom_area")      // TODO(b/297037052): Tracking bug.      @JvmField @@ -271,7 +274,7 @@ object Flags {      /** Migrate the lock icon view to the new keyguard root view. */      // TODO(b/286552209): Tracking bug. -    @JvmField val MIGRATE_LOCK_ICON = unreleasedFlag("migrate_lock_icon", teamfood = true) +    @JvmField val MIGRATE_LOCK_ICON = releasedFlag("migrate_lock_icon")      // TODO(b/288276738): Tracking bug.      @JvmField val WIDGET_ON_KEYGUARD = unreleasedFlag("widget_on_keyguard") @@ -401,6 +404,9 @@ object Flags {      @JvmField val SIGNAL_CALLBACK_DEPRECATION =          unreleasedFlag("signal_callback_deprecation", teamfood = true) +    // TODO(b/301610137): Tracking bug +    @JvmField val NEW_NETWORK_SLICE_UI = unreleasedFlag("new_network_slice_ui", teamfood = true) +      // TODO(b/265892345): Tracking Bug      val PLUG_IN_STATUS_BAR_CHIP = releasedFlag("plug_in_status_bar_chip") @@ -413,7 +419,7 @@ object Flags {          releasedFlag("incompatible_charging_battery_icon")      // TODO(b/293585143): Tracking Bug -    val INSTANT_TETHER = unreleasedFlag("instant_tether") +    val INSTANT_TETHER = unreleasedFlag("instant_tether", teamfood = true)      // TODO(b/294588085): Tracking Bug      val WIFI_SECONDARY_NETWORKS = releasedFlag("wifi_secondary_networks") @@ -497,10 +503,9 @@ object Flags {      @Keep      @JvmField      val WM_ENABLE_PARTIAL_SCREEN_SHARING = -        unreleasedFlag( -            name = "record_task_content", +        releasedFlag( +            name = "enable_record_task_content",              namespace = DeviceConfig.NAMESPACE_WINDOW_MANAGER, -            teamfood = true          )      // TODO(b/254512674): Tracking Bug @@ -620,7 +625,7 @@ object Flags {      /** TODO(b/295143676): Tracking bug. When enable, captures a screenshot for each display. */      @JvmField -    val MULTI_DISPLAY_SCREENSHOT = unreleasedFlag("multi_display_screenshot") +    val MULTI_DISPLAY_SCREENSHOT = unreleasedFlag("multi_display_screenshot", teamfood = true)      // 1400 - columbus      // TODO(b/254512756): Tracking Bug diff --git a/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt b/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt index 3fe68062f19a..7ccc26c063d3 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt @@ -22,11 +22,11 @@ import com.android.systemui.Dependency  /**   * This class promotes best practices for flag guarding System UI view refactors.   * * [isEnabled] allows changing an implementation. - * * [assertDisabled] allows authors to flag code as being "dead" when the flag gets enabled and + * * [assertInLegacyMode] allows authors to flag code as being "dead" when the flag gets enabled and   *   ensure that it is not being invoked accidentally in the post-flag refactor. - * * [expectEnabled] allows authors to guard new code with a "safe" alternative when invoked on - *   flag-disabled builds, but with a check that should crash eng builds or tests when the - *   expectation is violated. + * * [isUnexpectedlyInLegacyMode] allows authors to guard new code with a "safe" alternative when + *   invoked on flag-disabled builds, but with a check that should crash eng builds or tests when + *   the expectation is violated.   *   * The constructors require that you provide a [FeatureFlags] instance. If you're using this in a   * View class, it's acceptable to ue the [forView] constructor methods, which do not require one, @@ -60,13 +60,13 @@ private constructor(       * Example usage:       * ```       * public void setController(NotificationShelfController notificationShelfController) { -     *     mShelfRefactor.assertDisabled(); +     *     mShelfRefactor.assertInLegacyMode();       *     mController = notificationShelfController;       * }       * ````       */ -    fun assertDisabled() = -        check(!isEnabled) { "Code path not supported when $flagName is enabled." } +    fun assertInLegacyMode() = +        check(!isEnabled) { "Legacy code path not supported when $flagName is enabled." }      /**       * Called to ensure code is only run when the flag is enabled. This protects users from the @@ -76,18 +76,17 @@ private constructor(       * Example usage:       * ```       * public void setShelfIcons(NotificationIconContainer icons) { -     *     if (mShelfRefactor.expectEnabled()) { -     *         mShelfIcons = icons; -     *     } +     *     if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return; +     *     mShelfIcons = icons;       * }       * ```       */ -    fun expectEnabled(): Boolean { +    fun isUnexpectedlyInLegacyMode(): Boolean {          if (!isEnabled) { -            val message = "Code path not supported when $flagName is disabled." +            val message = "New code path expects $flagName to be enabled."              Log.wtf(TAG, message, Exception(message))          } -        return isEnabled +        return !isEnabled      }      companion object { diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt index 20d99d1e75fb..7b33e11a0c9c 100644 --- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt @@ -32,6 +32,8 @@ data class SliderHapticFeedbackConfig(      @FloatRange(from = 0.0, to = 1.0) val additionalVelocityMaxBump: Float = 0.15f,      /** Additional time delta to wait between drag texture vibrations */      @FloatRange(from = 0.0) val deltaMillisForDragInterval: Float = 0f, +    /** Progress threshold beyond which a new drag texture is delivered */ +    @FloatRange(from = 0.0, to = 1.0) val deltaProgressForDragThreshold: Float = 0.015f,      /** Number of low ticks in a drag texture composition. This is not expected to change */      val numberOfLowTicks: Int = 5,      /** Maximum velocity allowed for vibration scaling. This is not expected to change. */ diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt index e6de156de0c4..f313fb3eef0f 100644 --- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt @@ -46,6 +46,8 @@ class SliderHapticFeedbackProvider(      private val positionAccelerateInterpolator =          AccelerateInterpolator(config.progressInterpolatorFactor)      private var dragTextureLastTime = clock.elapsedRealtime() +    var dragTextureLastProgress = -1f +        private set      private val lowTickDurationMs =          vibratorHelper.getPrimitiveDurations(VibrationEffect.Composition.PRIMITIVE_LOW_TICK)[0]      private var hasVibratedAtLowerBookend = false @@ -91,6 +93,9 @@ class SliderHapticFeedbackProvider(          val elapsedSinceLastDrag = currentTime - dragTextureLastTime          if (elapsedSinceLastDrag < thresholdUntilNextDragCallMillis) return +        val deltaProgress = abs(normalizedSliderProgress - dragTextureLastProgress) +        if (deltaProgress < config.deltaProgressForDragThreshold) return +          val velocityInterpolated =              velocityAccelerateInterpolator.getInterpolation(                  min(absoluteVelocity / config.maxVelocityToScale, 1f) @@ -116,11 +121,14 @@ class SliderHapticFeedbackProvider(          }          vibratorHelper.vibrate(composition.compose(), VIBRATION_ATTRIBUTES_PIPELINING)          dragTextureLastTime = currentTime +        dragTextureLastProgress = normalizedSliderProgress      }      override fun onHandleAcquiredByTouch() {} -    override fun onHandleReleasedFromTouch() {} +    override fun onHandleReleasedFromTouch() { +        dragTextureLastProgress = -1f +    }      override fun onLowerBookend() {          if (!hasVibratedAtLowerBookend) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt index 86bf368791bc..119ade48d4f7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt @@ -20,6 +20,7 @@ package com.android.systemui.keyguard  import android.content.Context  import android.view.LayoutInflater  import android.view.View +import com.android.internal.jank.InteractionJankMonitor  import com.android.keyguard.KeyguardStatusView  import com.android.keyguard.KeyguardStatusViewController  import com.android.keyguard.LockIconView @@ -27,6 +28,7 @@ import com.android.keyguard.LockIconViewController  import com.android.keyguard.dagger.KeyguardStatusViewComponent  import com.android.systemui.CoreStartable  import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor  import com.android.systemui.flags.FeatureFlags  import com.android.systemui.flags.Flags  import com.android.systemui.keyguard.ui.binder.KeyguardBlueprintViewBinder @@ -43,6 +45,7 @@ import com.android.systemui.res.R  import com.android.systemui.shade.NotificationShadeWindowView  import com.android.systemui.shade.domain.interactor.ShadeInteractor  import com.android.systemui.statusbar.KeyguardIndicationController +import com.android.systemui.statusbar.VibratorHelper  import com.android.systemui.statusbar.policy.KeyguardStateController  import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator  import javax.inject.Inject @@ -71,6 +74,9 @@ constructor(      private val keyguardIndicationController: KeyguardIndicationController,      private val lockIconViewController: LockIconViewController,      private val shadeInteractor: ShadeInteractor, +    private val interactionJankMonitor: InteractionJankMonitor, +    private val deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor, +    private val vibratorHelper: VibratorHelper,  ) : CoreStartable {      private var rootViewHandle: DisposableHandle? = null @@ -140,6 +146,9 @@ constructor(                  keyguardStateController,                  shadeInteractor,                  { keyguardStatusViewController!!.getClockController() }, +                interactionJankMonitor, +                deviceEntryHapticsInteractor, +                vibratorHelper,              )      } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java index 081edd152538..019d4283a03c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java @@ -38,9 +38,6 @@ import com.android.systemui.animation.ActivityLaunchAnimator;  import com.android.systemui.broadcast.BroadcastDispatcher;  import com.android.systemui.classifier.FalsingCollector;  import com.android.systemui.classifier.FalsingModule; -import com.android.systemui.communal.data.repository.CommunalRepositoryModule; -import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule; -import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule;  import com.android.systemui.dagger.SysUISingleton;  import com.android.systemui.dagger.qualifiers.Main;  import com.android.systemui.dagger.qualifiers.UiBackground; @@ -96,9 +93,6 @@ import kotlinx.coroutines.CoroutineDispatcher;          KeyguardStatusViewComponent.class,          KeyguardUserSwitcherComponent.class},          includes = { -            CommunalRepositoryModule.class, -            CommunalTutorialRepositoryModule.class, -            CommunalWidgetRepositoryModule.class,              FalsingModule.class,              KeyguardDataQuickAffordanceModule.class,              KeyguardRepositoryModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt index e8740a4b24c5..654f2d106206 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt @@ -90,7 +90,7 @@ interface BiometricSettingsRepository {       * If the current user can use face auth to enter the device. This is true when the user has       * face auth enrolled, and is enabled in settings/device policy.       */ -    val isFaceAuthEnrolledAndEnabled: Flow<Boolean> +    val isFaceAuthEnrolledAndEnabled: StateFlow<Boolean>      /**       * If the current user can use face auth to enter the device right now. This is true when @@ -348,10 +348,11 @@ constructor(              .and(isFingerprintBiometricAllowed)              .stateIn(scope, SharingStarted.Eagerly, false) -    override val isFaceAuthEnrolledAndEnabled: Flow<Boolean> = +    override val isFaceAuthEnrolledAndEnabled: StateFlow<Boolean> =          isFaceAuthenticationEnabled              .and(isFaceEnrolled)              .and(mobileConnectionsRepository.isAnySimSecure.isFalse()) +            .stateIn(scope, SharingStarted.Eagerly, false)      override val isFaceAuthCurrentlyAllowed: Flow<Boolean> =          isFaceAuthEnrolledAndEnabled diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt index 2f8010620e5e..565962394db1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt @@ -22,6 +22,7 @@ import com.android.systemui.bouncer.data.repository.BouncerMessageRepositoryImpl  import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository  import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepositoryImpl  import com.android.systemui.bouncer.domain.interactor.BouncerMessageAuditLogger +import com.android.systemui.keyguard.ui.binder.SideFpsProgressBarViewBinder  import dagger.Binds  import dagger.Module  import dagger.multibindings.ClassKey @@ -32,6 +33,11 @@ interface KeyguardRepositoryModule {      @Binds fun keyguardRepository(impl: KeyguardRepositoryImpl): KeyguardRepository      @Binds +    @IntoMap +    @ClassKey(SideFpsProgressBarViewBinder::class) +    fun bindSideFpsProgressBarViewBinder(viewBinder: SideFpsProgressBarViewBinder): CoreStartable + +    @Binds      fun keyguardSurfaceBehindRepository(          impl: KeyguardSurfaceBehindRepositoryImpl      ): KeyguardSurfaceBehindRepository diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt index 38eb730d1498..6e0aa4c7682d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt @@ -26,10 +26,11 @@ import com.android.systemui.keyguard.shared.model.DozeStateModel  import com.android.systemui.keyguard.shared.model.KeyguardState  import com.android.systemui.util.kotlin.Utils.Companion.toTriple  import com.android.systemui.util.kotlin.sample +import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds  import kotlinx.coroutines.CoroutineScope  import kotlinx.coroutines.flow.combine  import kotlinx.coroutines.launch -import javax.inject.Inject  @SysUISingleton  class FromAodTransitionInteractor @@ -86,15 +87,19 @@ constructor(                  }          }      } -      override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {          return ValueAnimator().apply {              interpolator = Interpolators.LINEAR -            duration = TRANSITION_DURATION_MS +            duration = +                when (toState) { +                    KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION +                    else -> DEFAULT_DURATION +                }.inWholeMilliseconds          }      }      companion object { -        private const val TRANSITION_DURATION_MS = 500L +        val TO_LOCKSCREEN_DURATION = 500.milliseconds +        private val DEFAULT_DURATION = 500.milliseconds      }  } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt index ad51e74090d9..c67153a5963d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt @@ -114,7 +114,8 @@ constructor(                  .collect { (isAsleep, lastStartedStep, isAodAvailable) ->                      if (lastStartedStep.to == KeyguardState.GONE && isAsleep) {                          startTransitionTo( -                            if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING +                            if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING, +                            resetIfCancelled = true,                          )                      }                  } @@ -127,6 +128,7 @@ constructor(              duration =                  when (toState) {                      KeyguardState.DREAMING -> TO_DREAMING_DURATION +                    KeyguardState.AOD -> TO_AOD_DURATION                      else -> DEFAULT_DURATION                  }.inWholeMilliseconds          } @@ -134,5 +136,6 @@ constructor(      companion object {          private val DEFAULT_DURATION = 500.milliseconds          val TO_DREAMING_DURATION = 933.milliseconds +        val TO_AOD_DURATION = 1100.milliseconds      }  } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt index 660bd84006d7..c39a4c9c4172 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt @@ -33,6 +33,9 @@ import com.android.systemui.shade.data.repository.ShadeRepository  import com.android.systemui.util.kotlin.Utils.Companion.toQuad  import com.android.systemui.util.kotlin.Utils.Companion.toTriple  import com.android.systemui.util.kotlin.sample +import java.util.UUID +import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds  import kotlinx.coroutines.CoroutineScope  import kotlinx.coroutines.flow.Flow  import kotlinx.coroutines.flow.combine @@ -40,9 +43,6 @@ import kotlinx.coroutines.flow.distinctUntilChanged  import kotlinx.coroutines.flow.map  import kotlinx.coroutines.flow.onStart  import kotlinx.coroutines.launch -import java.util.UUID -import javax.inject.Inject -import kotlin.time.Duration.Companion.milliseconds  @SysUISingleton  class FromLockscreenTransitionInteractor @@ -355,6 +355,7 @@ constructor(                  when (toState) {                      KeyguardState.DREAMING -> TO_DREAMING_DURATION                      KeyguardState.OCCLUDED -> TO_OCCLUDED_DURATION +                    KeyguardState.AOD -> TO_AOD_DURATION                      else -> DEFAULT_DURATION                  }.inWholeMilliseconds          } @@ -364,5 +365,6 @@ constructor(          private val DEFAULT_DURATION = 400.milliseconds          val TO_DREAMING_DURATION = 933.milliseconds          val TO_OCCLUDED_DURATION = 450.milliseconds +        val TO_AOD_DURATION = 500.milliseconds      }  } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt index 89aca7631934..85b0f4fb864b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt @@ -41,6 +41,9 @@ interface KeyguardFaceAuthInteractor {      /** Whether face auth is in lock out state. */      fun isLockedOut(): Boolean +    /** Whether face auth is enrolled and enabled for the current user */ +    fun isFaceAuthEnabledAndEnrolled(): Boolean +      /**       * Register listener for use from code that cannot use [authenticationStatus] or       * [detectionStatus] 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 6e19fdbefab5..b953b4879a4a 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 @@ -39,7 +39,6 @@ import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel  import com.android.systemui.keyguard.shared.model.DozeStateModel  import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff  import com.android.systemui.keyguard.shared.model.DozeTransitionModel -import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState  import com.android.systemui.keyguard.shared.model.StatusBarState  import com.android.systemui.power.domain.interactor.PowerInteractor  import com.android.systemui.res.R @@ -49,6 +48,8 @@ import com.android.systemui.scene.shared.model.SceneKey  import com.android.systemui.shade.data.repository.ShadeRepository  import com.android.systemui.statusbar.CommandQueue  import com.android.systemui.util.kotlin.sample +import javax.inject.Inject +import javax.inject.Provider  import kotlinx.coroutines.ExperimentalCoroutinesApi  import kotlinx.coroutines.channels.awaitClose  import kotlinx.coroutines.delay @@ -64,8 +65,6 @@ import kotlinx.coroutines.flow.flowOf  import kotlinx.coroutines.flow.map  import kotlinx.coroutines.flow.merge  import kotlinx.coroutines.flow.onStart -import javax.inject.Inject -import javax.inject.Provider  /**   * Encapsulates business-logic related to the keyguard but not to a more specific part within it. @@ -148,9 +147,7 @@ constructor(              .combine(dozeTransitionModel) { isDreaming, dozeTransitionModel ->                  isDreaming && isDozeOff(dozeTransitionModel.to)              } -            .sample(powerInteractor.isAwake) { isAbleToDream, isAwake -> -                isAbleToDream && isAwake -            } +            .sample(powerInteractor.isAwake) { isAbleToDream, isAwake -> isAbleToDream && isAwake }              .flatMapLatest { isAbleToDream ->                  flow {                      delay(50) @@ -217,11 +214,8 @@ constructor(      val faceSensorLocation: Flow<Point?> = repository.faceSensorLocation      /** Notifies when a new configuration is set */ -    val configurationChange: Flow<Unit> = configurationRepository.onAnyConfigurationChange - -    /** Represents the current state of the KeyguardRootView visibility */ -    val keyguardRootViewVisibilityState: Flow<KeyguardRootViewVisibilityState> = -        repository.keyguardRootViewVisibility +    val configurationChange: Flow<Unit> = +        configurationRepository.onAnyConfigurationChange.onStart { emit(Unit) }      /** The position of the keyguard clock. */      val clockPosition: Flow<Position> = repository.clockPosition @@ -235,12 +229,17 @@ constructor(                      R.dimen.keyguard_translate_distance_on_swipe_up                  )              shadeRepository.shadeModel.map { -                // On swipe up, translate the keyguard to reveal the bouncer -                MathUtils.lerp( -                    translationDistance, -                    0, -                    Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(it.expansionAmount) -                ) +                if (it.expansionAmount == 0f) { +                    // Reset the translation value +                    0f +                } else { +                    // On swipe up, translate the keyguard to reveal the bouncer +                    MathUtils.lerp( +                        translationDistance, +                        0, +                        Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(it.expansionAmount) +                    ) +                }              }          } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt index f38bb2b519e7..fbadde63a6b9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt @@ -43,6 +43,7 @@ class NoopKeyguardFaceAuthInteractor @Inject constructor() : KeyguardFaceAuthInt      override fun isLockedOut(): Boolean = false      override fun isEnabled() = false +    override fun isFaceAuthEnabledAndEnrolled(): Boolean = false      override fun registerListener(listener: FaceAuthenticationListener) {} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt index 797dec2c9625..2641846251cc 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt @@ -32,6 +32,7 @@ import com.android.systemui.dagger.qualifiers.Application  import com.android.systemui.dagger.qualifiers.Main  import com.android.systemui.flags.FeatureFlags  import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository  import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository  import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository  import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus @@ -81,6 +82,7 @@ constructor(      private val facePropertyRepository: FacePropertyRepository,      private val faceWakeUpTriggersConfig: FaceWakeUpTriggersConfig,      private val powerInteractor: PowerInteractor, +    private val biometricSettingsRepository: BiometricSettingsRepository,  ) : CoreStartable, KeyguardFaceAuthInteractor {      private val listeners: MutableList<FaceAuthenticationListener> = mutableListOf() @@ -149,7 +151,10 @@ constructor(              .onEach {                  if (it) {                      faceAuthenticationLogger.faceLockedOut("Fingerprint locked out") -                    repository.setLockedOut(true) +                    // We don't care about this if face auth is not enabled. +                    if (isFaceAuthEnabledAndEnrolled()) { +                        repository.setLockedOut(true) +                    }                  }              }              .launchIn(applicationScope) @@ -263,6 +268,9 @@ constructor(          }      } +    override fun isFaceAuthEnabledAndEnrolled(): Boolean = +        biometricSettingsRepository.isFaceAuthEnrolledAndEnabled.value +      private fun observeFaceAuthStateUpdates() {          authenticationStatus              .onEach { authStatusUpdate -> diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt index ae18681a5b92..3c143fe1a68a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt @@ -16,6 +16,8 @@  package com.android.systemui.keyguard.shared.model +import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD +import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START  import android.hardware.fingerprint.FingerprintManager  import android.os.SystemClock.elapsedRealtime @@ -39,7 +41,12 @@ data class HelpFingerprintAuthenticationStatus(  /** Fingerprint acquired message. */  data class AcquiredFingerprintAuthenticationStatus(val acquiredInfo: Int) : -    FingerprintAuthenticationStatus() +    FingerprintAuthenticationStatus() { + +    val fingerprintCaptureStarted: Boolean = acquiredInfo == FINGERPRINT_ACQUIRED_START + +    val fingerprintCaptureCompleted: Boolean = acquiredInfo == FINGERPRINT_ACQUIRED_GOOD +}  /** Fingerprint authentication failed message. */  object FailFingerprintAuthenticationStatus : FingerprintAuthenticationStatus() diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt index ac4ad39a38a5..4d5c503d1c4e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt @@ -17,24 +17,30 @@  package com.android.systemui.keyguard.ui.binder  import android.annotation.DrawableRes +import android.view.HapticFeedbackConstants  import android.view.View  import android.view.View.OnLayoutChangeListener  import android.view.ViewGroup +import android.view.ViewGroup.OnHierarchyChangeListener  import androidx.lifecycle.Lifecycle  import androidx.lifecycle.repeatOnLifecycle -import com.android.app.animation.Interpolators +import com.android.internal.jank.InteractionJankMonitor +import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD +import com.android.keyguard.KeyguardClockSwitch.MISSING_CLOCK_ID  import com.android.systemui.common.shared.model.Icon  import com.android.systemui.common.shared.model.Text  import com.android.systemui.common.shared.model.TintedIcon +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor  import com.android.systemui.flags.FeatureFlags  import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.shared.model.TransitionState  import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel  import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel  import com.android.systemui.lifecycle.repeatWhenAttached  import com.android.systemui.plugins.ClockController  import com.android.systemui.res.R  import com.android.systemui.shade.domain.interactor.ShadeInteractor -import com.android.systemui.statusbar.StatusBarState +import com.android.systemui.statusbar.VibratorHelper  import com.android.systemui.statusbar.policy.KeyguardStateController  import com.android.systemui.temporarydisplay.ViewPriority  import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator @@ -42,14 +48,13 @@ import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo  import javax.inject.Provider  import kotlinx.coroutines.DisposableHandle  import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.filter  import kotlinx.coroutines.launch  /** Bind occludingAppDeviceEntryMessageViewModel to run whenever the keyguard view is attached. */  @ExperimentalCoroutinesApi  object KeyguardRootViewBinder { -    private var onLayoutChangeListener: OnLayoutChange? = null -      @JvmStatic      fun bind(          view: ViewGroup, @@ -60,7 +65,16 @@ object KeyguardRootViewBinder {          keyguardStateController: KeyguardStateController,          shadeInteractor: ShadeInteractor,          clockControllerProvider: Provider<ClockController>?, +        interactionJankMonitor: InteractionJankMonitor?, +        deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor?, +        vibratorHelper: VibratorHelper?,      ): DisposableHandle { +        var onLayoutChangeListener: OnLayoutChange? = null +        val childViews = mutableMapOf<Int, View?>() +        val statusViewId = R.id.keyguard_status_view +        val burnInLayerId = R.id.burn_in_layer +        val aodNotificationIconContainerId = R.id.aod_notification_icon_container +        val largeClockId = R.id.lockscreen_clock_view_large          val disposableHandle =              view.repeatWhenAttached {                  repeatOnLifecycle(Lifecycle.State.CREATED) { @@ -86,36 +100,74 @@ object KeyguardRootViewBinder {                      if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {                          launch { +                            viewModel.burnInLayerVisibility.collect { visibility -> +                                childViews[burnInLayerId]?.visibility = visibility +                                // Reset alpha only for the icons, as they currently have their +                                // own animator +                                childViews[aodNotificationIconContainerId]?.alpha = 0f +                            } +                        } + +                        launch { +                            viewModel.burnInLayerAlpha.collect { alpha -> +                                childViews[statusViewId]?.alpha = alpha +                                childViews[aodNotificationIconContainerId]?.alpha = alpha +                            } +                        } + +                        launch { +                            viewModel.lockscreenStateAlpha.collect { alpha -> +                                childViews[statusViewId]?.alpha = alpha +                            } +                        } + +                        launch {                              viewModel.translationY.collect { y -> -                                val burnInLayer = view.requireViewById<View>(R.id.burn_in_layer) -                                burnInLayer.translationY = y +                                childViews[burnInLayerId]?.translationY = y                              }                          } -                    } -                    if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {                          launch {                              viewModel.translationX.collect { x -> -                                val burnInLayer = view.requireViewById<View>(R.id.burn_in_layer) -                                burnInLayer.translationX = x +                                childViews[burnInLayerId]?.translationX = x                              }                          } -                    } -                    if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {                          launch {                              viewModel.scale.collect { (scale, scaleClockOnly) ->                                  if (scaleClockOnly) { -                                    val largeClock = -                                        view.findViewById<View?>(R.id.lockscreen_clock_view_large) -                                    largeClock?.let { +                                    childViews[largeClockId]?.let {                                          it.scaleX = scale                                          it.scaleY = scale                                      }                                  } else { -                                    val burnInLayer = view.requireViewById<View>(R.id.burn_in_layer) -                                    burnInLayer.scaleX = scale -                                    burnInLayer.scaleY = scale +                                    childViews[burnInLayerId]?.scaleX = scale +                                    childViews[burnInLayerId]?.scaleY = scale +                                } +                            } +                        } + +                        interactionJankMonitor?.let { jankMonitor -> +                            launch { +                                viewModel.goneToAodTransition.collect { +                                    when (it.transitionState) { +                                        TransitionState.STARTED -> { +                                            val clockId = +                                                clockControllerProvider?.get()?.config?.id +                                                    ?: MISSING_CLOCK_ID +                                            val builder = +                                                InteractionJankMonitor.Configuration.Builder +                                                    .withView(CUJ_SCREEN_OFF_SHOW_AOD, view) +                                                    .setTag(clockId) + +                                            jankMonitor.begin(builder) +                                        } +                                        TransitionState.CANCELED -> +                                            jankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD) +                                        TransitionState.FINISHED -> +                                            jankMonitor.end(CUJ_SCREEN_OFF_SHOW_AOD) +                                        TransitionState.RUNNING -> Unit +                                    }                                  }                              }                          } @@ -131,42 +183,42 @@ object KeyguardRootViewBinder {                                  }                          }                      } -                } -                repeatOnLifecycle(Lifecycle.State.STARTED) { -                    if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) { +                    if (deviceEntryHapticsInteractor != null && vibratorHelper != null) {                          launch { -                            viewModel.keyguardRootViewVisibilityState.collect { visibilityState -> -                                view.animate().cancel() -                                val goingToFullShade = visibilityState.goingToFullShade -                                val statusBarState = visibilityState.statusBarState -                                val isOcclusionTransitionRunning = -                                    visibilityState.occlusionTransitionRunning -                                if (goingToFullShade) { -                                    view -                                        .animate() -                                        .alpha(0f) -                                        .setStartDelay( -                                            keyguardStateController.keyguardFadingAwayDelay +                            deviceEntryHapticsInteractor.playSuccessHaptic +                                .filter { it } +                                .collect { +                                    if ( +                                        featureFlags.isEnabled(Flags.ONE_WAY_HAPTICS_API_MIGRATION) +                                    ) { +                                        vibratorHelper.performHapticFeedback( +                                            view, +                                            HapticFeedbackConstants.CONFIRM,                                          ) -                                        .setDuration( -                                            keyguardStateController.shortenedFadingAwayDuration +                                    } else { +                                        vibratorHelper.vibrateAuthSuccess("device-entry::success") +                                    } +                                    deviceEntryHapticsInteractor.handleSuccessHaptic() +                                } +                        } + +                        launch { +                            deviceEntryHapticsInteractor.playErrorHaptic +                                .filter { it } +                                .collect { +                                    if ( +                                        featureFlags.isEnabled(Flags.ONE_WAY_HAPTICS_API_MIGRATION) +                                    ) { +                                        vibratorHelper.performHapticFeedback( +                                            view, +                                            HapticFeedbackConstants.REJECT,                                          ) -                                        .setInterpolator(Interpolators.ALPHA_OUT) -                                        .withEndAction { view.visibility = View.GONE } -                                        .start() -                                } else if ( -                                    statusBarState == StatusBarState.KEYGUARD || -                                        statusBarState == StatusBarState.SHADE_LOCKED -                                ) { -                                    view.visibility = View.VISIBLE -                                    if (!isOcclusionTransitionRunning) { -                                        view.alpha = 1f +                                    } else { +                                        vibratorHelper.vibrateAuthSuccess("device-entry::error")                                      } -                                } else { -                                    view.visibility = View.GONE +                                    deviceEntryHapticsInteractor.handleErrorHaptic()                                  } -                            }                          }                      }                  } @@ -176,10 +228,26 @@ object KeyguardRootViewBinder {          onLayoutChangeListener = OnLayoutChange(viewModel)          view.addOnLayoutChangeListener(onLayoutChangeListener) +        // Views will be added or removed after the call to bind(). This is needed to avoid many +        // calls to findViewById +        view.setOnHierarchyChangeListener( +            object : OnHierarchyChangeListener { +                override fun onChildViewAdded(parent: View, child: View) { +                    childViews.put(child.id, child) +                } + +                override fun onChildViewRemoved(parent: View, child: View) { +                    childViews.remove(child.id) +                } +            } +        ) +          return object : DisposableHandle {              override fun dispose() {                  disposableHandle.dispose()                  view.removeOnLayoutChangeListener(onLayoutChangeListener) +                view.setOnHierarchyChangeListener(null) +                childViews.clear()              }          }      } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/PreviewKeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/PreviewKeyguardBlueprintViewBinder.kt index f3586bae1a9e..2feaa2e81c0f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/PreviewKeyguardBlueprintViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/PreviewKeyguardBlueprintViewBinder.kt @@ -24,6 +24,7 @@ import androidx.lifecycle.Lifecycle  import androidx.lifecycle.repeatOnLifecycle  import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel  import com.android.systemui.lifecycle.repeatWhenAttached +import kotlinx.coroutines.DisposableHandle  import kotlinx.coroutines.launch  /** @@ -46,8 +47,8 @@ class PreviewKeyguardBlueprintViewBinder {              constraintLayout: ConstraintLayout,              viewModel: KeyguardBlueprintViewModel,              finishedAddViewCallback: () -> Unit -        ) { -            constraintLayout.repeatWhenAttached { +        ): DisposableHandle { +            return constraintLayout.repeatWhenAttached {                  repeatOnLifecycle(Lifecycle.State.CREATED) {                      launch {                          viewModel.blueprint.collect { blueprint -> diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt new file mode 100644 index 000000000000..1acea5cfc579 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.binder + +import com.android.systemui.CoreStartable +import com.android.systemui.biometrics.SideFpsController +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.ui.view.SideFpsProgressBar +import com.android.systemui.keyguard.ui.viewmodel.SideFpsProgressBarViewModel +import com.android.systemui.util.kotlin.Quint +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.launch + +@SysUISingleton +class SideFpsProgressBarViewBinder +@Inject +constructor( +    private val viewModel: SideFpsProgressBarViewModel, +    private val view: SideFpsProgressBar, +    @Application private val applicationScope: CoroutineScope, +    private val sfpsController: dagger.Lazy<SideFpsController>, +) : CoreStartable { + +    override fun start() { +        applicationScope.launch { +            viewModel.isProlongedTouchRequiredForAuthentication.collectLatest { enabled -> +                if (enabled) { +                    launch { +                        combine( +                                viewModel.isVisible, +                                viewModel.sensorLocation, +                                viewModel.shouldRotate90Degrees, +                                viewModel.isFingerprintAuthRunning, +                                viewModel.sensorWidth, +                                ::Quint +                            ) +                            .collectLatest { +                                (visible, location, shouldRotate, fpDetectRunning, sensorWidth) -> +                                view.updateView(visible, location, shouldRotate, sensorWidth) +                                // We have to hide the SFPS indicator as the progress bar will +                                // be shown at the same location +                                if (visible) { +                                    sfpsController.get().hideIndicator() +                                } else if (fpDetectRunning) { +                                    sfpsController.get().showIndicator() +                                } +                            } +                    } +                    launch { viewModel.progress.collectLatest { view.setProgress(it) } } +                } else { +                    view.hideOverlay() +                } +            } +        } +    } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt index 4a2954dc6559..692984a90a14 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt @@ -27,6 +27,7 @@ import android.hardware.display.DisplayManager  import android.os.Bundle  import android.os.Handler  import android.os.IBinder +import android.view.ContextThemeWrapper  import android.view.Display  import android.view.Display.DEFAULT_DISPLAY  import android.view.DisplayInfo @@ -45,6 +46,7 @@ import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor  import com.android.systemui.broadcast.BroadcastDispatcher  import com.android.systemui.dagger.qualifiers.Application  import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor  import com.android.systemui.flags.FeatureFlags  import com.android.systemui.flags.Flags  import com.android.systemui.keyguard.ui.binder.KeyguardPreviewClockViewBinder @@ -113,6 +115,7 @@ constructor(      private val chipbarCoordinator: ChipbarCoordinator,      private val keyguardStateController: KeyguardStateController,      private val shadeInteractor: ShadeInteractor, +    private val deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor,  ) {      val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN) @@ -179,7 +182,11 @@ constructor(      fun render() {          mainHandler.post { -            val previewContext = display?.let { context.createDisplayContext(it) } ?: context +            val previewContext = +                display?.let { +                    ContextThemeWrapper(context.createDisplayContext(it), context.getTheme()) +                } +                    ?: context              val rootView = FrameLayout(previewContext) @@ -322,7 +329,7 @@ constructor(      @OptIn(ExperimentalCoroutinesApi::class)      private fun setupKeyguardRootView(previewContext: Context, rootView: FrameLayout) { -        val keyguardRootView = KeyguardRootView(previewContext, null).apply { removeAllViews() } +        val keyguardRootView = KeyguardRootView(previewContext, null)          disposables.add(              KeyguardRootViewBinder.bind(                  keyguardRootView, @@ -333,6 +340,9 @@ constructor(                  keyguardStateController,                  shadeInteractor,                  null, // clock provider only needed for burn in +                null, // jank monitor not required for preview mode +                null, // device entry haptics not required for preview mode +                null, // device entry haptics not required for preview mode              )          )          rootView.addView( @@ -342,26 +352,29 @@ constructor(                  FrameLayout.LayoutParams.MATCH_PARENT,              ),          ) -        PreviewKeyguardBlueprintViewBinder.bind(keyguardRootView, keyguardBlueprintViewModel) { -            if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) { -                setupShortcuts(keyguardRootView) -            } -            setUpUdfps(previewContext, rootView) - -            if (!shouldHideClock) { -                setUpClock(previewContext, rootView) -                KeyguardPreviewClockViewBinder.bind( -                    largeClockHostView, -                    smallClockHostView, -                    clockViewModel, -                ) -            } -            setUpSmartspace(previewContext, rootView) -            smartSpaceView?.let { -                KeyguardPreviewSmartspaceViewBinder.bind(it, smartspaceViewModel) +        disposables.add( +            PreviewKeyguardBlueprintViewBinder.bind(keyguardRootView, keyguardBlueprintViewModel) { +                if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) { +                    setupShortcuts(keyguardRootView) +                } +                setUpUdfps(previewContext, rootView) + +                if (!shouldHideClock) { +                    setUpClock(previewContext, rootView) +                    KeyguardPreviewClockViewBinder.bind( +                        largeClockHostView, +                        smallClockHostView, +                        clockViewModel, +                    ) +                } + +                setUpSmartspace(previewContext, rootView) +                smartSpaceView?.let { +                    KeyguardPreviewSmartspaceViewBinder.bind(it, smartspaceViewModel) +                }              } -        } +        )      }      private fun setupShortcuts(keyguardRootView: ConstraintLayout) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt new file mode 100644 index 000000000000..f7ab1ee77582 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.ui.view + +import android.graphics.PixelFormat +import android.graphics.Point +import android.view.Gravity +import android.view.LayoutInflater +import android.view.View +import android.view.WindowManager +import android.widget.ProgressBar +import com.android.systemui.biometrics.Utils +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.res.R +import javax.inject.Inject + +private const val TAG = "SideFpsProgressBar" + +const val progressBarHeight = 100 + +@SysUISingleton +class SideFpsProgressBar +@Inject +constructor( +    private val layoutInflater: LayoutInflater, +    private val windowManager: WindowManager, +) { +    private var progressBarWidth = 200 +    fun updateView( +        visible: Boolean, +        location: Point, +        shouldRotate90Degrees: Boolean, +        progressBarWidth: Int +    ) { +        if (visible) { +            this.progressBarWidth = progressBarWidth +            createAndShowOverlay(location, shouldRotate90Degrees) +        } else { +            hideOverlay() +        } +    } + +    fun hideOverlay() { +        overlayView = null +    } + +    private val overlayViewParams = +        WindowManager.LayoutParams( +                progressBarHeight, +                progressBarWidth, +                WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, +                Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS, +                PixelFormat.TRANSPARENT +            ) +            .apply { +                title = TAG +                fitInsetsTypes = 0 // overrides default, avoiding status bars during layout +                gravity = Gravity.TOP or Gravity.LEFT +                layoutInDisplayCutoutMode = +                    WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS +                privateFlags = +                    WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY or +                        WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION +            } + +    private var overlayView: View? = null +        set(value) { +            field?.let { oldView -> windowManager.removeView(oldView) } +            field = value +            field?.let { newView -> windowManager.addView(newView, overlayViewParams) } +        } + +    private fun createAndShowOverlay( +        fingerprintSensorLocation: Point, +        shouldRotate90Degrees: Boolean +    ) { +        if (overlayView == null) { +            overlayView = layoutInflater.inflate(R.layout.sidefps_progress_bar, null, false) +        } +        overlayViewParams.x = fingerprintSensorLocation.x +        overlayViewParams.y = fingerprintSensorLocation.y +        if (shouldRotate90Degrees) { +            overlayView?.rotation = 270.0f +            overlayViewParams.width = progressBarHeight +            overlayViewParams.height = progressBarWidth +        } else { +            overlayView?.rotation = 0.0f +            overlayViewParams.width = progressBarWidth +            overlayViewParams.height = progressBarHeight +        } +        windowManager.updateViewLayout(overlayView, overlayViewParams) +    } + +    fun setProgress(value: Float) { +        overlayView +            ?.findViewById<ProgressBar?>(R.id.side_fps_progress_bar) +            ?.setProgress((value * 100).toInt(), false) +    } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt index 9371d4e2d465..342a440d972b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt @@ -50,11 +50,8 @@ constructor(      override fun addViews(constraintLayout: ConstraintLayout) {          if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) { -            val view = -                LayoutInflater.from(constraintLayout.context) -                    .inflate(R.layout.ambient_indication, constraintLayout, false) - -            constraintLayout.addView(view) +            LayoutInflater.from(constraintLayout.context) +                .inflate(R.layout.ambient_indication, constraintLayout, true)          }      } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt new file mode 100644 index 000000000000..024707ad2885 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.flow.Flow + +/** + * Breaks down AOD->LOCKSCREEN transition into discrete steps for corresponding views to consume. + */ +@SysUISingleton +class AodToLockscreenTransitionViewModel +@Inject +constructor( +    private val interactor: KeyguardTransitionInteractor, +) { + +    private val transitionAnimation = +        KeyguardTransitionAnimationFlow( +            transitionDuration = TO_LOCKSCREEN_DURATION, +            transitionFlow = interactor.aodToLockscreenTransition, +        ) + +    /** Ensure alpha is set to be visible */ +    val lockscreenAlpha: Flow<Float> = +        transitionAnimation.createFlow( +            duration = 500.milliseconds, +            onStart = { 1f }, +            onStep = { 1f }, +        ) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt new file mode 100644 index 000000000000..601dbccb1de1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_AOD_DURATION +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.flow.Flow + +/** Breaks down GONE->AOD transition into discrete steps for corresponding views to consume. */ +@SysUISingleton +class GoneToAodTransitionViewModel +@Inject +constructor( +    private val interactor: KeyguardTransitionInteractor, +) { + +    private val transitionAnimation = +        KeyguardTransitionAnimationFlow( +            transitionDuration = TO_AOD_DURATION, +            transitionFlow = interactor.goneToAodTransition, +        ) + +    /** y-translation from the top of the screen for AOD */ +    fun enterFromTopTranslationY(translatePx: Int): Flow<Float> { +        return transitionAnimation.createFlow( +            startTime = 600.milliseconds, +            duration = 500.milliseconds, +            onStart = { translatePx }, +            onStep = { translatePx + it * -translatePx }, +            onFinish = { 0f }, +            onCancel = { 0f }, +            interpolator = EMPHASIZED_DECELERATE, +        ) +    } + +    /** alpha animation upon entering AOD */ +    val enterFromTopAnimationAlpha: Flow<Float> = +        transitionAnimation.createFlow( +            startTime = 600.milliseconds, +            duration = 500.milliseconds, +            onStart = { 0f }, +            onStep = { it }, +        ) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt index 89835fecd95c..1f98082c4065 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt @@ -17,14 +17,19 @@  package com.android.systemui.keyguard.ui.viewmodel +import android.content.Context  import android.util.MathUtils +import android.view.View.VISIBLE  import com.android.app.animation.Interpolators  import com.android.systemui.common.shared.model.SharedNotificationContainerPosition  import com.android.systemui.keyguard.domain.interactor.BurnInInteractor  import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor  import com.android.systemui.keyguard.shared.model.BurnInModel -import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState +import com.android.systemui.keyguard.shared.model.KeyguardState.AOD +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN  import com.android.systemui.plugins.ClockController +import com.android.systemui.res.R  import javax.inject.Inject  import javax.inject.Provider  import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -32,17 +37,23 @@ import kotlinx.coroutines.flow.Flow  import kotlinx.coroutines.flow.MutableStateFlow  import kotlinx.coroutines.flow.combine  import kotlinx.coroutines.flow.distinctUntilChanged +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  @OptIn(ExperimentalCoroutinesApi::class)  class KeyguardRootViewModel  @Inject  constructor( +    private val context: Context,      private val keyguardInteractor: KeyguardInteractor,      private val burnInInteractor: BurnInInteractor, +    private val goneToAodTransitionViewModel: GoneToAodTransitionViewModel, +    private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel, +    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,  ) {      data class PreviewMode(val isInPreviewMode: Boolean = false) @@ -56,9 +67,12 @@ constructor(      public var clockControllerProvider: Provider<ClockController>? = null -    /** Represents the current state of the KeyguardRootView visibility */ -    val keyguardRootViewVisibilityState: Flow<KeyguardRootViewVisibilityState> = -        keyguardInteractor.keyguardRootViewVisibilityState +    val burnInLayerVisibility: Flow<Int> = +        keyguardTransitionInteractor.startedKeyguardState +            .filter { it == AOD || it == LOCKSCREEN } +            .map { VISIBLE } + +    val goneToAodTransition = keyguardTransitionInteractor.goneToAodTransition      /** An observable for the alpha level for the entire keyguard root view. */      val alpha: Flow<Float> = @@ -70,9 +84,14 @@ constructor(              }          } -    private val burnIn: Flow<BurnInModel> = -        combine(keyguardInteractor.dozeAmount, burnInInteractor.keyguardBurnIn) { dozeAmount, burnIn -            -> +    private fun burnIn(): Flow<BurnInModel> { +        val dozingAmount: Flow<Float> = +            merge( +                keyguardTransitionInteractor.goneToAodTransition.map { it.value }, +                keyguardTransitionInteractor.dozeAmountTransition.map { it.value }, +            ) + +        return combine(dozingAmount, burnInInteractor.keyguardBurnIn) { dozeAmount, burnIn ->              val interpolation = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(dozeAmount)              val useScaleOnly =                  clockControllerProvider?.get()?.config?.useAlternateSmartspaceAODTransition ?: false @@ -91,13 +110,61 @@ constructor(                  )              }          } +    } + +    /** Specific alpha value for elements visible during [KeyguardState.LOCKSCREEN] */ +    val lockscreenStateAlpha: Flow<Float> = aodToLockscreenTransitionViewModel.lockscreenAlpha + +    /** For elements that appear and move during the animation -> AOD */ +    val burnInLayerAlpha: Flow<Float> = +        previewMode.flatMapLatest { +            if (it.isInPreviewMode) { +                flowOf(1f) +            } else { +                goneToAodTransitionViewModel.enterFromTopAnimationAlpha +            } +        }      val translationY: Flow<Float> = -        merge(keyguardInteractor.keyguardTranslationY, burnIn.map { it.translationY.toFloat() }) +        previewMode.flatMapLatest { +            if (it.isInPreviewMode) { +                flowOf(0f) +            } else { +                keyguardInteractor.configurationChange.flatMapLatest { _ -> +                    val enterFromTopAmount = +                        context.resources.getDimensionPixelSize( +                            R.dimen.keyguard_enter_from_top_translation_y +                        ) +                    combine( +                        keyguardInteractor.keyguardTranslationY.onStart { emit(0f) }, +                        burnIn().map { it.translationY.toFloat() }.onStart { emit(0f) }, +                        goneToAodTransitionViewModel +                            .enterFromTopTranslationY(enterFromTopAmount) +                            .onStart { emit(0f) }, +                    ) { keyguardTransitionY, burnInTranslationY, goneToAodTransitionTranslationY -> +                        // All 3 values need to be combined for a smooth translation +                        keyguardTransitionY + burnInTranslationY + goneToAodTransitionTranslationY +                    } +                } +            } +        } -    val translationX: Flow<Float> = burnIn.map { it.translationX.toFloat() } +    val translationX: Flow<Float> = +        previewMode.flatMapLatest { +            if (it.isInPreviewMode) { +                flowOf(0f) +            } else { +                burnIn().map { it.translationX.toFloat() } +            } +        } -    val scale: Flow<Pair<Float, Boolean>> = burnIn.map { Pair(it.scale, it.scaleClockOnly) } +    val scale: Flow<Pair<Float, Boolean>> = +        previewMode.flatMapLatest { previewMode -> +            burnIn().map { +                val scale = if (previewMode.isInPreviewMode) 1f else it.scale +                Pair(scale, it.scaleClockOnly) +            } +        }      /**       * Puts this view-model in "preview mode", which means it's being used for UI that is rendering @@ -105,11 +172,14 @@ constructor(       * lock screen.       */      fun enablePreviewMode() { -        val newPreviewMode = PreviewMode(true) -        previewMode.value = newPreviewMode +        previewMode.value = PreviewMode(true)      }      fun onSharedNotificationContainerPositionChanged(top: Float, bottom: Float) { +        // Notifications should not be visible in preview mode +        if (previewMode.value.isInPreviewMode) { +            return +        }          keyguardInteractor.sharedNotificationContainerPosition.value =              SharedNotificationContainerPosition(top, bottom)      } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt new file mode 100644 index 000000000000..2c3b431715a9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.android.systemui.keyguard.ui.viewmodel + +import android.animation.ValueAnimator +import android.graphics.Point +import androidx.core.animation.doOnEnd +import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor +import com.android.systemui.biometrics.domain.interactor.SideFpsSensorInteractor +import com.android.systemui.biometrics.shared.model.isDefaultOrientation +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus +import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus +import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus +import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch + +@SysUISingleton +class SideFpsProgressBarViewModel +@Inject +constructor( +    private val fpAuthRepository: DeviceEntryFingerprintAuthRepository, +    private val sfpsSensorInteractor: SideFpsSensorInteractor, +    displayStateInteractor: DisplayStateInteractor, +    @Application private val applicationScope: CoroutineScope, +) { +    private val _progress = MutableStateFlow(0.0f) +    private val _visible = MutableStateFlow(false) +    private var _animator: ValueAnimator? = null + +    private fun onFingerprintCaptureCompleted() { +        _visible.value = false +        _progress.value = 0.0f +    } + +    val isVisible: Flow<Boolean> = _visible.asStateFlow() + +    val progress: Flow<Float> = _progress.asStateFlow() + +    val sensorWidth: Flow<Int> = sfpsSensorInteractor.sensorLocation.map { it.width } + +    val sensorLocation: Flow<Point> = +        sfpsSensorInteractor.sensorLocation.map { Point(it.left, it.top) } + +    val isFingerprintAuthRunning: Flow<Boolean> = fpAuthRepository.isRunning + +    val shouldRotate90Degrees: Flow<Boolean> = +        combine(displayStateInteractor.currentRotation, sfpsSensorInteractor.sensorLocation, ::Pair) +            .map { (rotation, sensorLocation) -> +                if (rotation.isDefaultOrientation()) { +                    sensorLocation.isSensorVerticalInDefaultOrientation +                } else { +                    !sensorLocation.isSensorVerticalInDefaultOrientation +                } +            } + +    val isProlongedTouchRequiredForAuthentication: Flow<Boolean> = +        sfpsSensorInteractor.isProlongedTouchRequiredForAuthentication + +    init { +        applicationScope.launch { +            combine( +                    sfpsSensorInteractor.isProlongedTouchRequiredForAuthentication, +                    sfpsSensorInteractor.authenticationDuration, +                    ::Pair +                ) +                .collectLatest { (enabled, authDuration) -> +                    if (!enabled) return@collectLatest + +                    launch { +                        fpAuthRepository.authenticationStatus.collectLatest { authStatus -> +                            when (authStatus) { +                                is AcquiredFingerprintAuthenticationStatus -> { +                                    if (authStatus.fingerprintCaptureStarted) { + +                                        _visible.value = true +                                        _animator?.cancel() +                                        _animator = +                                            ValueAnimator.ofFloat(0.0f, 1.0f) +                                                .setDuration(authDuration) +                                                .apply { +                                                    addUpdateListener { +                                                        _progress.value = it.animatedValue as Float +                                                    } +                                                    addListener( +                                                        doOnEnd { +                                                            if (_progress.value == 0.0f) { +                                                                _visible.value = false +                                                            } +                                                        } +                                                    ) +                                                } +                                        _animator?.start() +                                    } else if (authStatus.fingerprintCaptureCompleted) { +                                        onFingerprintCaptureCompleted() +                                    } else { +                                        // Abandoned FP Auth attempt +                                        _animator?.reverse() +                                    } +                                } +                                is ErrorFingerprintAuthenticationStatus -> +                                    onFingerprintCaptureCompleted() +                                is FailFingerprintAuthenticationStatus -> +                                    onFingerprintCaptureCompleted() +                                is SuccessFingerprintAuthenticationStatus -> +                                    onFingerprintCaptureCompleted() +                                else -> Unit +                            } +                        } +                    } +                } +        } +    } +} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt new file mode 100644 index 000000000000..a53f0f11c380 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.mediaprojection + +import android.media.projection.IMediaProjectionManager +import android.os.Process +import android.os.RemoteException +import android.util.Log +import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_APP as METRICS_CREATION_SOURCE_APP +import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_CAST as METRICS_CREATION_SOURCE_CAST +import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER as METRICS_CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER +import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN as METRICS_CREATION_SOURCE_UNKNOWN +import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject + +/** + * Helper class for requesting that the server emit logs describing the MediaProjection setup + * experience. + */ +@SysUISingleton +class MediaProjectionMetricsLogger +@Inject +constructor(private val service: IMediaProjectionManager) { +    /** +     * Request to log that the permission was requested. +     * +     * @param sessionCreationSource The entry point requesting permission to capture. +     */ +    fun notifyProjectionInitiated(sessionCreationSource: SessionCreationSource) { +        notifyToServer( +            MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED, +            sessionCreationSource +        ) +    } + +    /** +     * Request to log that the permission request moved to the given state. +     * +     * Should not be used for the initialization state, since that should use {@link +     * MediaProjectionMetricsLogger#notifyProjectionInitiated(Int)} and pass the +     * sessionCreationSource. +     */ +    fun notifyPermissionProgress(state: Int) { +        // TODO validate state is valid +        notifyToServer(state, SessionCreationSource.UNKNOWN) +    } + +    /** +     * Notifies system server that we are handling a particular state during the consent flow. +     * +     * Only used for emitting atoms. +     * +     * @param state The state that SystemUI is handling during the consent flow. Must be a valid +     *   state defined in the MediaProjectionState enum. +     * @param sessionCreationSource Only set if the state is MEDIA_PROJECTION_STATE_INITIATED. +     *   Indicates the entry point for requesting the permission. Must be a valid state defined in +     *   the SessionCreationSource enum. +     */ +    private fun notifyToServer(state: Int, sessionCreationSource: SessionCreationSource) { +        Log.v(TAG, "FOO notifyToServer of state $state and source $sessionCreationSource") +        try { +            service.notifyPermissionRequestStateChange( +                Process.myUid(), +                state, +                sessionCreationSource.toMetricsConstant() +            ) +        } catch (e: RemoteException) { +            Log.e( +                TAG, +                "Error notifying server of permission flow state $state from source " + +                    "$sessionCreationSource", +                e +            ) +        } +    } + +    companion object { +        const val TAG = "MediaProjectionMetricsLogger" +    } +} + +enum class SessionCreationSource { +    APP, +    CAST, +    SYSTEM_UI_SCREEN_RECORDER, +    UNKNOWN; + +    fun toMetricsConstant(): Int = +        when (this) { +            APP -> METRICS_CREATION_SOURCE_APP +            CAST -> METRICS_CREATION_SOURCE_CAST +            SYSTEM_UI_SCREEN_RECORDER -> METRICS_CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER +            UNKNOWN -> METRICS_CREATION_SOURCE_UNKNOWN +        } +} diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt index b5d3e913cadb..0bbcfd9de24c 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt @@ -87,14 +87,15 @@ class MediaProjectionAppSelectorActivity(      override fun getLayoutResource() = R.layout.media_projection_app_selector -    public override fun onCreate(bundle: Bundle?) { +    public override fun onCreate(savedInstanceState: Bundle?) {          lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)          component =              componentFactory.create(                  hostUserHandle = hostUserHandle,                  callingPackage = callingPackage,                  view = this, -                resultHandler = this +                resultHandler = this, +                isFirstStart = savedInstanceState == null              )          component.lifecycleObservers.forEach { lifecycle.addObserver(it) } @@ -113,7 +114,7 @@ class MediaProjectionAppSelectorActivity(          reviewGrantedConsentRequired =              intent.getBooleanExtra(EXTRA_USER_REVIEW_GRANTED_CONSENT, false) -        super.onCreate(bundle) +        super.onCreate(savedInstanceState)          controller.init()          // we override AppList's AccessibilityDelegate set in ResolverActivity.onCreate because in          // our case this delegate must extend RecyclerViewAccessibilityDelegate, otherwise diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt index 2217509167ef..8c6f307c84d6 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt @@ -146,6 +146,9 @@ interface MediaProjectionAppSelectorComponent {              @BindsInstance @MediaProjectionAppSelector callingPackage: String?,              @BindsInstance view: MediaProjectionAppSelectorView,              @BindsInstance resultHandler: MediaProjectionAppSelectorResultHandler, +            // Whether the app selector is starting for the first time. False when it is re-starting +            // due to a config change. +            @BindsInstance @MediaProjectionAppSelector isFirstStart: Boolean,          ): MediaProjectionAppSelectorComponent      } diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt index fced117a8132..69132d3662d4 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt @@ -18,6 +18,8 @@ package com.android.systemui.mediaprojection.appselector  import android.content.ComponentName  import android.os.UserHandle +import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_APP_SELECTOR_DISPLAYED as STATE_APP_SELECTOR_DISPLAYED +import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger  import com.android.systemui.mediaprojection.appselector.data.RecentTask  import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider  import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader @@ -43,9 +45,17 @@ constructor(      @MediaProjectionAppSelector private val appSelectorComponentName: ComponentName,      @MediaProjectionAppSelector private val callerPackageName: String?,      private val thumbnailLoader: RecentTaskThumbnailLoader, +    @MediaProjectionAppSelector private val isFirstStart: Boolean, +    private val logger: MediaProjectionMetricsLogger,  ) {      fun init() { +        // Only log during the first start of the app selector. +        // Don't log when the app selector restarts due to a config change. +        if (isFirstStart) { +            logger.notifyPermissionProgress(STATE_APP_SELECTOR_DISPLAYED) +        } +          scope.launch {              val recentTasks = recentTaskListProvider.loadRecentTasks() diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt index a9e6c53b3bcd..e9b458271ef7 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt @@ -22,6 +22,7 @@ import android.content.ComponentName  data class RecentTask(      val taskId: Int, +    val displayId: Int,      @UserIdInt val userId: Int,      val topActivityComponent: ComponentName?,      val baseIntentComponent: ComponentName?, diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt index aa4c4e55c718..730aa620690a 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt @@ -60,6 +60,7 @@ constructor(                  .map {                      RecentTask(                          it.taskId, +                        it.displayId,                          it.userId,                          it.topActivity,                          it.baseIntent?.component, diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt index fd1a683dc78f..ba837dba5354 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt @@ -130,10 +130,10 @@ constructor(                  view.width,                  view.height              ) -        activityOptions.setPendingIntentBackgroundActivityStartMode( +        activityOptions.pendingIntentBackgroundActivityStartMode =              MODE_BACKGROUND_ACTIVITY_START_ALLOWED -        )          activityOptions.launchCookie = launchCookie +        activityOptions.launchDisplayId = task.displayId          activityTaskManager.startActivityFromRecents(task.taskId, activityOptions.toBundle())          resultHandler.returnSelectedApp(launchCookie) 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 d08d0400f354..fa418fc8b98b 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java @@ -53,7 +53,9 @@ import android.view.Window;  import com.android.systemui.flags.FeatureFlags;  import com.android.systemui.flags.Flags; +import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger;  import com.android.systemui.mediaprojection.MediaProjectionServiceHelper; +import com.android.systemui.mediaprojection.SessionCreationSource;  import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity;  import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;  import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog; @@ -74,6 +76,7 @@ public class MediaProjectionPermissionActivity extends Activity      private final FeatureFlags mFeatureFlags;      private final Lazy<ScreenCaptureDevicePolicyResolver> mScreenCaptureDevicePolicyResolver;      private final StatusBarManager mStatusBarManager; +    private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;      private String mPackageName;      private int mUid; @@ -90,15 +93,17 @@ public class MediaProjectionPermissionActivity extends Activity      @Inject      public MediaProjectionPermissionActivity(FeatureFlags featureFlags,              Lazy<ScreenCaptureDevicePolicyResolver> screenCaptureDevicePolicyResolver, -            StatusBarManager statusBarManager) { +            StatusBarManager statusBarManager, +            MediaProjectionMetricsLogger mediaProjectionMetricsLogger) {          mFeatureFlags = featureFlags;          mScreenCaptureDevicePolicyResolver = screenCaptureDevicePolicyResolver;          mStatusBarManager = statusBarManager; +        mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger;      }      @Override -    public void onCreate(Bundle icicle) { -        super.onCreate(icicle); +    public void onCreate(Bundle savedInstanceState) { +        super.onCreate(savedInstanceState);          final Intent launchingIntent = getIntent();          mReviewGrantedConsentRequired = launchingIntent.getBooleanExtra( @@ -133,6 +138,10 @@ public class MediaProjectionPermissionActivity extends Activity          try {              if (MediaProjectionServiceHelper.hasProjectionPermission(mUid, mPackageName)) { +                if (savedInstanceState == null) { +                    mMediaProjectionMetricsLogger.notifyProjectionInitiated( +                            SessionCreationSource.APP); +                }                  final IMediaProjection projection =                          MediaProjectionServiceHelper.createOrReuseProjection(mUid, mPackageName,                                  mReviewGrantedConsentRequired); @@ -231,6 +240,13 @@ public class MediaProjectionPermissionActivity extends Activity              mDialog = dialogBuilder.create();          } +        if (savedInstanceState == null) { +            mMediaProjectionMetricsLogger.notifyProjectionInitiated( +                    appName == null +                            ? SessionCreationSource.CAST +                            : SessionCreationSource.APP); +        } +          setUpDialog(mDialog);          mDialog.show();      } diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt index 2f10ad3e6486..b9bafd4926fa 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialog.kt @@ -89,15 +89,15 @@ class MediaProjectionPermissionDialog(                  }              return listOf(                  ScreenShareOption( -                    mode = ENTIRE_SCREEN, -                    spinnerText = R.string.screen_share_permission_dialog_option_entire_screen, -                    warningText = entireScreenWarningText -                ), -                ScreenShareOption(                      mode = SINGLE_APP,                      spinnerText = R.string.screen_share_permission_dialog_option_single_app,                      warningText = singleAppWarningText,                      spinnerDisabledText = singleAppDisabledText, +                ), +                ScreenShareOption( +                    mode = ENTIRE_SCREEN, +                    spinnerText = R.string.screen_share_permission_dialog_option_entire_screen, +                    warningText = entireScreenWarningText                  )              )          } diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ScreenShareOption.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ScreenShareOption.kt index 37e8d9f26ee3..9bd57832c4df 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ScreenShareOption.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/ScreenShareOption.kt @@ -23,8 +23,8 @@ import kotlin.annotation.Retention  @IntDef(ENTIRE_SCREEN, SINGLE_APP)  annotation class ScreenShareMode -const val ENTIRE_SCREEN = 0 -const val SINGLE_APP = 1 +const val SINGLE_APP = 0 +const val ENTIRE_SCREEN = 1  class ScreenShareOption(      @ScreenShareMode val mode: Int, diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt index c4749e093854..c77f3f49a77c 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt @@ -231,7 +231,6 @@ internal constructor(              animation.removeEndListener(this)              if (!canceled) { -                  // The delay between finishing this animation and starting the runnable                  val delay = max(0, runnableDelay - elapsedTimeSinceEntry) @@ -461,7 +460,6 @@ internal constructor(      }      private fun handleMoveEvent(event: MotionEvent) { -          val x = event.x          val y = event.y @@ -927,17 +925,7 @@ internal constructor(              GestureState.ACTIVE -> {                  previousXTranslationOnActiveOffset = previousXTranslation                  updateRestingArrowDimens() -                if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { -                    vibratorHelper.performHapticFeedback( -                        mView, -                        HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE -                    ) -                } else { -                    vibratorHelper.cancel() -                    mainHandler.postDelayed(10L) { -                        vibratorHelper.vibrate(VIBRATE_ACTIVATED_EFFECT) -                    } -                } +                performActivatedHapticFeedback()                  val popVelocity =                      if (previousState == GestureState.INACTIVE) {                          POP_ON_INACTIVE_TO_ACTIVE_VELOCITY @@ -958,25 +946,24 @@ internal constructor(                  mView.popOffEdge(POP_ON_INACTIVE_VELOCITY) -                if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { -                    vibratorHelper.performHapticFeedback( -                        mView, -                        HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE -                    ) -                } else { -                    vibratorHelper.vibrate(VIBRATE_DEACTIVATED_EFFECT) -                } +                performDeactivatedHapticFeedback()                  updateRestingArrowDimens()              }              GestureState.FLUNG -> { +                // Typically a vibration is only played while transitioning to ACTIVE. However there +                // are instances where a fling to trigger back occurs while not in that state. +                // (e.g. A fling is detected before crossing the trigger threshold.) +                if (previousState != GestureState.ACTIVE) { +                    performActivatedHapticFeedback() +                }                  mainHandler.postDelayed(POP_ON_FLING_DELAY) {                      mView.popScale(POP_ON_FLING_VELOCITY)                  } -                updateRestingArrowDimens()                  mainHandler.postDelayed(                      onEndSetCommittedStateListener.runnable,                      MIN_DURATION_FLING_ANIMATION                  ) +                updateRestingArrowDimens()              }              GestureState.COMMITTED -> {                  // In most cases, animating between states is handled via `updateRestingArrowDimens` @@ -1011,6 +998,31 @@ internal constructor(          }      } +    private fun performDeactivatedHapticFeedback() { +        if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { +            vibratorHelper.performHapticFeedback( +                    mView, +                    HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE +            ) +        } else { +            vibratorHelper.vibrate(VIBRATE_DEACTIVATED_EFFECT) +        } +    } + +    private fun performActivatedHapticFeedback() { +        if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { +            vibratorHelper.performHapticFeedback( +                    mView, +                    HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE +            ) +        } else { +            vibratorHelper.cancel() +            mainHandler.postDelayed(10L) { +                vibratorHelper.vibrate(VIBRATE_ACTIVATED_EFFECT) +            } +        } +    } +      private fun convertVelocityToAnimationFactor(          valueOnFastVelocity: Float,          valueOnSlowVelocity: Float, diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java index 733383e344b8..58c4f0d029c7 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java @@ -96,6 +96,44 @@ import java.util.regex.Matcher;  import java.util.regex.Pattern;  import java.util.stream.Collectors; +/** Variables and functions that is related to Emoji. */ +class EmojiHelper { +    static final CharSequence EMOJI_CAKE = "\ud83c\udf82"; + +    // This regex can be used to match Unicode emoji characters and character sequences. It's from +    // the official Unicode site (https://unicode.org/reports/tr51/#EBNF_and_Regex) with minor +    // changes to fit our needs. It should be updated once new emoji categories are added. +    // +    // Emoji categories that can be matched by this regex: +    // - Country flags. "\p{RI}\p{RI}" matches country flags since they always consist of 2 Unicode +    //   scalars. +    // - Single-Character Emoji. "\p{Emoji}" matches Single-Character Emojis. +    // - Emoji with modifiers. E.g. Emojis with different skin tones. "\p{Emoji}\p{EMod}" matches +    //   them. +    // - Emoji Presentation. Those are characters which can normally be drawn as either text or as +    //   Emoji. "\p{Emoji}\x{FE0F}" matches them. +    // - Emoji Keycap. E.g. Emojis for number 0 to 9. "\p{Emoji}\x{FE0F}\x{20E3}" matches them. +    // - Emoji tag sequence. "\p{Emoji}[\x{E0020}-\x{E007E}]+\x{E007F}" matches them. +    // - Emoji Zero-Width Joiner (ZWJ) Sequence. A ZWJ emoji is actually multiple emojis joined by +    //   the jointer "0x200D". +    // +    // Note: since "\p{Emoji}" also matches some ASCII characters like digits 0-9, we use +    // "\p{Emoji}&&\p{So}" to exclude them. This is the change we made from the official emoji +    // regex. +    private static final String UNICODE_EMOJI_REGEX = +            "\\p{RI}\\p{RI}|" +                    + "(" +                    + "\\p{Emoji}(\\p{EMod}|\\x{FE0F}\\x{20E3}?|[\\x{E0020}-\\x{E007E}]+\\x{E007F})" +                    + "|[\\p{Emoji}&&\\p{So}]" +                    + ")" +                    + "(" +                    + "\\x{200D}" +                    + "\\p{Emoji}(\\p{EMod}|\\x{FE0F}\\x{20E3}?|[\\x{E0020}-\\x{E007E}]+\\x{E007F})" +                    + "?)*"; + +    static final Pattern EMOJI_PATTERN = Pattern.compile(UNICODE_EMOJI_REGEX); +} +  /** Functions that help creating the People tile layouts. */  public class PeopleTileViewHelper {      /** Turns on debugging information about People Space. */ @@ -125,8 +163,6 @@ public class PeopleTileViewHelper {      private static final int MESSAGES_COUNT_OVERFLOW = 6; -    private static final CharSequence EMOJI_CAKE = "\ud83c\udf82"; -      private static final Pattern DOUBLE_EXCLAMATION_PATTERN = Pattern.compile("[!][!]+");      private static final Pattern DOUBLE_QUESTION_PATTERN = Pattern.compile("[?][?]+");      private static final Pattern ANY_DOUBLE_MARK_PATTERN = Pattern.compile("[!?][!?]+"); @@ -134,39 +170,6 @@ public class PeopleTileViewHelper {      static final String BRIEF_PAUSE_ON_TALKBACK = "\n\n"; -    // This regex can be used to match Unicode emoji characters and character sequences. It's from -    // the official Unicode site (https://unicode.org/reports/tr51/#EBNF_and_Regex) with minor -    // changes to fit our needs. It should be updated once new emoji categories are added. -    // -    // Emoji categories that can be matched by this regex: -    // - Country flags. "\p{RI}\p{RI}" matches country flags since they always consist of 2 Unicode -    //   scalars. -    // - Single-Character Emoji. "\p{Emoji}" matches Single-Character Emojis. -    // - Emoji with modifiers. E.g. Emojis with different skin tones. "\p{Emoji}\p{EMod}" matches -    //   them. -    // - Emoji Presentation. Those are characters which can normally be drawn as either text or as -    //   Emoji. "\p{Emoji}\x{FE0F}" matches them. -    // - Emoji Keycap. E.g. Emojis for number 0 to 9. "\p{Emoji}\x{FE0F}\x{20E3}" matches them. -    // - Emoji tag sequence. "\p{Emoji}[\x{E0020}-\x{E007E}]+\x{E007F}" matches them. -    // - Emoji Zero-Width Joiner (ZWJ) Sequence. A ZWJ emoji is actually multiple emojis joined by -    //   the jointer "0x200D". -    // -    // Note: since "\p{Emoji}" also matches some ASCII characters like digits 0-9, we use -    // "\p{Emoji}&&\p{So}" to exclude them. This is the change we made from the official emoji -    // regex. -    private static final String UNICODE_EMOJI_REGEX = -            "\\p{RI}\\p{RI}|" -                    + "(" -                    + "\\p{Emoji}(\\p{EMod}|\\x{FE0F}\\x{20E3}?|[\\x{E0020}-\\x{E007E}]+\\x{E007F})" -                    + "|[\\p{Emoji}&&\\p{So}]" -                    + ")" -                    + "(" -                    + "\\x{200D}" -                    + "\\p{Emoji}(\\p{EMod}|\\x{FE0F}\\x{20E3}?|[\\x{E0020}-\\x{E007E}]+\\x{E007F})" -                    + "?)*"; - -    private static final Pattern EMOJI_PATTERN = Pattern.compile(UNICODE_EMOJI_REGEX); -      public static final String EMPTY_STRING = "";      private int mMediumVerticalPadding; @@ -831,7 +834,7 @@ public class PeopleTileViewHelper {          if (status.getActivity() == ACTIVITY_BIRTHDAY                  || status.getActivity() == ACTIVITY_UPCOMING_BIRTHDAY) { -            setEmojiBackground(views, EMOJI_CAKE); +            setEmojiBackground(views, EmojiHelper.EMOJI_CAKE);          }          Icon statusIcon = status.getIcon(); @@ -1072,7 +1075,7 @@ public class PeopleTileViewHelper {      /** Returns emoji if {@code message} has two of the same emoji in sequence. */      @VisibleForTesting      CharSequence getDoubleEmoji(CharSequence message) { -        Matcher unicodeEmojiMatcher = EMOJI_PATTERN.matcher(message); +        Matcher unicodeEmojiMatcher = EmojiHelper.EMOJI_PATTERN.matcher(message);          // Stores the start and end indices of each matched emoji.          List<Pair<Integer, Integer>> emojiIndices = new ArrayList<>();          // Stores each emoji text. 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 5c6e9028545a..a69820868838 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java @@ -38,7 +38,6 @@ import com.android.internal.app.MediaRouteDialogPresenter;  import com.android.internal.jank.InteractionJankMonitor;  import com.android.internal.logging.MetricsLogger;  import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.systemui.res.R;  import com.android.systemui.animation.ActivityLaunchAnimator;  import com.android.systemui.animation.DialogCuj;  import com.android.systemui.animation.DialogLaunchAnimator; @@ -53,12 +52,13 @@ import com.android.systemui.qs.QSHost;  import com.android.systemui.qs.QsEventLogger;  import com.android.systemui.qs.logging.QSLogger;  import com.android.systemui.qs.tileimpl.QSTileImpl; +import com.android.systemui.res.R;  import com.android.systemui.statusbar.connectivity.NetworkController;  import com.android.systemui.statusbar.connectivity.SignalCallback;  import com.android.systemui.statusbar.connectivity.WifiIndicators;  import com.android.systemui.statusbar.phone.SystemUIDialog; -import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor; -import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel; +import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel; +import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository;  import com.android.systemui.statusbar.policy.CastController;  import com.android.systemui.statusbar.policy.CastController.CastDevice;  import com.android.systemui.statusbar.policy.HotspotController; @@ -85,10 +85,9 @@ public class CastTile extends QSTileImpl<BooleanState> {      private final NetworkController mNetworkController;      private final DialogLaunchAnimator mDialogLaunchAnimator;      private final Callback mCallback = new Callback(); -    private final WifiInteractor mWifiInteractor;      private final TileJavaAdapter mJavaAdapter;      private final FeatureFlags mFeatureFlags; -    private boolean mWifiConnected; +    private boolean mCastTransportAllowed;      private boolean mHotspotConnected;      @Inject @@ -107,7 +106,7 @@ public class CastTile extends QSTileImpl<BooleanState> {              NetworkController networkController,              HotspotController hotspotController,              DialogLaunchAnimator dialogLaunchAnimator, -            WifiInteractor wifiInteractor, +            ConnectivityRepository connectivityRepository,              TileJavaAdapter javaAdapter,              FeatureFlags featureFlags      ) { @@ -117,7 +116,6 @@ public class CastTile extends QSTileImpl<BooleanState> {          mKeyguard = keyguardStateController;          mNetworkController = networkController;          mDialogLaunchAnimator = dialogLaunchAnimator; -        mWifiInteractor = wifiInteractor;          mJavaAdapter = javaAdapter;          mFeatureFlags = featureFlags;          mController.observe(this, mCallback); @@ -125,7 +123,11 @@ public class CastTile extends QSTileImpl<BooleanState> {          if (!mFeatureFlags.isEnabled(SIGNAL_CALLBACK_DEPRECATION)) {              mNetworkController.observe(this, mSignalCallback);          } else { -            mJavaAdapter.bind(this, mWifiInteractor.getWifiNetwork(), mNetworkModelConsumer); +            mJavaAdapter.bind( +                    this, +                    connectivityRepository.getDefaultConnections(), +                    mNetworkModelConsumer +            );          }          hotspotController.observe(this, mHotspotCallback);      } @@ -282,7 +284,7 @@ public class CastTile extends QSTileImpl<BooleanState> {          }          state.icon = ResourceIcon.get(state.value ? R.drawable.ic_cast_connected                  : R.drawable.ic_cast); -        if (canCastToWifi() || state.value) { +        if (canCastToNetwork() || state.value) {              state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;              if (!state.value) {                  state.secondaryLabel = ""; @@ -291,7 +293,7 @@ public class CastTile extends QSTileImpl<BooleanState> {              state.forceExpandIcon = willPopDialog();          } else {              state.state = Tile.STATE_UNAVAILABLE; -            String noWifi = mContext.getString(R.string.quick_settings_cast_no_wifi); +            String noWifi = mContext.getString(R.string.quick_settings_cast_no_network);              state.secondaryLabel = noWifi;              state.forceExpandIcon = false;          } @@ -308,13 +310,13 @@ public class CastTile extends QSTileImpl<BooleanState> {                  : mContext.getString(R.string.quick_settings_cast_device_default_name);      } -    private boolean canCastToWifi() { -        return mWifiConnected || mHotspotConnected; +    private boolean canCastToNetwork() { +        return mCastTransportAllowed || mHotspotConnected;      } -    private void setWifiConnected(boolean connected) { -        if (connected != mWifiConnected) { -            mWifiConnected = connected; +    private void setCastTransportAllowed(boolean connected) { +        if (connected != mCastTransportAllowed) { +            mCastTransportAllowed = connected;              // Hotspot is not connected, so changes here should update              if (!mHotspotConnected) {                  refreshState(); @@ -326,14 +328,17 @@ public class CastTile extends QSTileImpl<BooleanState> {          if (connected != mHotspotConnected) {              mHotspotConnected = connected;              // Wifi is not connected, so changes here should update -            if (!mWifiConnected) { +            if (!mCastTransportAllowed) {                  refreshState();              }          }      } -    private final Consumer<WifiNetworkModel> mNetworkModelConsumer = (model) -> { -        setWifiConnected(model instanceof WifiNetworkModel.Active); +    private final Consumer<DefaultConnectionModel> mNetworkModelConsumer = (model) -> { +        boolean isWifiDefault = model.getWifi().isDefault(); +        boolean isEthernetDefault = model.getEthernet().isDefault(); +        boolean hasCellularTransport = model.getMobile().isDefault(); +        setCastTransportAllowed((isWifiDefault || isEthernetDefault) && !hasCellularTransport);      };      private final SignalCallback mSignalCallback = new SignalCallback() { @@ -342,7 +347,7 @@ public class CastTile extends QSTileImpl<BooleanState> {                      // statusIcon.visible has the connected status information                      boolean enabledAndConnected = indicators.enabled                              && (indicators.qsIcon != null && indicators.qsIcon.visible); -                    setWifiConnected(enabledAndConnected); +                    setCastTransportAllowed(enabledAndConnected);                  }              }; diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java index 7a0c087caacf..f469c6b78e87 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java @@ -38,6 +38,8 @@ import com.android.systemui.dagger.SysUISingleton;  import com.android.systemui.dagger.qualifiers.Main;  import com.android.systemui.flags.FeatureFlags;  import com.android.systemui.flags.Flags; +import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger; +import com.android.systemui.mediaprojection.SessionCreationSource;  import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;  import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog;  import com.android.systemui.plugins.ActivityStarter; @@ -45,13 +47,13 @@ import com.android.systemui.settings.UserContextProvider;  import com.android.systemui.settings.UserTracker;  import com.android.systemui.statusbar.policy.CallbackController; +import dagger.Lazy; +  import java.util.concurrent.CopyOnWriteArrayList;  import java.util.concurrent.Executor;  import javax.inject.Inject; -import dagger.Lazy; -  /**   * Helper class to initiate a screen recording   */ @@ -71,6 +73,7 @@ public class RecordingController      private final FeatureFlags mFlags;      private final UserContextProvider mUserContextProvider;      private final UserTracker mUserTracker; +    private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;      protected static final String INTENT_UPDATE_STATE =              "com.android.systemui.screenrecord.UPDATE_STATE"; @@ -115,7 +118,8 @@ public class RecordingController              FeatureFlags flags,              UserContextProvider userContextProvider,              Lazy<ScreenCaptureDevicePolicyResolver> devicePolicyResolver, -            UserTracker userTracker) { +            UserTracker userTracker, +            MediaProjectionMetricsLogger mediaProjectionMetricsLogger) {          mMainExecutor = mainExecutor;          mContext = context;          mFlags = flags; @@ -123,6 +127,7 @@ public class RecordingController          mBroadcastDispatcher = broadcastDispatcher;          mUserContextProvider = userContextProvider;          mUserTracker = userTracker; +        mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger;          BroadcastOptions options = BroadcastOptions.makeBasic();          options.setInteractive(true); @@ -149,6 +154,9 @@ public class RecordingController              return new ScreenCaptureDisabledDialog(mContext);          } +        mMediaProjectionMetricsLogger.notifyProjectionInitiated( +                SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER); +          return flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)                  ? new ScreenRecordPermissionDialog(context,  getHostUserHandle(), this,                      activityStarter, mUserContextProvider, onStartRecordingClicked) diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt index a1d5d98ba9e9..ade93b1a913e 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt @@ -186,14 +186,14 @@ class ScreenRecordPermissionDialog(          private fun createOptionList(): List<ScreenShareOption> {              return listOf(                  ScreenShareOption( -                    ENTIRE_SCREEN, -                    R.string.screen_share_permission_dialog_option_entire_screen, -                    R.string.screenrecord_permission_dialog_warning_entire_screen -                ), -                ScreenShareOption(                      SINGLE_APP,                      R.string.screen_share_permission_dialog_option_single_app,                      R.string.screenrecord_permission_dialog_warning_single_app +                ), +                ScreenShareOption( +                    ENTIRE_SCREEN, +                    R.string.screen_share_permission_dialog_option_entire_screen, +                    R.string.screenrecord_permission_dialog_warning_entire_screen                  )              )          } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt index 51540673b9e2..c34fd42e2154 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt @@ -20,11 +20,10 @@ import android.util.Log  import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE  import com.android.systemui.dagger.SysUISingleton  import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.flags.FeatureFlags -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch  import java.util.function.Consumer  import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch  /** Processes a screenshot request sent from [ScreenshotHelper]. */  interface ScreenshotRequestProcessor { @@ -36,16 +35,15 @@ interface ScreenshotRequestProcessor {      suspend fun process(screenshot: ScreenshotData): ScreenshotData  } -/** - * Implementation of [ScreenshotRequestProcessor] - */ +/** Implementation of [ScreenshotRequestProcessor] */  @SysUISingleton -class RequestProcessor @Inject constructor( -        private val capture: ImageCapture, -        private val policy: ScreenshotPolicy, -        private val flags: FeatureFlags, -        /** For the Java Async version, to invoke the callback. */ -        @Application private val mainScope: CoroutineScope +class RequestProcessor +@Inject +constructor( +    private val capture: ImageCapture, +    private val policy: ScreenshotPolicy, +    /** For the Java Async version, to invoke the callback. */ +    @Application private val mainScope: CoroutineScope  ) : ScreenshotRequestProcessor {      override suspend fun process(screenshot: ScreenshotData): ScreenshotData { @@ -67,8 +65,9 @@ class RequestProcessor @Inject constructor(              result.userHandle = info.user              if (policy.isManagedProfile(info.user.identifier)) { -                val image = capture.captureTask(info.taskId) -                    ?: error("Task snapshot returned a null Bitmap!") +                val image = +                    capture.captureTask(info.taskId) +                        ?: throw RequestProcessorException("Task snapshot returned a null Bitmap!")                  // Provide the task snapshot as the screenshot                  result.type = TAKE_SCREENSHOT_PROVIDED_IMAGE @@ -97,3 +96,6 @@ class RequestProcessor @Inject constructor(  }  private const val TAG = "RequestProcessor" + +/** Exception thrown by [RequestProcessor] if something goes wrong. */ +class RequestProcessorException(message: String) : IllegalStateException(message) diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 127a57e26a30..21a08a9a4980 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -325,7 +325,7 @@ public class ScreenshotController {              Context context,              FeatureFlags flags,              ScreenshotSmartActions screenshotSmartActions, -            ScreenshotNotificationsController screenshotNotificationsController, +            ScreenshotNotificationsController.Factory screenshotNotificationsControllerFactory,              ScrollCaptureClient scrollCaptureClient,              UiEventLogger uiEventLogger,              ImageExporter imageExporter, @@ -346,7 +346,7 @@ public class ScreenshotController {              @Assisted boolean showUIOnExternalDisplay      ) {          mScreenshotSmartActions = screenshotSmartActions; -        mNotificationsController = screenshotNotificationsController; +        mNotificationsController = screenshotNotificationsControllerFactory.create(displayId);          mScrollCaptureClient = scrollCaptureClient;          mUiEventLogger = uiEventLogger;          mImageExporter = imageExporter; diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java deleted file mode 100644 index 4344fd1a7567..000000000000 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2019 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 static android.content.Context.NOTIFICATION_SERVICE; - -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.app.admin.DevicePolicyManager; -import android.content.Context; -import android.content.Intent; -import android.content.res.Resources; -import android.os.UserHandle; -import android.util.DisplayMetrics; -import android.view.WindowManager; - -import com.android.internal.messages.nano.SystemMessageProto; -import com.android.systemui.res.R; -import com.android.systemui.SystemUIApplication; -import com.android.systemui.util.NotificationChannels; - -import javax.inject.Inject; - -/** - * Convenience class to handle showing and hiding notifications while taking a screenshot. - */ -public class ScreenshotNotificationsController { -    private static final String TAG = "ScreenshotNotificationManager"; - -    private final Context mContext; -    private final Resources mResources; -    private final NotificationManager mNotificationManager; - -    @Inject -    ScreenshotNotificationsController(Context context, WindowManager windowManager) { -        mContext = context; -        mResources = context.getResources(); -        mNotificationManager = -                (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE); - -        DisplayMetrics displayMetrics = new DisplayMetrics(); -        windowManager.getDefaultDisplay().getRealMetrics(displayMetrics); -    } - -    /** -     * Sends a notification that the screenshot capture has failed. -     */ -    public void notifyScreenshotError(int msgResId) { -        Resources res = mContext.getResources(); -        String errorMsg = res.getString(msgResId); - -        // Repurpose the existing notification to notify the user of the error -        Notification.Builder b = new Notification.Builder(mContext, NotificationChannels.ALERTS) -                .setTicker(res.getString(R.string.screenshot_failed_title)) -                .setContentTitle(res.getString(R.string.screenshot_failed_title)) -                .setContentText(errorMsg) -                .setSmallIcon(R.drawable.stat_notify_image_error) -                .setWhen(System.currentTimeMillis()) -                .setVisibility(Notification.VISIBILITY_PUBLIC) // ok to show outside lockscreen -                .setCategory(Notification.CATEGORY_ERROR) -                .setAutoCancel(true) -                .setColor(mContext.getColor( -                        com.android.internal.R.color.system_notification_accent_color)); -        final DevicePolicyManager dpm = -                (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE); -        final Intent intent = -                dpm.createAdminSupportIntent(DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE); -        if (intent != null) { -            final PendingIntent pendingIntent = PendingIntent.getActivityAsUser( -                    mContext, 0, intent, PendingIntent.FLAG_IMMUTABLE, null, UserHandle.CURRENT); -            b.setContentIntent(pendingIntent); -        } - -        SystemUIApplication.overrideNotificationAppName(mContext, b, true); - -        Notification n = new Notification.BigTextStyle(b) -                .bigText(errorMsg) -                .build(); -        mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT, n); -    } -} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.kt new file mode 100644 index 000000000000..d874eb68460b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.kt @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.screenshot + +import android.app.Notification +import android.app.NotificationManager +import android.app.PendingIntent +import android.app.admin.DevicePolicyManager +import android.content.Context +import android.os.UserHandle +import android.view.Display +import com.android.internal.R +import com.android.internal.messages.nano.SystemMessageProto +import com.android.systemui.SystemUIApplication +import com.android.systemui.util.NotificationChannels +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +/** Convenience class to handle showing and hiding notifications while taking a screenshot. */ +class ScreenshotNotificationsController +@AssistedInject +internal constructor( +    @Assisted private val displayId: Int, +    private val context: Context, +    private val notificationManager: NotificationManager, +    private val devicePolicyManager: DevicePolicyManager, +) { +    private val res = context.resources + +    /** +     * Sends a notification that the screenshot capture has failed. +     * +     * Errors for the non-default display are shown in a unique separate notification. +     */ +    fun notifyScreenshotError(msgResId: Int) { +        val displayErrorString = +            if (displayId != Display.DEFAULT_DISPLAY) { +                " ($externalDisplayString)" +            } else { +                "" +            } +        val errorMsg = res.getString(msgResId) + displayErrorString + +        // Repurpose the existing notification or create a new one +        val builder = +            Notification.Builder(context, NotificationChannels.ALERTS) +                .setTicker(res.getString(com.android.systemui.res.R.string.screenshot_failed_title)) +                .setContentTitle( +                    res.getString(com.android.systemui.res.R.string.screenshot_failed_title) +                ) +                .setContentText(errorMsg) +                .setSmallIcon(com.android.systemui.res.R.drawable.stat_notify_image_error) +                .setWhen(System.currentTimeMillis()) +                .setVisibility(Notification.VISIBILITY_PUBLIC) // ok to show outside lockscreen +                .setCategory(Notification.CATEGORY_ERROR) +                .setAutoCancel(true) +                .setColor(context.getColor(R.color.system_notification_accent_color)) +        val intent = +            devicePolicyManager.createAdminSupportIntent( +                DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE +            ) +        if (intent != null) { +            val pendingIntent = +                PendingIntent.getActivityAsUser( +                    context, +                    0, +                    intent, +                    PendingIntent.FLAG_IMMUTABLE, +                    null, +                    UserHandle.CURRENT +                ) +            builder.setContentIntent(pendingIntent) +        } +        SystemUIApplication.overrideNotificationAppName(context, builder, true) +        val notification = Notification.BigTextStyle(builder).bigText(errorMsg).build() +        // A different id for external displays to keep the 2 error notifications separated. +        val id = +            if (displayId == Display.DEFAULT_DISPLAY) { +                SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT +            } else { +                SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT_EXTERNAL_DISPLAY +            } +        notificationManager.notify(id, notification) +    } + +    private val externalDisplayString: String +        get() = +            res.getString( +                com.android.systemui.res.R.string.screenshot_failed_external_display_indication +            ) + +    /** Factory for [ScreenshotNotificationsController]. */ +    @AssistedFactory +    interface Factory { +        fun create(displayId: Int = Display.DEFAULT_DISPLAY): ScreenshotNotificationsController +    } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotServiceErrorReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotServiceErrorReceiver.java index 070fb1eef99a..049799e96c53 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotServiceErrorReceiver.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotServiceErrorReceiver.java @@ -16,25 +16,29 @@  package com.android.systemui.screenshot; +import android.app.NotificationManager; +import android.app.admin.DevicePolicyManager;  import android.content.BroadcastReceiver;  import android.content.Context;  import android.content.Intent; -import android.view.WindowManager; +import android.view.Display;  import com.android.systemui.res.R;  /** - * Performs a number of miscellaneous, non-system-critical actions - * after the system has finished booting. + * Receives errors related to screenshot.   */  public class ScreenshotServiceErrorReceiver extends BroadcastReceiver {      @Override      public void onReceive(final Context context, Intent intent) {          // Show a message that we've failed to save the image to disk -        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); -        ScreenshotNotificationsController controller = -                new ScreenshotNotificationsController(context, wm); +        NotificationManager notificationManager = context.getSystemService( +                NotificationManager.class); +        DevicePolicyManager devicePolicyManager = context.getSystemService( +                DevicePolicyManager.class); +        ScreenshotNotificationsController controller = new ScreenshotNotificationsController( +                Display.DEFAULT_DISPLAY, context, notificationManager, devicePolicyManager);          controller.notifyScreenshotError(R.string.screenshot_failed_to_save_unknown_text);      }  } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt index abe40ff11ead..015828438375 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt @@ -10,6 +10,8 @@ import com.android.internal.util.ScreenshotRequest  import com.android.systemui.dagger.SysUISingleton  import com.android.systemui.dagger.qualifiers.Application  import com.android.systemui.display.data.repository.DisplayRepository +import com.android.systemui.res.R +import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED  import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback  import java.util.function.Consumer  import javax.inject.Inject @@ -34,7 +36,8 @@ constructor(      displayRepository: DisplayRepository,      @Application private val mainScope: CoroutineScope,      private val screenshotRequestProcessor: ScreenshotRequestProcessor, -    private val uiEventLogger: UiEventLogger +    private val uiEventLogger: UiEventLogger, +    private val screenshotNotificationControllerFactory: ScreenshotNotificationsController.Factory,  ) {      private lateinit var displays: StateFlow<Set<Display>> @@ -44,6 +47,7 @@ constructor(          }      private val screenshotControllers = mutableMapOf<Int, ScreenshotController>() +    private val notificationControllers = mutableMapOf<Int, ScreenshotNotificationsController>()      /**       * Executes the [ScreenshotRequest]. @@ -58,40 +62,68 @@ constructor(      ) {          val displayIds = getDisplaysToScreenshot(screenshotRequest.type)          val resultCallbackWrapper = MultiResultCallbackWrapper(requestCallback) -        screenshotRequest.oneForEachDisplay(displayIds).forEach { screenshotData: ScreenshotData -> +        displayIds.forEach { displayId: Int ->              dispatchToController( -                screenshotData = screenshotData, +                rawScreenshotData = ScreenshotData.fromRequest(screenshotRequest, displayId),                  onSaved = -                    if (screenshotData.displayId == Display.DEFAULT_DISPLAY) onSaved else { _ -> }, -                callback = resultCallbackWrapper.createCallbackForId(screenshotData.displayId) +                    if (displayId == Display.DEFAULT_DISPLAY) { +                        onSaved +                    } else { _ -> }, +                callback = resultCallbackWrapper.createCallbackForId(displayId)              )          }      } -    /** Creates a [ScreenshotData] for each display. */ -    private suspend fun ScreenshotRequest.oneForEachDisplay( -        displayIds: List<Int> -    ): List<ScreenshotData> { -        return displayIds -            .map { displayId -> ScreenshotData.fromRequest(this, displayId) } -            .map { screenshotData: ScreenshotData -> -                screenshotRequestProcessor.process(screenshotData) -            } -    } - -    private fun dispatchToController( -        screenshotData: ScreenshotData, +    /** All logging should be triggered only by this method. */ +    private suspend fun dispatchToController( +        rawScreenshotData: ScreenshotData,          onSaved: (Uri) -> Unit,          callback: RequestCallback      ) { +        // Let's wait before logging "screenshot requested", as we should log the processed +        // ScreenshotData. +        val screenshotData = +            try { +                screenshotRequestProcessor.process(rawScreenshotData) +            } catch (e: RequestProcessorException) { +                Log.e(TAG, "Failed to process screenshot request!", e) +                logScreenshotRequested(rawScreenshotData) +                onFailedScreenshotRequest(rawScreenshotData, callback) +                return +            } + +        logScreenshotRequested(screenshotData) +        Log.d(TAG, "Screenshot request: $screenshotData") +        try { +            getScreenshotController(screenshotData.displayId) +                .handleScreenshot(screenshotData, onSaved, callback) +        } catch (e: IllegalStateException) { +            Log.e(TAG, "Error while ScreenshotController was handling ScreenshotData!", e) +            onFailedScreenshotRequest(screenshotData, callback) +            return // After a failure log, nothing else should run. +        } +    } + +    /** +     * This should be logged also in case of failed requests, before the [SCREENSHOT_CAPTURE_FAILED] +     * event. +     */ +    private fun logScreenshotRequested(screenshotData: ScreenshotData) {          uiEventLogger.log(              ScreenshotEvent.getScreenshotSource(screenshotData.source),              0,              screenshotData.packageNameString          ) -        Log.d(TAG, "Screenshot request: $screenshotData") -        getScreenshotController(screenshotData.displayId) -            .handleScreenshot(screenshotData, onSaved, callback) +    } + +    private fun onFailedScreenshotRequest( +        screenshotData: ScreenshotData, +        callback: RequestCallback +    ) { +        uiEventLogger.log(SCREENSHOT_CAPTURE_FAILED, 0, screenshotData.packageNameString) +        getNotificationController(screenshotData.displayId) +            .notifyScreenshotError(R.string.screenshot_failed_to_capture_text) +        callback.reportError()      }      private fun getDisplaysToScreenshot(requestType: Int): List<Int> { @@ -140,6 +172,12 @@ constructor(          }      } +    private fun getNotificationController(id: Int): ScreenshotNotificationsController { +        return notificationControllers.computeIfAbsent(id) { +            screenshotNotificationControllerFactory.create(id) +        } +    } +      /** For java compatibility only. see [executeScreenshots] */      fun executeScreenshotsAsync(          screenshotRequest: ScreenshotRequest, diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java index 75d52cbe2e36..0991c9a326c8 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java @@ -113,7 +113,8 @@ public class TakeScreenshotService extends Service {      @Inject      public TakeScreenshotService(ScreenshotController.Factory screenshotControllerFactory,              UserManager userManager, DevicePolicyManager devicePolicyManager, -            UiEventLogger uiEventLogger, ScreenshotNotificationsController notificationsController, +            UiEventLogger uiEventLogger, +            ScreenshotNotificationsController.Factory notificationsControllerFactory,              Context context, @Background Executor bgExecutor, FeatureFlags featureFlags,              RequestProcessor processor, Provider<TakeScreenshotExecutor> takeScreenshotExecutor) {          if (DEBUG_SERVICE) { @@ -123,7 +124,7 @@ public class TakeScreenshotService extends Service {          mUserManager = userManager;          mDevicePolicyManager = devicePolicyManager;          mUiEventLogger = uiEventLogger; -        mNotificationsController = notificationsController; +        mNotificationsController = notificationsControllerFactory.create(Display.DEFAULT_DISPLAY);          mContext = context;          mBgExecutor = bgExecutor;          mFeatureFlags = featureFlags; @@ -246,15 +247,17 @@ public class TakeScreenshotService extends Service {          Log.d(TAG, "Processing screenshot data"); -        ScreenshotData screenshotData = ScreenshotData.fromRequest( -                request, Display.DEFAULT_DISPLAY); +        if (mFeatureFlags.isEnabled(MULTI_DISPLAY_SCREENSHOT)) { +            mTakeScreenshotExecutor.get().executeScreenshotsAsync(request, onSaved, callback); +            return; +        } +        // TODO(b/295143676): Delete the following after the flag is released.          try { -            if (mFeatureFlags.isEnabled(MULTI_DISPLAY_SCREENSHOT)) { -                mTakeScreenshotExecutor.get().executeScreenshotsAsync(request, onSaved, callback); -            } else { -                mProcessor.processAsync(screenshotData, (data) -> -                        dispatchToController(data, onSaved, callback)); -            } +            ScreenshotData screenshotData = ScreenshotData.fromRequest( +                    request, Display.DEFAULT_DISPLAY); +            mProcessor.processAsync(screenshotData, (data) -> +                    dispatchToController(data, onSaved, callback)); +          } catch (IllegalStateException e) {              Log.e(TAG, "Failed to process screenshot request!", e);              logFailedRequest(request); @@ -264,6 +267,7 @@ public class TakeScreenshotService extends Service {          }      } +    // TODO(b/295143676): Delete this.      private void dispatchToController(ScreenshotData screenshot,              Consumer<Uri> uriConsumer, RequestCallback callback) {          mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(screenshot.getSource()), 0, diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java index 6783afa3eb9d..1ecb127f0c92 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java @@ -21,6 +21,7 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;  import static android.view.WindowManagerPolicyConstants.EXTRA_FROM_BRIGHTNESS_KEY;  import android.app.Activity; +import android.content.res.Configuration;  import android.graphics.Rect;  import android.os.Bundle;  import android.view.Gravity; @@ -35,8 +36,8 @@ import android.widget.FrameLayout;  import com.android.internal.annotations.VisibleForTesting;  import com.android.internal.logging.MetricsLogger;  import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.systemui.res.R;  import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.res.R;  import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;  import com.android.systemui.util.concurrency.DelayableExecutor; @@ -74,21 +75,26 @@ public class BrightnessDialog extends Activity {      @Override      protected void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState); +        setWindowAttributes(); +        setContentView(R.layout.brightness_mirror_container); +        setBrightnessDialogViewAttributes(); +    } +    private void setWindowAttributes() {          final Window window = getWindow(); -        window.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL); +        window.setGravity(Gravity.TOP | Gravity.LEFT);          window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);          window.requestFeature(Window.FEATURE_NO_TITLE);          // Calling this creates the decor View, so setLayout takes proper effect          // (see Dialog#onWindowAttributesChanged)          window.getDecorView(); -        window.setLayout( -                WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT); +        window.setLayout(WRAP_CONTENT, WRAP_CONTENT);          getTheme().applyStyle(R.style.Theme_SystemUI_QuickSettings, false); +    } -        setContentView(R.layout.brightness_mirror_container); +    void setBrightnessDialogViewAttributes() {          FrameLayout frame = findViewById(R.id.brightness_mirror_container);          // The brightness mirror container is INVISIBLE by default.          frame.setVisibility(View.VISIBLE); @@ -97,6 +103,14 @@ public class BrightnessDialog extends Activity {                  getResources().getDimensionPixelSize(R.dimen.notification_side_paddings);          lp.leftMargin = horizontalMargin;          lp.rightMargin = horizontalMargin; + +        int verticalMargin = +                getResources().getDimensionPixelSize( +                        R.dimen.notification_guts_option_vertical_padding); + +        lp.topMargin = verticalMargin; +        lp.bottomMargin = verticalMargin; +          frame.setLayoutParams(lp);          Rect bounds = new Rect();          frame.addOnLayoutChangeListener( @@ -113,6 +127,20 @@ public class BrightnessDialog extends Activity {          frame.addView(controller.getRootView(), MATCH_PARENT, WRAP_CONTENT);          mBrightnessController = mBrightnessControllerFactory.create(controller); + +        Configuration configuration = getResources().getConfiguration(); +        int orientation = configuration.orientation; + +        if (orientation == Configuration.ORIENTATION_LANDSCAPE) { +            lp.width = getWindowManager().getDefaultDisplay().getWidth() / 2 +                    - lp.leftMargin * 2; +        } else if (orientation == Configuration.ORIENTATION_PORTRAIT) { +            lp.width = getWindowManager().getDefaultDisplay().getWidth() +                    - lp.leftMargin * 2; +        } + +        frame.setLayoutParams(lp); +      }      @Override diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index ba0cf08150f6..8dc97c0f797c 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -414,9 +414,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump      private int mDisplayLeftInset = 0; // in pixels      @VisibleForTesting -    KeyguardClockPositionAlgorithm -            mClockPositionAlgorithm = -            new KeyguardClockPositionAlgorithm(); +    KeyguardClockPositionAlgorithm mClockPositionAlgorithm;      private final KeyguardClockPositionAlgorithm.Result              mClockPositionResult =              new KeyguardClockPositionAlgorithm.Result(); @@ -779,7 +777,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump              KeyguardViewConfigurator keyguardViewConfigurator,              KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,              SplitShadeStateController splitShadeStateController, -            PowerInteractor powerInteractor) { +            PowerInteractor powerInteractor, +            KeyguardClockPositionAlgorithm keyguardClockPositionAlgorithm) {          keyguardStateController.addCallback(new KeyguardStateController.Callback() {              @Override              public void onKeyguardFadingAwayChanged() { @@ -807,6 +806,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump          mKeyguardInteractor = keyguardInteractor;          mPowerInteractor = powerInteractor;          mKeyguardViewConfigurator = keyguardViewConfigurator; +        mClockPositionAlgorithm = keyguardClockPositionAlgorithm;          mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {              @Override              public void onViewAttachedToWindow(View v) { @@ -1446,9 +1446,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump                      mBarState);          } -        if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) { -            setKeyguardVisibility(mBarState, false); -        } else { +        if (!mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {              setKeyguardBottomAreaVisibility(mBarState, false);          } @@ -2358,14 +2356,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump          }      } -    private void setKeyguardVisibility(int statusBarState, boolean goingToFullShade) { -        mKeyguardInteractor.setKeyguardRootVisibility( -            statusBarState, -            goingToFullShade, -            mIsOcclusionTransitionRunning -        ); -    } -      @Deprecated      private void setKeyguardBottomAreaVisibility(int statusBarState, boolean goingToFullShade) {          mKeyguardBottomArea.animate().cancel(); @@ -4443,11 +4433,13 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump                      && statusBarState == KEYGUARD) {                  // This means we're doing the screen off animation - position the keyguard status                  // view where it'll be on AOD, so we can animate it in. -                mKeyguardStatusViewController.updatePosition( -                        mClockPositionResult.clockX, -                        mClockPositionResult.clockYFullyDozing, -                        mClockPositionResult.clockScale, -                        false /* animate */); +                if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) { +                    mKeyguardStatusViewController.updatePosition( +                            mClockPositionResult.clockX, +                            mClockPositionResult.clockYFullyDozing, +                            mClockPositionResult.clockScale, +                            false /* animate */); +                }              }              mKeyguardStatusViewController.setKeyguardStatusViewVisibility( @@ -4456,9 +4448,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump                      goingToFullShade,                      mBarState); -            if (mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) { -                setKeyguardVisibility(statusBarState, goingToFullShade); -            } else { +            if (!mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {                  setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade);              } @@ -4562,7 +4552,12 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump      public void showAodUi() {          setDozing(true /* dozing */, false /* animate */);          mStatusBarStateController.setUpcomingState(KEYGUARD); -        mStatusBarStateListener.onStateChanged(KEYGUARD); + +        if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) { +            mStatusBarStateController.setState(KEYGUARD); +        } else { +            mStatusBarStateListener.onStateChanged(KEYGUARD); +        }          mStatusBarStateListener.onDozeAmountChanged(1f, 1f);          setExpandedFraction(1f);      } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt index 25bd8e7446bc..cb95b25ece80 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt @@ -38,7 +38,6 @@ import androidx.core.view.doOnLayout  import com.android.app.animation.Interpolators  import com.android.settingslib.Utils  import com.android.systemui.Dumpable -import com.android.systemui.res.R  import com.android.systemui.animation.ShadeInterpolation  import com.android.systemui.battery.BatteryMeterView  import com.android.systemui.battery.BatteryMeterViewController @@ -49,6 +48,7 @@ import com.android.systemui.dump.DumpManager  import com.android.systemui.plugins.ActivityStarter  import com.android.systemui.qs.ChipVisibilityListener  import com.android.systemui.qs.HeaderPrivacyIconsController +import com.android.systemui.res.R  import com.android.systemui.shade.ShadeHeaderController.Companion.HEADER_TRANSITION_ID  import com.android.systemui.shade.ShadeHeaderController.Companion.LARGE_SCREEN_HEADER_CONSTRAINT  import com.android.systemui.shade.ShadeHeaderController.Companion.LARGE_SCREEN_HEADER_TRANSITION_ID @@ -304,7 +304,8 @@ constructor(          iconManager = tintedIconManagerFactory.create(iconContainer, StatusBarLocation.QS)          iconManager.setTint( -            Utils.getColorAttrDefaultColor(header.context, android.R.attr.textColorPrimary) +            Utils.getColorAttrDefaultColor(header.context, android.R.attr.textColorPrimary), +            Utils.getColorAttrDefaultColor(header.context, android.R.attr.textColorPrimaryInverse),          )          carrierIconSlots = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt deleted file mode 100644 index 17b4e3baef13..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2019 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 - -import android.content.Context -import android.graphics.Bitmap -import android.graphics.Canvas -import android.graphics.Point -import android.graphics.Rect -import android.renderscript.Allocation -import android.renderscript.Element -import android.renderscript.RenderScript -import android.renderscript.ScriptIntrinsicBlur -import android.util.Log -import android.util.MathUtils -import com.android.internal.graphics.ColorUtils -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.statusbar.notification.MediaNotificationProcessor -import javax.inject.Inject - -private const val TAG = "MediaArtworkProcessor" -private const val COLOR_ALPHA = (255 * 0.7f).toInt() -private const val BLUR_RADIUS = 25f -private const val DOWNSAMPLE = 6 - -@SysUISingleton -class MediaArtworkProcessor @Inject constructor() { - -    private val mTmpSize = Point() -    private var mArtworkCache: Bitmap? = null - -    fun processArtwork(context: Context, artwork: Bitmap): Bitmap? { -        if (mArtworkCache != null) { -            return mArtworkCache -        } -        val renderScript = RenderScript.create(context) -        val blur = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript)) -        var input: Allocation? = null -        var output: Allocation? = null -        var inBitmap: Bitmap? = null -        try { -            @Suppress("DEPRECATION") -            context.display?.getSize(mTmpSize) -            val rect = Rect(0, 0, artwork.width, artwork.height) -            MathUtils.fitRect(rect, Math.max(mTmpSize.x / DOWNSAMPLE, mTmpSize.y / DOWNSAMPLE)) -            inBitmap = Bitmap.createScaledBitmap(artwork, rect.width(), rect.height(), -                    true /* filter */) -            // Render script blurs only support ARGB_8888, we need a conversion if we got a -            // different bitmap config. -            if (inBitmap.config != Bitmap.Config.ARGB_8888) { -                val oldIn = inBitmap -                inBitmap = oldIn.copy(Bitmap.Config.ARGB_8888, false /* isMutable */) -                oldIn.recycle() -            } -            val outBitmap = Bitmap.createBitmap(inBitmap?.width ?: 0, inBitmap?.height ?: 0, -                    Bitmap.Config.ARGB_8888) - -            input = Allocation.createFromBitmap(renderScript, inBitmap, -                    Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_GRAPHICS_TEXTURE) -            output = Allocation.createFromBitmap(renderScript, outBitmap) - -            blur.setRadius(BLUR_RADIUS) -            blur.setInput(input) -            blur.forEach(output) -            output.copyTo(outBitmap) - -            val swatch = MediaNotificationProcessor.findBackgroundSwatch(artwork) - -            val canvas = Canvas(outBitmap) -            canvas.drawColor(ColorUtils.setAlphaComponent(swatch.rgb, COLOR_ALPHA)) -            return outBitmap -        } catch (ex: IllegalArgumentException) { -            Log.e(TAG, "error while processing artwork", ex) -            return null -        } finally { -            input?.destroy() -            output?.destroy() -            blur.destroy() -            inBitmap?.recycle() -        } -    } - -    fun clearCache() { -        mArtworkCache?.recycle() -        mArtworkCache = null -    } -}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java index 2147510bbde5..f32f1c2dcd25 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java @@ -16,9 +16,16 @@  package com.android.systemui.statusbar;  import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED; +import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS; +import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS; +import static android.os.UserHandle.USER_NULL; +import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS; +import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS;  import static com.android.systemui.DejankUtils.whitelistIpcs; +import android.annotation.SuppressLint; +import android.annotation.UserIdInt;  import android.app.ActivityOptions;  import android.app.KeyguardManager;  import android.app.Notification; @@ -30,8 +37,10 @@ import android.content.IntentFilter;  import android.content.IntentSender;  import android.content.pm.UserInfo;  import android.database.ContentObserver; +import android.net.Uri;  import android.os.Handler;  import android.os.HandlerExecutor; +import android.os.Looper;  import android.os.UserHandle;  import android.os.UserManager;  import android.provider.Settings; @@ -41,14 +50,18 @@ import android.util.SparseBooleanArray;  import androidx.annotation.NonNull;  import androidx.annotation.VisibleForTesting; +import androidx.annotation.WorkerThread;  import com.android.internal.statusbar.NotificationVisibility;  import com.android.internal.widget.LockPatternUtils;  import com.android.systemui.Dumpable;  import com.android.systemui.broadcast.BroadcastDispatcher;  import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Background;  import com.android.systemui.dagger.qualifiers.Main;  import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FeatureFlagsClassic; +import com.android.systemui.flags.Flags;  import com.android.systemui.plugins.statusbar.StatusBarStateController;  import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;  import com.android.systemui.recents.OverviewProxyService; @@ -63,7 +76,9 @@ import com.android.systemui.util.settings.SecureSettings;  import java.io.PrintWriter;  import java.util.ArrayList; +import java.util.Collection;  import java.util.List; +import java.util.concurrent.Executor;  import javax.inject.Inject; @@ -84,6 +99,11 @@ public class NotificationLockscreenUserManagerImpl implements      private final SecureSettings mSecureSettings;      private final Object mLock = new Object(); +    private static final Uri SHOW_LOCKSCREEN = +            Settings.Secure.getUriFor(LOCK_SCREEN_SHOW_NOTIFICATIONS); +    private static final Uri SHOW_PRIVATE_LOCKSCREEN = +            Settings.Secure.getUriFor(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); +      private final Lazy<NotificationVisibilityProvider> mVisibilityProviderLazy;      private final Lazy<CommonNotifCollection> mCommonNotifCollectionLazy;      private final DevicePolicyManager mDevicePolicyManager; @@ -91,6 +111,23 @@ public class NotificationLockscreenUserManagerImpl implements      private final SparseBooleanArray mUsersWithSeparateWorkChallenge = new SparseBooleanArray();      private final SparseBooleanArray mUsersAllowingPrivateNotifications = new SparseBooleanArray();      private final SparseBooleanArray mUsersAllowingNotifications = new SparseBooleanArray(); + +    // The variables between mUsersDpcAllowingNotifications and +    // mUsersUsersAllowingPrivateNotifications (inclusive) are written on a background thread +    // and read on the main thread. Because the pipeline needs these values, adding locks would +    // introduce too much jank. This means that some pipeline runs could get incorrect values, that +    // would be fixed on the next pipeline run. We think this will be rare since a pipeline run +    // would have to overlap with a DPM sync or a user changing a value in Settings, and we run the +    // pipeline frequently enough that it should be corrected by the next time it matters for the +    // user. +    private final SparseBooleanArray mUsersDpcAllowingNotifications = new SparseBooleanArray(); +    private final SparseBooleanArray mUsersUsersAllowingNotifications = new SparseBooleanArray(); +    private boolean mKeyguardAllowingNotifications = true; +    private final SparseBooleanArray mUsersDpcAllowingPrivateNotifications +            = new SparseBooleanArray(); +    private final SparseBooleanArray mUsersUsersAllowingPrivateNotifications +            = new SparseBooleanArray(); +      private final SparseBooleanArray mUsersInLockdownLatestResult = new SparseBooleanArray();      private final SparseBooleanArray mShouldHideNotifsLatestResult = new SparseBooleanArray();      private final UserManager mUserManager; @@ -99,24 +136,39 @@ public class NotificationLockscreenUserManagerImpl implements      private final BroadcastDispatcher mBroadcastDispatcher;      private final NotificationClickNotifier mClickNotifier;      private final Lazy<OverviewProxyService> mOverviewProxyServiceLazy; +    private final FeatureFlagsClassic mFeatureFlags;      private boolean mShowLockscreenNotifications;      private LockPatternUtils mLockPatternUtils;      protected KeyguardManager mKeyguardManager;      private int mState = StatusBarState.SHADE;      private final ListenerSet<NotificationStateChangedListener> mNotifStateChangedListeners =              new ListenerSet<>(); +    private final Collection<Uri> mLockScreenUris = new ArrayList<>(); +      protected final BroadcastReceiver mAllUsersReceiver = new BroadcastReceiver() {          @Override          public void onReceive(Context context, Intent intent) {              final String action = intent.getAction(); -            if (ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(action) && -                    isCurrentProfile(getSendingUserId())) { -                mUsersAllowingPrivateNotifications.clear(); -                updateLockscreenNotificationSetting(); -                // TODO(b/231976036): Consolidate pipeline invalidations related to this event -                // notifyNotificationStateChanged(); +            if (ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(action)) { +                if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { +                    boolean changed = updateDpcSettings(getSendingUserId()); +                    if (mCurrentUserId == getSendingUserId()) { +                        changed |= updateLockscreenNotificationSetting(); +                    } +                    if (changed) { +                        notifyNotificationStateChanged(); +                    } +                } else { +                    if (isCurrentProfile(getSendingUserId())) { +                        mUsersAllowingPrivateNotifications.clear(); +                        updateLockscreenNotificationSetting(); +                        // TODO(b/231976036): Consolidate pipeline invalidations related to this +                        //  event +                        // notifyNotificationStateChanged(); +                    } +                }              }          }      }; @@ -136,6 +188,14 @@ public class NotificationLockscreenUserManagerImpl implements                      updateCurrentProfilesCache();                      break;                  case Intent.ACTION_USER_ADDED: +                    updateCurrentProfilesCache(); +                    if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { +                        final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL); +                        mBackgroundHandler.post(() -> { +                            initValuesForUser(userId); +                        }); +                    } +                    break;                  case Intent.ACTION_MANAGED_PROFILE_AVAILABLE:                  case Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE:                      updateCurrentProfilesCache(); @@ -193,6 +253,8 @@ public class NotificationLockscreenUserManagerImpl implements      protected final Context mContext;      private final Handler mMainHandler; +    private final Handler mBackgroundHandler; +    private final Executor mBackgroundExecutor;      protected final SparseArray<UserInfo> mCurrentProfiles = new SparseArray<>();      protected final SparseArray<UserInfo> mCurrentManagedProfiles = new SparseArray<>(); @@ -214,13 +276,18 @@ public class NotificationLockscreenUserManagerImpl implements              KeyguardManager keyguardManager,              StatusBarStateController statusBarStateController,              @Main Handler mainHandler, +            @Background Handler backgroundHandler, +            @Background Executor backgroundExecutor,              DeviceProvisionedController deviceProvisionedController,              KeyguardStateController keyguardStateController,              SecureSettings secureSettings,              DumpManager dumpManager, -            LockPatternUtils lockPatternUtils) { +            LockPatternUtils lockPatternUtils, +            FeatureFlagsClassic featureFlags) {          mContext = context;          mMainHandler = mainHandler; +        mBackgroundHandler = backgroundHandler; +        mBackgroundExecutor = backgroundExecutor;          mDevicePolicyManager = devicePolicyManager;          mUserManager = userManager;          mUserTracker = userTracker; @@ -236,6 +303,10 @@ public class NotificationLockscreenUserManagerImpl implements          mDeviceProvisionedController = deviceProvisionedController;          mSecureSettings = secureSettings;          mKeyguardStateController = keyguardStateController; +        mFeatureFlags = featureFlags; + +        mLockScreenUris.add(SHOW_LOCKSCREEN); +        mLockScreenUris.add(SHOW_PRIVATE_LOCKSCREEN);          dumpManager.registerDumpable(this);      } @@ -243,16 +314,53 @@ public class NotificationLockscreenUserManagerImpl implements      public void setUpWithPresenter(NotificationPresenter presenter) {          mPresenter = presenter; -        mLockscreenSettingsObserver = new ContentObserver(mMainHandler) { +        mLockscreenSettingsObserver = new ContentObserver( +                mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD) +                        ? mBackgroundHandler +                        : mMainHandler) { +              @Override -            public void onChange(boolean selfChange) { -                // We don't know which user changed LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS or -                // LOCK_SCREEN_SHOW_NOTIFICATIONS, so we just dump our cache ... -                mUsersAllowingPrivateNotifications.clear(); -                mUsersAllowingNotifications.clear(); -                // ... and refresh all the notifications -                updateLockscreenNotificationSetting(); -                notifyNotificationStateChanged(); +            public void onChange(boolean selfChange, Collection<Uri> uris, int flags) { +                if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { +                    @SuppressLint("MissingPermission") +                    List<UserInfo> users = mUserManager.getUsers(); +                    for (int i = users.size() - 1; i >= 0; i--) { +                        onChange(selfChange, uris, flags,users.get(i).getUserHandle()); +                    } +                } else { +                    // We don't know which user changed LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS or +                    // LOCK_SCREEN_SHOW_NOTIFICATIONS, so we just dump our cache ... +                    mUsersAllowingPrivateNotifications.clear(); +                    mUsersAllowingNotifications.clear(); +                    // ... and refresh all the notifications +                    updateLockscreenNotificationSetting(); +                    notifyNotificationStateChanged(); +                } +            } + +            // Note: even though this is an override, this method is not called by the OS +            // since we're not in system_server. We are using it internally for cases when +            // we have a single user id available (e.g. from USER_ADDED). +            @Override +            public void onChange(boolean selfChange, Collection<Uri> uris, +                    int flags, UserHandle user) { +                if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { +                    boolean changed = false; +                    for (Uri uri: uris) { +                        if (SHOW_LOCKSCREEN.equals(uri)) { +                            changed |= updateUserShowSettings(user.getIdentifier()); +                        } else if (SHOW_PRIVATE_LOCKSCREEN.equals(uri)) { +                            changed |= updateUserShowPrivateSettings(user.getIdentifier()); +                        } +                    } + +                    if (mCurrentUserId == user.getIdentifier()) { +                        changed |= updateLockscreenNotificationSetting(); +                    } +                    if (changed) { +                        notifyNotificationStateChanged(); +                    } +                }              }          }; @@ -268,23 +376,26 @@ public class NotificationLockscreenUserManagerImpl implements          };          mContext.getContentResolver().registerContentObserver( -                mSecureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS), false, +                SHOW_LOCKSCREEN, false,                  mLockscreenSettingsObserver,                  UserHandle.USER_ALL);          mContext.getContentResolver().registerContentObserver( -                mSecureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS), +                SHOW_PRIVATE_LOCKSCREEN,                  true,                  mLockscreenSettingsObserver,                  UserHandle.USER_ALL); -        mContext.getContentResolver().registerContentObserver( -                Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false, -                mSettingsObserver); +        if (!mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { +            mContext.getContentResolver().registerContentObserver( +                    Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false, +                    mSettingsObserver); +        }          mBroadcastDispatcher.registerReceiver(mAllUsersReceiver,                  new IntentFilter(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED), -                null /* handler */, UserHandle.ALL); +                mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD) +                        ? mBackgroundExecutor : null, UserHandle.ALL);          IntentFilter filter = new IntentFilter();          filter.addAction(Intent.ACTION_USER_ADDED); @@ -305,7 +416,23 @@ public class NotificationLockscreenUserManagerImpl implements          mCurrentUserId = mUserTracker.getUserId(); // in case we reg'd receiver too late          updateCurrentProfilesCache(); -        mSettingsObserver.onChange(false);  // set up +        if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { +            // Set  up +            mBackgroundHandler.post(() -> { +                @SuppressLint("MissingPermission") List<UserInfo> users = mUserManager.getUsers(); +                for (int i = users.size() - 1; i >= 0; i--) { +                    initValuesForUser(users.get(i).id); +                } +            }); +        } else { +            mSettingsObserver.onChange(false);  // set up +        } +    } + +    private void initValuesForUser(@UserIdInt int userId) { +        mLockscreenSettingsObserver.onChange( +                false, mLockScreenUris, 0, UserHandle.of(userId)); +        updateDpcSettings(userId);      }      public boolean shouldShowLockscreenNotifications() { @@ -322,17 +449,75 @@ public class NotificationLockscreenUserManagerImpl implements          mShowLockscreenNotifications = show;      } -    protected void updateLockscreenNotificationSetting() { -        final boolean show = mSecureSettings.getIntForUser( -                Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, -                1, -                mCurrentUserId) != 0; -        final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures( -                null /* admin */, mCurrentUserId); -        final boolean allowedByDpm = (dpmFlags -                & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0; +    protected boolean updateLockscreenNotificationSetting() { +        boolean show; +        boolean allowedByDpm; + +        if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { +            show = mUsersUsersAllowingNotifications.get(mCurrentUserId) +                    && mKeyguardAllowingNotifications; +            // If DPC never notified us about a user, that means they have no policy for the user, +            // and they allow the behavior +            allowedByDpm = mUsersDpcAllowingNotifications.get(mCurrentUserId, true); +        } else { +            show = mSecureSettings.getIntForUser( +                    LOCK_SCREEN_SHOW_NOTIFICATIONS, +                    1, +                    mCurrentUserId) != 0; +            final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures( +                    null /* admin */, mCurrentUserId); +            allowedByDpm = (dpmFlags +                    & KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0; +        } +        final boolean oldValue = mShowLockscreenNotifications;          setShowLockscreenNotifications(show && allowedByDpm); + +        return oldValue != mShowLockscreenNotifications; +    } + +    @WorkerThread +    protected boolean updateDpcSettings(int userId) { +        boolean originalAllowLockscreen = mUsersDpcAllowingNotifications.get(userId); +        boolean originalAllowPrivate = mUsersDpcAllowingPrivateNotifications.get(userId); +        final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures( +                null /* admin */, userId); +        final boolean allowedLockscreen = (dpmFlags & KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0; +        final boolean allowedPrivate = (dpmFlags & KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS) == 0; +        mUsersDpcAllowingNotifications.put(userId, allowedLockscreen); +        mUsersDpcAllowingPrivateNotifications.put(userId, allowedPrivate); +        return (originalAllowLockscreen != allowedLockscreen) +                || (originalAllowPrivate != allowedPrivate); +    } + +    @WorkerThread +    private boolean updateUserShowSettings(int userId) { +        boolean originalAllowLockscreen = mUsersUsersAllowingNotifications.get(userId); +        boolean newAllowLockscreen = mSecureSettings.getIntForUser( +                LOCK_SCREEN_SHOW_NOTIFICATIONS, +                1, +                userId) != 0; +        mUsersUsersAllowingNotifications.put(userId, newAllowLockscreen); +        boolean keyguardChanged = updateGlobalKeyguardSettings(); +        return (newAllowLockscreen != originalAllowLockscreen) || keyguardChanged; +    } + +    @WorkerThread +    private boolean updateUserShowPrivateSettings(int userId) { +        boolean originalValue = mUsersUsersAllowingPrivateNotifications.get(userId); +        boolean newValue = mSecureSettings.getIntForUser( +                LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, +                0, +                userId) != 0; +        mUsersUsersAllowingPrivateNotifications.put(userId, newValue); +        return (newValue != originalValue); +    } + +    @WorkerThread +    private boolean updateGlobalKeyguardSettings() { +        final boolean oldValue = mKeyguardAllowingNotifications; +        mKeyguardAllowingNotifications = mKeyguardManager.getPrivateNotificationsAllowed(); +        return oldValue != mKeyguardAllowingNotifications;      }      /** @@ -340,21 +525,41 @@ public class NotificationLockscreenUserManagerImpl implements       * when the lockscreen is in "public" (secure & locked) mode?       */      public boolean userAllowsPrivateNotificationsInPublic(int userHandle) { -        if (userHandle == UserHandle.USER_ALL) { -            return true; -        } +        if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { +            if (userHandle == UserHandle.USER_ALL) { +                userHandle = mCurrentUserId; +            } +            if (mUsersUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) { +                // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe +                // default value before moving to 'released' +                Log.wtf(TAG, "Asking for redact notifs setting too early", new Throwable()); +                updateUserShowPrivateSettings(userHandle); +            } +            if (mUsersDpcAllowingPrivateNotifications.indexOfKey(userHandle) < 0) { +                // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe +                // default value before moving to 'released' +                Log.wtf(TAG, "Asking for redact notifs dpm override too early", new Throwable()); +                updateDpcSettings(userHandle); +            } +            return mUsersUsersAllowingPrivateNotifications.get(userHandle) +                    && mUsersDpcAllowingPrivateNotifications.get(userHandle); +        } else { +            if (userHandle == UserHandle.USER_ALL) { +                return true; +            } -        if (mUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) { -            final boolean allowedByUser = 0 != mSecureSettings.getIntForUser( -                    Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, userHandle); -            final boolean allowedByDpm = adminAllowsKeyguardFeature(userHandle, -                    DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS); -            final boolean allowed = allowedByUser && allowedByDpm; -            mUsersAllowingPrivateNotifications.append(userHandle, allowed); -            return allowed; -        } +            if (mUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) { +                final boolean allowedByUser = 0 != mSecureSettings.getIntForUser( +                        LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, userHandle); +                final boolean allowedByDpm = adminAllowsKeyguardFeature(userHandle, +                        KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS); +                final boolean allowed = allowedByUser && allowedByDpm; +                mUsersAllowingPrivateNotifications.append(userHandle, allowed); +                return allowed; +            } -        return mUsersAllowingPrivateNotifications.get(userHandle); +            return mUsersAllowingPrivateNotifications.get(userHandle); +        }      }      /** @@ -406,21 +611,44 @@ public class NotificationLockscreenUserManagerImpl implements       * "public" (secure & locked) mode?       */      public boolean userAllowsNotificationsInPublic(int userHandle) { -        if (isCurrentProfile(userHandle) && userHandle != mCurrentUserId) { -            return true; -        } +        if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { +            // Unlike 'show private', settings does not show a copy of this setting for each +            // profile, so it inherits from the parent user. +            if (userHandle == UserHandle.USER_ALL || mCurrentManagedProfiles.contains(userHandle)) { +                userHandle = mCurrentUserId; +            } +            if (mUsersUsersAllowingNotifications.indexOfKey(userHandle) < 0) { +                // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe +                // default value before moving to 'released' +                Log.wtf(TAG, "Asking for show notifs setting too early", new Throwable()); +                updateUserShowSettings(userHandle); +            } +            if (mUsersDpcAllowingNotifications.indexOfKey(userHandle) < 0) { +                // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe +                // default value before moving to 'released' +                Log.wtf(TAG, "Asking for show notifs dpm override too early", new Throwable()); +                updateDpcSettings(userHandle); +            } +            return mUsersUsersAllowingNotifications.get(userHandle) +                    && mUsersDpcAllowingNotifications.get(userHandle) +                    && mKeyguardAllowingNotifications; +        } else { +            if (isCurrentProfile(userHandle) && userHandle != mCurrentUserId) { +                return true; +            } -        if (mUsersAllowingNotifications.indexOfKey(userHandle) < 0) { -            final boolean allowedByUser = 0 != mSecureSettings.getIntForUser( -                    Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, userHandle); -            final boolean allowedByDpm = adminAllowsKeyguardFeature(userHandle, -                    DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS); -            final boolean allowedBySystem = mKeyguardManager.getPrivateNotificationsAllowed(); -            final boolean allowed = allowedByUser && allowedByDpm && allowedBySystem; -            mUsersAllowingNotifications.append(userHandle, allowed); -            return allowed; +            if (mUsersAllowingNotifications.indexOfKey(userHandle) < 0) { +                final boolean allowedByUser = 0 != mSecureSettings.getIntForUser( +                        LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, userHandle); +                final boolean allowedByDpm = adminAllowsKeyguardFeature(userHandle, +                        KEYGUARD_DISABLE_SECURE_NOTIFICATIONS); +                final boolean allowedBySystem = mKeyguardManager.getPrivateNotificationsAllowed(); +                final boolean allowed = allowedByUser && allowedByDpm && allowedBySystem; +                mUsersAllowingNotifications.append(userHandle, allowed); +                return allowed; +            } +            return mUsersAllowingNotifications.get(userHandle);          } -        return mUsersAllowingNotifications.get(userHandle);      }      /** @return true if the entry needs redaction when on the lockscreen. */ @@ -451,9 +679,15 @@ public class NotificationLockscreenUserManagerImpl implements              return true;          }          NotificationEntry entry = mCommonNotifCollectionLazy.get().getEntry(key); -        return entry != null -                && entry.getRanking().getLockscreenVisibilityOverride()  -                == Notification.VISIBILITY_PRIVATE; +        if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { +            return entry != null +                    && entry.getRanking().getChannel().getLockscreenVisibility() +                    == Notification.VISIBILITY_PRIVATE; +        } else { +            return entry != null +                    && entry.getRanking().getLockscreenVisibilityOverride() +                    == Notification.VISIBILITY_PRIVATE; +        }      }      private void updateCurrentProfilesCache() { @@ -491,20 +725,6 @@ public class NotificationLockscreenUserManagerImpl implements      }      /** -     * If any managed/work profiles are in public mode. -     */ -    public boolean isAnyManagedProfilePublicMode() { -        synchronized (mLock) { -            for (int i = mCurrentManagedProfiles.size() - 1; i >= 0; i--) { -                if (isLockscreenPublicMode(mCurrentManagedProfiles.valueAt(i).id)) { -                    return true; -                } -            } -        } -        return false; -    } - -    /**       * Returns the current user id. This can change if the user is switched.       */      public int getCurrentUserId() { @@ -581,8 +801,16 @@ public class NotificationLockscreenUserManagerImpl implements      }      private void notifyNotificationStateChanged() { -        for (NotificationStateChangedListener listener : mNotifStateChangedListeners) { -            listener.onNotificationStateChanged(); +        if (!Looper.getMainLooper().isCurrentThread()) { +            mMainHandler.post(() -> { +                for (NotificationStateChangedListener listener : mNotifStateChangedListeners) { +                    listener.onNotificationStateChanged(); +                } +            }); +        } else { +            for (NotificationStateChangedListener listener : mNotifStateChangedListeners) { +                listener.onNotificationStateChanged(); +            }          }      } @@ -620,5 +848,15 @@ public class NotificationLockscreenUserManagerImpl implements          pw.println(mUsersInLockdownLatestResult);          pw.print("  mShouldHideNotifsLatestResult=");          pw.println(mShouldHideNotifsLatestResult); +        pw.print("  mUsersDpcAllowingNotifications="); +        pw.println(mUsersDpcAllowingNotifications); +        pw.print("  mUsersUsersAllowingNotifications="); +        pw.println(mUsersUsersAllowingNotifications); +        pw.print("  mKeyguardAllowingNotifications="); +        pw.println(mKeyguardAllowingNotifications); +        pw.print("  mUsersDpcAllowingPrivateNotifications="); +        pw.println(mUsersDpcAllowingPrivateNotifications); +        pw.print("  mUsersUsersAllowingPrivateNotifications="); +        pw.println(mUsersUsersAllowingPrivateNotifications);      }  } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java index 5bd40b8ed714..389486f0ada3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java @@ -15,43 +15,29 @@   */  package com.android.systemui.statusbar; -import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; -import static com.android.systemui.statusbar.phone.CentralSurfaces.DEBUG_MEDIA_FAKE_ARTWORK; -import static com.android.systemui.statusbar.phone.CentralSurfaces.ENABLE_LOCKSCREEN_WALLPAPER; -import static com.android.systemui.statusbar.phone.CentralSurfaces.SHOW_LOCKSCREEN_MEDIA_ARTWORK; - -import android.annotation.MainThread;  import android.annotation.NonNull;  import android.annotation.Nullable;  import android.app.Notification;  import android.app.WallpaperManager;  import android.content.Context; -import android.graphics.Bitmap;  import android.graphics.Point; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable;  import android.graphics.drawable.Icon;  import android.hardware.display.DisplayManager;  import android.media.MediaMetadata;  import android.media.session.MediaController;  import android.media.session.MediaSession;  import android.media.session.PlaybackState; -import android.os.AsyncTask;  import android.os.Trace;  import android.service.notification.NotificationStats;  import android.service.notification.StatusBarNotification; -import android.util.ArraySet;  import android.util.Log;  import android.view.Display;  import android.view.View;  import android.widget.ImageView; -import com.android.app.animation.Interpolators;  import com.android.internal.annotations.VisibleForTesting;  import com.android.systemui.Dumpable;  import com.android.systemui.colorextraction.SysuiColorExtractor; -import com.android.systemui.dagger.qualifiers.Main;  import com.android.systemui.dump.DumpManager;  import com.android.systemui.media.controls.models.player.MediaData;  import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData; @@ -65,18 +51,11 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.Di  import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;  import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;  import com.android.systemui.statusbar.phone.BiometricUnlockController; -import com.android.systemui.statusbar.phone.KeyguardBypassController;  import com.android.systemui.statusbar.phone.LockscreenWallpaper;  import com.android.systemui.statusbar.phone.ScrimController; -import com.android.systemui.statusbar.phone.ScrimState;  import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.util.Utils; -import com.android.systemui.util.concurrency.DelayableExecutor; - -import dagger.Lazy;  import java.io.PrintWriter; -import java.lang.ref.WeakReference;  import java.util.ArrayList;  import java.util.Arrays;  import java.util.Collection; @@ -85,7 +64,6 @@ import java.util.HashSet;  import java.util.List;  import java.util.Objects;  import java.util.Optional; -import java.util.Set;  import java.util.stream.Collectors;  /** @@ -99,7 +77,6 @@ public class NotificationMediaManager implements Dumpable {      private final StatusBarStateController mStatusBarStateController;      private final SysuiColorExtractor mColorExtractor;      private final KeyguardStateController mKeyguardStateController; -    private final KeyguardBypassController mKeyguardBypassController;      private static final HashSet<Integer> PAUSED_MEDIA_STATES = new HashSet<>();      private static final HashSet<Integer> CONNECTING_MEDIA_STATES = new HashSet<>();      static { @@ -117,9 +94,6 @@ public class NotificationMediaManager implements Dumpable {      private final NotifCollection mNotifCollection;      @Nullable -    private Lazy<NotificationShadeWindowController> mNotificationShadeWindowController; - -    @Nullable      private BiometricUnlockController mBiometricUnlockController;      @Nullable      private ScrimController mScrimController; @@ -128,12 +102,8 @@ public class NotificationMediaManager implements Dumpable {      @VisibleForTesting      boolean mIsLockscreenLiveWallpaperEnabled; -    private final DelayableExecutor mMainExecutor; -      private final Context mContext;      private final ArrayList<MediaListener> mMediaListeners; -    private final MediaArtworkProcessor mMediaArtworkProcessor; -    private final Set<AsyncTask<?, ?, ?>> mProcessArtworkTasks = new ArraySet<>();      protected NotificationPresenter mPresenter;      private MediaController mMediaController; @@ -150,8 +120,6 @@ public class NotificationMediaManager implements Dumpable {      private List<String> mSmallerInternalDisplayUids;      private Display mCurrentDisplay; -    private LockscreenWallpaper.WallpaperDrawable mWallapperDrawable; -      private final MediaController.Callback mMediaListener = new MediaController.Callback() {          @Override          public void onPlaybackStateChanged(PlaybackState state) { @@ -173,7 +141,6 @@ public class NotificationMediaManager implements Dumpable {              if (DEBUG_MEDIA) {                  Log.v(TAG, "DEBUG_MEDIA: onMetadataChanged: " + metadata);              } -            mMediaArtworkProcessor.clearCache();              mMediaMetadata = metadata;              dispatchUpdateMediaMetaData(true /* changed */, true /* allowAnimation */);          } @@ -184,13 +151,9 @@ public class NotificationMediaManager implements Dumpable {       */      public NotificationMediaManager(              Context context, -            Lazy<NotificationShadeWindowController> notificationShadeWindowController,              NotificationVisibilityProvider visibilityProvider, -            MediaArtworkProcessor mediaArtworkProcessor, -            KeyguardBypassController keyguardBypassController,              NotifPipeline notifPipeline,              NotifCollection notifCollection, -            @Main DelayableExecutor mainExecutor,              MediaDataManager mediaDataManager,              StatusBarStateController statusBarStateController,              SysuiColorExtractor colorExtractor, @@ -199,12 +162,8 @@ public class NotificationMediaManager implements Dumpable {              WallpaperManager wallpaperManager,              DisplayManager displayManager) {          mContext = context; -        mMediaArtworkProcessor = mediaArtworkProcessor; -        mKeyguardBypassController = keyguardBypassController;          mMediaListeners = new ArrayList<>(); -        mNotificationShadeWindowController = notificationShadeWindowController;          mVisibilityProvider = visibilityProvider; -        mMainExecutor = mainExecutor;          mMediaDataManager = mediaDataManager;          mNotifPipeline = notifPipeline;          mNotifCollection = notifCollection; @@ -476,7 +435,6 @@ public class NotificationMediaManager implements Dumpable {      }      private void clearCurrentMediaNotificationSession() { -        mMediaArtworkProcessor.clearCache();          mMediaMetadata = null;          if (mMediaController != null) {              if (DEBUG_MEDIA) { @@ -494,9 +452,6 @@ public class NotificationMediaManager implements Dumpable {      public void onDisplayUpdated(Display display) {          Trace.beginSection("NotificationMediaManager#onDisplayUpdated");          mCurrentDisplay = display; -        if (mWallapperDrawable != null) { -            mWallapperDrawable.onDisplayUpdated(isOnSmallerInternalDisplays()); -        }          Trace.endSection();      } @@ -531,18 +486,13 @@ public class NotificationMediaManager implements Dumpable {      }      /** -     * Refresh or remove lockscreen artwork from media metadata or the lockscreen wallpaper. +     * Update media state of lockscreen media views and controllers .       */ -    public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) { +    public void updateMediaMetaData(boolean metaDataChanged) {          if (mIsLockscreenLiveWallpaperEnabled) return;          Trace.beginSection("CentralSurfaces#updateMediaMetaData"); -        if (!SHOW_LOCKSCREEN_MEDIA_ARTWORK) { -            Trace.endSection(); -            return; -        } -          if (getBackDropView() == null) {              Trace.endSection();              return; // called too early @@ -566,168 +516,12 @@ public class NotificationMediaManager implements Dumpable {                      + " state=" + mStatusBarStateController.getState());          } -        Bitmap artworkBitmap = null; -        if (mediaMetadata != null && !mKeyguardBypassController.getBypassEnabled()) { -            artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ART); -            if (artworkBitmap == null) { -                artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART); -            } -        } - -        // Process artwork on a background thread and send the resulting bitmap to -        // finishUpdateMediaMetaData. -        if (metaDataChanged) { -            for (AsyncTask<?, ?, ?> task : mProcessArtworkTasks) { -                task.cancel(true); -            } -            mProcessArtworkTasks.clear(); -        } -        if (artworkBitmap != null && !Utils.useQsMediaPlayer(mContext)) { -            mProcessArtworkTasks.add(new ProcessArtworkTask(this, metaDataChanged, -                    allowEnterAnimation).execute(artworkBitmap)); -        } else { -            finishUpdateMediaMetaData(metaDataChanged, allowEnterAnimation, null); -        } - -        Trace.endSection(); -    } - -    private void finishUpdateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation, -            @Nullable Bitmap bmp) { -        Drawable artworkDrawable = null; -        if (bmp != null) { -            artworkDrawable = new BitmapDrawable(mBackdropBack.getResources(), bmp); -        } -        boolean hasMediaArtwork = artworkDrawable != null; -        boolean allowWhenShade = false; -        if (ENABLE_LOCKSCREEN_WALLPAPER && artworkDrawable == null) { -            Bitmap lockWallpaper = -                    mLockscreenWallpaper != null ? mLockscreenWallpaper.getBitmap() : null; -            if (lockWallpaper != null) { -                artworkDrawable = new LockscreenWallpaper.WallpaperDrawable( -                        mBackdropBack.getResources(), lockWallpaper, isOnSmallerInternalDisplays()); -                // We're in the SHADE mode on the SIM screen - yet we still need to show -                // the lockscreen wallpaper in that mode. -                allowWhenShade = mStatusBarStateController.getState() == KEYGUARD; -            } -        } - -        NotificationShadeWindowController windowController = -                mNotificationShadeWindowController.get(); -        boolean hideBecauseOccluded = mKeyguardStateController.isOccluded(); - -        final boolean hasArtwork = artworkDrawable != null; -        mColorExtractor.setHasMediaArtwork(hasMediaArtwork); +        mColorExtractor.setHasMediaArtwork(false);          if (mScrimController != null) { -            mScrimController.setHasBackdrop(hasArtwork); +            mScrimController.setHasBackdrop(false);          } -        if ((hasArtwork || DEBUG_MEDIA_FAKE_ARTWORK) -                && (mStatusBarStateController.getState() != StatusBarState.SHADE || allowWhenShade) -                &&  mBiometricUnlockController != null && mBiometricUnlockController.getMode() -                        != BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING -                && !hideBecauseOccluded) { -            // time to show some art! -            if (mBackdrop.getVisibility() != View.VISIBLE) { -                mBackdrop.setVisibility(View.VISIBLE); -                if (allowEnterAnimation) { -                    mBackdrop.setAlpha(0); -                    mBackdrop.animate().alpha(1f); -                } else { -                    mBackdrop.animate().cancel(); -                    mBackdrop.setAlpha(1f); -                } -                if (windowController != null) { -                    windowController.setBackdropShowing(true); -                } -                metaDataChanged = true; -                if (DEBUG_MEDIA) { -                    Log.v(TAG, "DEBUG_MEDIA: Fading in album artwork"); -                } -            } -            if (metaDataChanged) { -                if (mBackdropBack.getDrawable() != null) { -                    Drawable drawable = -                            mBackdropBack.getDrawable().getConstantState() -                                    .newDrawable(mBackdropFront.getResources()).mutate(); -                    mBackdropFront.setImageDrawable(drawable); -                    mBackdropFront.setAlpha(1f); -                    mBackdropFront.setVisibility(View.VISIBLE); -                } else { -                    mBackdropFront.setVisibility(View.INVISIBLE); -                } - -                if (DEBUG_MEDIA_FAKE_ARTWORK) { -                    final int c = 0xFF000000 | (int)(Math.random() * 0xFFFFFF); -                    Log.v(TAG, String.format("DEBUG_MEDIA: setting new color: 0x%08x", c)); -                    mBackdropBack.setBackgroundColor(0xFFFFFFFF); -                    mBackdropBack.setImageDrawable(new ColorDrawable(c)); -                } else { -                    if (artworkDrawable instanceof LockscreenWallpaper.WallpaperDrawable) { -                        mWallapperDrawable = -                                (LockscreenWallpaper.WallpaperDrawable) artworkDrawable; -                    } -                    mBackdropBack.setImageDrawable(artworkDrawable); -                } - -                if (mBackdropFront.getVisibility() == View.VISIBLE) { -                    if (DEBUG_MEDIA) { -                        Log.v(TAG, "DEBUG_MEDIA: Crossfading album artwork from " -                                + mBackdropFront.getDrawable() -                                + " to " -                                + mBackdropBack.getDrawable()); -                    } -                    mBackdropFront.animate() -                            .setDuration(250) -                            .alpha(0f).withEndAction(mHideBackdropFront); -                } -            } -        } else { -            // need to hide the album art, either because we are unlocked, on AOD -            // or because the metadata isn't there to support it -            if (mBackdrop.getVisibility() != View.GONE) { -                if (DEBUG_MEDIA) { -                    Log.v(TAG, "DEBUG_MEDIA: Fading out album artwork"); -                } -                boolean cannotAnimateDoze = mStatusBarStateController.isDozing() -                        && !ScrimState.AOD.getAnimateChange(); -                if (((mBiometricUnlockController != null && mBiometricUnlockController.getMode() -                        == BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING -                                || cannotAnimateDoze)) -                        || hideBecauseOccluded) { -                    // We are unlocking directly - no animation! -                    mBackdrop.setVisibility(View.GONE); -                    mBackdropBack.setImageDrawable(null); -                    if (windowController != null) { -                        windowController.setBackdropShowing(false); -                    } -                } else { -                    if (windowController != null) { -                        windowController.setBackdropShowing(false); -                    } -                    mBackdrop.animate() -                            .alpha(0) -                            .setInterpolator(Interpolators.ACCELERATE_DECELERATE) -                            .setDuration(300) -                            .setStartDelay(0) -                            .withEndAction(() -> { -                                mBackdrop.setVisibility(View.GONE); -                                mBackdropFront.animate().cancel(); -                                mBackdropBack.setImageDrawable(null); -                                mMainExecutor.execute(mHideBackdropFront); -                            }); -                    if (mKeyguardStateController.isKeyguardFadingAway()) { -                        mBackdrop.animate() -                                .setDuration( -                                        mKeyguardStateController.getShortenedFadingAwayDuration()) -                                .setStartDelay( -                                        mKeyguardStateController.getKeyguardFadingAwayDelay()) -                                .setInterpolator(Interpolators.LINEAR) -                                .start(); -                    } -                } -            } -        } +        Trace.endSection();      }      public void setup(BackDropView backdrop, ImageView backdropFront, ImageView backdropBack, @@ -758,15 +552,6 @@ public class NotificationMediaManager implements Dumpable {          }      }; -    private Bitmap processArtwork(Bitmap artwork) { -        return mMediaArtworkProcessor.processArtwork(mContext, artwork); -    } - -    @MainThread -    private void removeTask(AsyncTask<?, ?, ?> task) { -        mProcessArtworkTasks.remove(task); -    } -      // TODO(b/273443374): remove      public boolean isLockscreenWallpaperOnNotificationShade() {          return mBackdrop != null && mLockscreenWallpaper != null @@ -780,52 +565,6 @@ public class NotificationMediaManager implements Dumpable {          return mBackdrop;      } -    /** -     * {@link AsyncTask} to prepare album art for use as backdrop on lock screen. -     */ -    private static final class ProcessArtworkTask extends AsyncTask<Bitmap, Void, Bitmap> { - -        private final WeakReference<NotificationMediaManager> mManagerRef; -        private final boolean mMetaDataChanged; -        private final boolean mAllowEnterAnimation; - -        ProcessArtworkTask(NotificationMediaManager manager, boolean changed, -                boolean allowAnimation) { -            mManagerRef = new WeakReference<>(manager); -            mMetaDataChanged = changed; -            mAllowEnterAnimation = allowAnimation; -        } - -        @Override -        protected Bitmap doInBackground(Bitmap... bitmaps) { -            NotificationMediaManager manager = mManagerRef.get(); -            if (manager == null || bitmaps.length == 0 || isCancelled()) { -                return null; -            } -            return manager.processArtwork(bitmaps[0]); -        } - -        @Override -        protected void onPostExecute(@Nullable Bitmap result) { -            NotificationMediaManager manager = mManagerRef.get(); -            if (manager != null && !isCancelled()) { -                manager.removeTask(this); -                manager.finishUpdateMediaMetaData(mMetaDataChanged, mAllowEnterAnimation, result); -            } -        } - -        @Override -        protected void onCancelled(Bitmap result) { -            if (result != null) { -                result.recycle(); -            } -            NotificationMediaManager manager = mManagerRef.get(); -            if (manager != null) { -                manager.removeTask(this); -            } -        } -    } -      public interface MediaListener {          /**           * Called whenever there's new metadata or playback state. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java index d4b6dfb9b625..61ebcc0c99d1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java @@ -15,6 +15,7 @@   */  package com.android.systemui.statusbar; +  import android.app.ActivityManager;  import android.app.ActivityOptions;  import android.app.KeyguardManager; @@ -48,10 +49,10 @@ import androidx.annotation.Nullable;  import com.android.internal.statusbar.IStatusBarService;  import com.android.internal.statusbar.NotificationVisibility;  import com.android.systemui.Dumpable; -import com.android.systemui.res.R;  import com.android.systemui.dump.DumpManager;  import com.android.systemui.plugins.statusbar.StatusBarStateController;  import com.android.systemui.power.domain.interactor.PowerInteractor; +import com.android.systemui.res.R;  import com.android.systemui.statusbar.dagger.CentralSurfacesDependenciesModule;  import com.android.systemui.statusbar.notification.NotifPipelineFlags;  import com.android.systemui.statusbar.notification.RemoteInputControllerLogger; @@ -535,7 +536,8 @@ public class NotificationRemoteInputManager implements Dumpable {      public void cleanUpRemoteInputForUserRemoval(NotificationEntry entry) {          if (isRemoteInputActive(entry)) {              entry.mRemoteEditImeVisible = false; -            mRemoteInputController.removeRemoteInput(entry, null); +            mRemoteInputController.removeRemoteInput(entry, null, +                    /* reason= */"RemoteInputManager#cleanUpRemoteInputForUserRemoval");          }      } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index f8c049e86cb8..23b697e5554b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -133,7 +133,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St      public void bind(AmbientState ambientState,                       NotificationStackScrollLayoutController hostLayoutController) { -        mShelfRefactor.assertDisabled(); +        mShelfRefactor.assertInLegacyMode();          mAmbientState = ambientState;          mHostLayoutController = hostLayoutController;          hostLayoutController.setOnNotificationRemovedListener((child, isTransferInProgress) -> { @@ -143,7 +143,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St      public void bind(AmbientState ambientState, NotificationStackScrollLayout hostLayout,              NotificationRoundnessManager roundnessManager) { -        if (!mShelfRefactor.expectEnabled()) return; +        if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;          mAmbientState = ambientState;          mHostLayout = hostLayout;          mRoundnessManager = roundnessManager; @@ -964,7 +964,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St      @Override      public void onStateChanged(int newState) { -        mShelfRefactor.assertDisabled(); +        mShelfRefactor.assertInLegacyMode();          mStatusBarState = newState;          updateInteractiveness();      } @@ -1022,17 +1022,17 @@ public class NotificationShelf extends ActivatableNotificationView implements St      }      public void setController(NotificationShelfController notificationShelfController) { -        mShelfRefactor.assertDisabled(); +        mShelfRefactor.assertInLegacyMode();          mController = notificationShelfController;      }      public void setCanModifyColorOfNotifications(boolean canModifyColorOfNotifications) { -        if (!mShelfRefactor.expectEnabled()) return; +        if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;          mCanModifyColorOfNotifications = canModifyColorOfNotifications;      }      public void setCanInteract(boolean canInteract) { -        if (!mShelfRefactor.expectEnabled()) return; +        if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;          mCanInteract = canInteract;          updateInteractiveness();      } @@ -1050,7 +1050,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St      }      public void requestRoundnessResetFor(ExpandableView child) { -        if (!mShelfRefactor.expectEnabled()) return; +        if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;          child.requestRoundnessReset(SHELF_SCROLL);      } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java index d5e4902366e7..17da015789ea 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java @@ -31,6 +31,8 @@ import com.android.systemui.statusbar.policy.RemoteInputUriController;  import com.android.systemui.statusbar.policy.RemoteInputView;  import com.android.systemui.util.DumpUtilsKt; +import com.google.errorprone.annotations.CompileTimeConstant; +  import java.lang.ref.WeakReference;  import java.util.ArrayList;  import java.util.Objects; @@ -69,7 +71,8 @@ public class RemoteInputController {       * @param entry the entry for which a remote input is now active.       * @param token a token identifying the view that is managing the remote input       */ -    public void addRemoteInput(NotificationEntry entry, Object token) { +    public void addRemoteInput(NotificationEntry entry, Object token, +            @CompileTimeConstant String reason) {          Objects.requireNonNull(entry);          Objects.requireNonNull(token);          boolean isActive = isRemoteInputActive(entry); @@ -77,7 +80,9 @@ public class RemoteInputController {                  entry /* contains */, null /* remove */, token /* removeToken */);          mLogger.logAddRemoteInput(entry.getKey()/* entryKey */,                  isActive /* isRemoteInputAlreadyActive */, -                found /* isRemoteInputFound */); +                found /* isRemoteInputFound */, +                reason /* reason */, +                entry.getNotificationStyle()/* notificationStyle */);          if (!found) {              mOpen.add(new Pair<>(new WeakReference<>(entry), token));          } @@ -96,7 +101,8 @@ public class RemoteInputController {       *              the entry is only removed if the token matches the last added token for this       *              entry. If null, the entry is removed regardless.       */ -    public void removeRemoteInput(NotificationEntry entry, Object token) { +    public void removeRemoteInput(NotificationEntry entry, Object token, +            @CompileTimeConstant String reason) {          Objects.requireNonNull(entry);          if (entry.mRemoteEditImeVisible && entry.mRemoteEditImeAnimatingAway) {              mLogger.logRemoveRemoteInput( @@ -104,9 +110,12 @@ public class RemoteInputController {                      true /* remoteEditImeVisible */,                      true /* remoteEditImeAnimatingAway */,                      isRemoteInputActive(entry) /* isRemoteInputActiveForEntry */, -                    isRemoteInputActive() /* isRemoteInputActive */); +                    isRemoteInputActive() /* isRemoteInputActive */, +                    reason /* reason */, +                    entry.getNotificationStyle()/* notificationStyle */);              return;          } +          // If the view is being removed, this may be called even though we're not active          boolean remoteInputActiveForEntry = isRemoteInputActive(entry);          mLogger.logRemoveRemoteInput( @@ -114,7 +123,9 @@ public class RemoteInputController {                  entry.mRemoteEditImeVisible /* remoteEditImeVisible */,                  entry.mRemoteEditImeAnimatingAway /* remoteEditImeAnimatingAway */,                  remoteInputActiveForEntry /* isRemoteInputActiveForEntry */, -                isRemoteInputActive()/* isRemoteInputActive */); +                isRemoteInputActive()/* isRemoteInputActive */, +                reason/* reason */, +                entry.getNotificationStyle()/* notificationStyle */);          if (!remoteInputActiveForEntry) return; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java index 1196211bd671..595ab701a9f2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java @@ -21,6 +21,21 @@ import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;  public interface StatusIconDisplayable extends DarkReceiver {      String getSlot();      void setStaticDrawableColor(int color); + +    /** +     * For a layer drawable, or one that has a background, {@code tintColor} should be used as the +     * background tint for the container, while {@code contrastColor} can be used as the foreground +     * drawable's tint so that it is visible on the background. Essentially, tintColor should apply +     * to the portion of the icon that borders the underlying window content (status bar's +     * background), and the contrastColor only need be used to distinguish from the tintColor. +     * +     * Defaults to calling {@link #setStaticDrawableColor(int)} with only the tint color, so modern +     * callers can just call this method and still get the default behavior. +     */ +    default void setStaticDrawableColor(int tintColor, int contrastColor) { +        setStaticDrawableColor(tintColor); +    } +      void setDecorColor(int color);      /** Sets the visible state that this displayable should be. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java index 7f5829d81c6f..125c8efe1884 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java @@ -31,7 +31,6 @@ import com.android.systemui.animation.DialogLaunchAnimator;  import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;  import com.android.systemui.colorextraction.SysuiColorExtractor;  import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.dagger.qualifiers.Main;  import com.android.systemui.dump.DumpHandler;  import com.android.systemui.dump.DumpManager;  import com.android.systemui.flags.FeatureFlags; @@ -45,12 +44,10 @@ import com.android.systemui.shade.ShadeSurface;  import com.android.systemui.shade.carrier.ShadeCarrierGroupController;  import com.android.systemui.statusbar.ActionClickLogger;  import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.MediaArtworkProcessor;  import com.android.systemui.statusbar.NotificationClickNotifier;  import com.android.systemui.statusbar.NotificationLockscreenUserManager;  import com.android.systemui.statusbar.NotificationMediaManager;  import com.android.systemui.statusbar.NotificationRemoteInputManager; -import com.android.systemui.statusbar.NotificationShadeWindowController;  import com.android.systemui.statusbar.SmartReplyController;  import com.android.systemui.statusbar.StatusBarStateControllerImpl;  import com.android.systemui.statusbar.SysuiStatusBarStateController; @@ -61,7 +58,6 @@ import com.android.systemui.statusbar.notification.collection.NotifCollection;  import com.android.systemui.statusbar.notification.collection.NotifPipeline;  import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;  import com.android.systemui.statusbar.phone.CentralSurfacesImpl; -import com.android.systemui.statusbar.phone.KeyguardBypassController;  import com.android.systemui.statusbar.phone.ManagedProfileController;  import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl;  import com.android.systemui.statusbar.phone.StatusBarIconController; @@ -71,7 +67,6 @@ import com.android.systemui.statusbar.phone.StatusBarNotificationPresenterModule  import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback;  import com.android.systemui.statusbar.policy.KeyguardStateController;  import com.android.systemui.statusbar.policy.RemoteInputUriController; -import com.android.systemui.util.concurrency.DelayableExecutor;  import dagger.Binds;  import dagger.Lazy; @@ -122,13 +117,9 @@ public interface CentralSurfacesDependenciesModule {      @Provides      static NotificationMediaManager provideNotificationMediaManager(              Context context, -            Lazy<NotificationShadeWindowController> notificationShadeWindowController,              NotificationVisibilityProvider visibilityProvider, -            MediaArtworkProcessor mediaArtworkProcessor, -            KeyguardBypassController keyguardBypassController,              NotifPipeline notifPipeline,              NotifCollection notifCollection, -            @Main DelayableExecutor mainExecutor,              MediaDataManager mediaDataManager,              StatusBarStateController statusBarStateController,              SysuiColorExtractor colorExtractor, @@ -138,13 +129,9 @@ public interface CentralSurfacesDependenciesModule {              DisplayManager displayManager) {          return new NotificationMediaManager(                  context, -                notificationShadeWindowController,                  visibilityProvider, -                mediaArtworkProcessor, -                keyguardBypassController,                  notifPipeline,                  notifCollection, -                mainExecutor,                  mediaDataManager,                  statusBarStateController,                  colorExtractor, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java deleted file mode 100644 index 732c115571f8..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - *      http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui.statusbar.notification; - -import android.graphics.Bitmap; -import android.graphics.Color; - -import androidx.palette.graphics.Palette; - -import java.util.List; - -/** - * A gutted class that now contains only a color extraction utility used by the - * MediaArtworkProcessor, which has otherwise supplanted this. - * - * TODO(b/182926117): move this into MediaArtworkProcessor.kt - */ -public class MediaNotificationProcessor { - -    /** -     * The population fraction to select a white or black color as the background over a color. -     */ -    private static final float POPULATION_FRACTION_FOR_WHITE_OR_BLACK = 2.5f; -    private static final float BLACK_MAX_LIGHTNESS = 0.08f; -    private static final float WHITE_MIN_LIGHTNESS = 0.90f; -    private static final int RESIZE_BITMAP_AREA = 150 * 150; - -    private MediaNotificationProcessor() { -    } - -    /** -     * Finds an appropriate background swatch from media artwork. -     * -     * @param artwork Media artwork -     * @return Swatch that should be used as the background of the media notification. -     */ -    public static Palette.Swatch findBackgroundSwatch(Bitmap artwork) { -        return findBackgroundSwatch(generateArtworkPaletteBuilder(artwork).generate()); -    } - -    /** -     * Finds an appropriate background swatch from the palette of media artwork. -     * -     * @param palette Artwork palette, should be obtained from {@link generateArtworkPaletteBuilder} -     * @return Swatch that should be used as the background of the media notification. -     */ -    public static Palette.Swatch findBackgroundSwatch(Palette palette) { -        // by default we use the dominant palette -        Palette.Swatch dominantSwatch = palette.getDominantSwatch(); -        if (dominantSwatch == null) { -            return new Palette.Swatch(Color.WHITE, 100); -        } - -        if (!isWhiteOrBlack(dominantSwatch.getHsl())) { -            return dominantSwatch; -        } -        // Oh well, we selected black or white. Lets look at the second color! -        List<Palette.Swatch> swatches = palette.getSwatches(); -        float highestNonWhitePopulation = -1; -        Palette.Swatch second = null; -        for (Palette.Swatch swatch : swatches) { -            if (swatch != dominantSwatch -                    && swatch.getPopulation() > highestNonWhitePopulation -                    && !isWhiteOrBlack(swatch.getHsl())) { -                second = swatch; -                highestNonWhitePopulation = swatch.getPopulation(); -            } -        } -        if (second == null) { -            return dominantSwatch; -        } -        if (dominantSwatch.getPopulation() / highestNonWhitePopulation -                > POPULATION_FRACTION_FOR_WHITE_OR_BLACK) { -            // The dominant swatch is very dominant, lets take it! -            // We're not filtering on white or black -            return dominantSwatch; -        } else { -            return second; -        } -    } - -    /** -     * Generate a palette builder for media artwork. -     * -     * For producing a smooth background transition, the palette is extracted from only the left -     * side of the artwork. -     * -     * @param artwork Media artwork -     * @return Builder that generates the {@link Palette} for the media artwork. -     */ -    public static Palette.Builder generateArtworkPaletteBuilder(Bitmap artwork) { -        // for the background we only take the left side of the image to ensure -        // a smooth transition -        return Palette.from(artwork) -                .setRegion(0, 0, artwork.getWidth() / 2, artwork.getHeight()) -                .clearFilters() // we want all colors, red / white / black ones too! -                .resizeBitmapArea(RESIZE_BITMAP_AREA); -    } - -    private static boolean isWhiteOrBlack(float[] hsl) { -        return isBlack(hsl) || isWhite(hsl); -    } - -    /** -     * @return true if the color represents a color which is close to black. -     */ -    private static boolean isBlack(float[] hslColor) { -        return hslColor[2] <= BLACK_MAX_LIGHTNESS; -    } - -    /** -     * @return true if the color represents a color which is close to white. -     */ -    private static boolean isWhite(float[] hslColor) { -        return hslColor[2] >= WHITE_MIN_LIGHTNESS; -    } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt index 39b999cb4f35..ff89c62ab496 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt @@ -32,17 +32,24 @@ constructor(@NotificationRemoteInputLog private val logBuffer: LogBuffer) {      fun logAddRemoteInput(          entryKey: String,          isRemoteInputAlreadyActive: Boolean, -        isRemoteInputFound: Boolean +        isRemoteInputFound: Boolean, +        reason: String, +        notificationStyle: String      ) =          logBuffer.log(              TAG,              DEBUG,              {                  str1 = entryKey +                str2 = reason +                str3 = notificationStyle                  bool1 = isRemoteInputAlreadyActive                  bool2 = isRemoteInputFound              }, -            { "addRemoteInput entry: $str1, isAlreadyActive: $bool1, isFound:$bool2" } +            { +                "addRemoteInput reason:$str2 entry: $str1, style:$str3" + +                    ", isAlreadyActive: $bool1, isFound:$bool2" +            }          )      /** logs removeRemoteInput invocation of [RemoteInputController] */ @@ -52,20 +59,25 @@ constructor(@NotificationRemoteInputLog private val logBuffer: LogBuffer) {          remoteEditImeVisible: Boolean,          remoteEditImeAnimatingAway: Boolean,          isRemoteInputActiveForEntry: Boolean, -        isRemoteInputActive: Boolean +        isRemoteInputActive: Boolean, +        reason: String, +        notificationStyle: String      ) =          logBuffer.log(              TAG,              DEBUG,              {                  str1 = entryKey +                str2 = reason +                str3 = notificationStyle                  bool1 = remoteEditImeVisible                  bool2 = remoteEditImeAnimatingAway                  bool3 = isRemoteInputActiveForEntry                  bool4 = isRemoteInputActive              },              { -                "removeRemoteInput entry: $str1, remoteEditImeVisible: $bool1" + +                "removeRemoteInput reason: $str2 entry: $str1" + +                    ", style: $str3, remoteEditImeVisible: $bool1" +                      ", remoteEditImeAnimatingAway: $bool2, isRemoteInputActiveForEntry: $bool3" +                      ", isRemoteInputActive: $bool4"              } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index affd2d186774..4573d5989faa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -979,6 +979,19 @@ public final class NotificationEntry extends ListEntry {          return mExpandAnimationRunning;      } +    /** +     * @return NotificationStyle +     */ +    public String getNotificationStyle() { +        if (isSummaryWithChildren()) { +            return "summary"; +        } + +        final Class<? extends Notification.Style> style = +                getSbn().getNotification().getNotificationStyle(); +        return style == null ? "nostyle" : style.getSimpleName(); +    } +      /** Information about a suggestion that is being edited. */      public static class EditedSuggestionInfo { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt index 50efbb5458cf..eb5c1fa3b0ca 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt @@ -167,17 +167,16 @@ constructor(          NotificationShelfViewBinderWrapperControllerImpl.unsupported      override fun setShelfIcons(icons: NotificationIconContainer) { -        if (shelfRefactor.expectEnabled()) { -            NotificationIconContainerViewBinder.bind( -                icons, -                shelfIconsViewModel, -                configurationController, -                dozeParameters, -                featureFlags, -                screenOffAnimationController, -            ) -            shelfIcons = icons -        } +        if (shelfRefactor.isUnexpectedlyInLegacyMode()) return +        NotificationIconContainerViewBinder.bind( +            icons, +            shelfIconsViewModel, +            configurationController, +            dozeParameters, +            featureFlags, +            screenOffAnimationController, +        ) +        shelfIcons = icons      }      override fun onDensityOrFontScaleChanged(context: Context) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt index b2c32cd42d1d..db7f46eb28f2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt @@ -1,6 +1,5 @@  package com.android.systemui.statusbar.notification.interruption -import android.app.Notification  import android.app.Notification.VISIBILITY_SECRET  import android.content.Context  import android.database.ContentObserver @@ -14,6 +13,8 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback  import com.android.systemui.CoreStartable  import com.android.systemui.dagger.SysUISingleton  import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.flags.FeatureFlagsClassic +import com.android.systemui.flags.Flags  import com.android.systemui.plugins.statusbar.StatusBarStateController  import com.android.systemui.settings.UserTracker  import com.android.systemui.statusbar.NotificationLockscreenUserManager @@ -78,7 +79,8 @@ class KeyguardNotificationVisibilityProviderImpl @Inject constructor(      private val statusBarStateController: SysuiStatusBarStateController,      private val userTracker: UserTracker,      private val secureSettings: SecureSettings, -    private val globalSettings: GlobalSettings +    private val globalSettings: GlobalSettings, +    private val featureFlags: FeatureFlagsClassic  ) : CoreStartable, KeyguardNotificationVisibilityProvider {      private val showSilentNotifsUri =              secureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS) @@ -201,7 +203,7 @@ class KeyguardNotificationVisibilityProviderImpl @Inject constructor(              // device isn't public, no need to check public-related settings, so allow              !lockscreenUserManager.isLockscreenPublicMode(user) -> false              // entry is meant to be secret on the lockscreen, disallow -            entry.ranking.lockscreenVisibilityOverride == Notification.VISIBILITY_SECRET -> true +            isRankingVisibilitySecret(entry) -> true              // disallow if user disallows notifications in public              else -> !lockscreenUserManager.userAllowsNotificationsInPublic(user)          } @@ -215,6 +217,17 @@ class KeyguardNotificationVisibilityProviderImpl @Inject constructor(          }      } +    private fun isRankingVisibilitySecret(entry: NotificationEntry): Boolean { +        return if (featureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) { +            // ranking.lockscreenVisibilityOverride contains possibly out of date DPC and Setting +            // info, and NotificationLockscreenUserManagerImpl is already listening for updates +            // to those +            entry.ranking.channel.lockscreenVisibility == VISIBILITY_SECRET +        } else { +            entry.ranking.lockscreenVisibilityOverride == VISIBILITY_SECRET +        } +    } +      override fun dump(pw: PrintWriter, args: Array<out String>) = pw.asIndenting().run {          println("isLockedOrLocking=$isLockedOrLocking")          withIncreasedIndent { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 9340b85a743d..11c65e542bcd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -1901,16 +1901,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView              return traceTag;          } -        if (isSummaryWithChildren()) { -            return traceTag + "(summary)"; -        } -        Class<? extends Notification.Style> style = -                getEntry().getSbn().getNotification().getNotificationStyle(); -        if (style == null) { -            return traceTag + "(nostyle)"; -        } else { -            return traceTag + "(" + style.getSimpleName() + ")"; -        } +        return  traceTag + "(" + getEntry().getNotificationStyle() + ")";      }      @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index a27a305428c4..60e75ff9e6e1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -1363,12 +1363,12 @@ public class NotificationContentView extends FrameLayout implements Notification                          result.mController.setPendingIntent(existingPendingIntent);                      }                      if (result.mController.updatePendingIntentFromActions(actions)) { -                        if (!result.mView.isActive()) { -                            result.mView.focus(); +                        if (!result.mController.isActive()) { +                            result.mController.focus();                          }                      } else { -                        if (result.mView.isActive()) { -                            result.mView.close(); +                        if (result.mController.isActive()) { +                            result.mController.close();                          }                      }                  } 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 79f8f22fd753..dba93d9718cb 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 @@ -2735,7 +2735,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable       * @param listener callback for notification removed       */      public void setOnNotificationRemovedListener(OnNotificationRemovedListener listener) { -        mShelfRefactor.assertDisabled(); +        mShelfRefactor.assertInLegacyMode();          mOnNotificationRemovedListener = listener;      } @@ -4982,12 +4982,12 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable      @Nullable      public ExpandableView getShelf() { -        if (!mShelfRefactor.expectEnabled()) return null; +        if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return null;          return mShelf;      }      public void setShelf(NotificationShelf shelf) { -        if (!mShelfRefactor.expectEnabled()) return; +        if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;          int index = -1;          if (mShelf != null) {              index = indexOfChild(mShelf); @@ -5001,7 +5001,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable      }      public void setShelfController(NotificationShelfController notificationShelfController) { -        mShelfRefactor.assertDisabled(); +        mShelfRefactor.assertInLegacyMode();          int index = -1;          if (mShelf != null) {              index = indexOfChild(mShelf); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 50207806ecaa..6a70815f82f3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -1431,7 +1431,7 @@ public class NotificationStackScrollLayoutController {      }      public void setShelfController(NotificationShelfController notificationShelfController) { -        mShelfRefactor.assertDisabled(); +        mShelfRefactor.assertInLegacyMode();          mView.setShelfController(notificationShelfController);      } @@ -1644,12 +1644,12 @@ public class NotificationStackScrollLayoutController {      }      public void setShelf(NotificationShelf shelf) { -        if (!mShelfRefactor.expectEnabled()) return; +        if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return;          mView.setShelf(shelf);      }      public int getShelfHeight() { -        if (!mShelfRefactor.expectEnabled()) { +        if (mShelfRefactor.isUnexpectedlyInLegacyMode()) {              return 0;          }          ExpandableView shelf = mView.getShelf(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt index f750feda9091..1229cb9b49ac 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt @@ -31,6 +31,7 @@ import kotlinx.coroutines.flow.combine  import kotlinx.coroutines.flow.distinctUntilChanged  import kotlinx.coroutines.flow.flatMapLatest  import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart  /** View-model for the shared notification container, used by both the shade and keyguard spaces */  class SharedNotificationContainerViewModel @@ -45,8 +46,8 @@ constructor(  ) {      private val statesForConstrainedNotifications =          setOf( -            KeyguardState.LOCKSCREEN,              KeyguardState.AOD, +            KeyguardState.LOCKSCREEN,              KeyguardState.DOZING,              KeyguardState.ALTERNATE_BOUNCER,              KeyguardState.PRIMARY_BOUNCER @@ -68,8 +69,17 @@ constructor(      /** If the user is visually on one of the unoccluded lockscreen states. */      val isOnLockscreen: Flow<Boolean> = -        keyguardTransitionInteractor.finishedKeyguardState -            .map { statesForConstrainedNotifications.contains(it) } +        combine( +                keyguardTransitionInteractor.finishedKeyguardState.map { +                    statesForConstrainedNotifications.contains(it) +                }, +                keyguardTransitionInteractor +                    .transitionValue(KeyguardState.LOCKSCREEN) +                    .onStart { emit(0f) } +                    .map { it > 0 } +            ) { constrainedNotificationState, transitioningToOrFromLockscreen -> +                constrainedNotificationState || transitioningToOrFromLockscreen +            }              .distinctUntilChanged()      /** Are we purely on the keyguard without the shade/qs? */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java index 2809cad34143..8129b83a22d9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -17,8 +17,6 @@  package com.android.systemui.statusbar.phone;  import static android.app.StatusBarManager.SESSION_KEYGUARD; -import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION; -import static com.android.systemui.keyguard.WakefulnessLifecycle.UNKNOWN_LAST_WAKE_TIME;  import android.annotation.IntDef;  import android.content.res.Resources; @@ -30,7 +28,6 @@ import android.metrics.LogMaker;  import android.os.Handler;  import android.os.PowerManager;  import android.os.Trace; -import android.view.HapticFeedbackConstants;  import androidx.annotation.Nullable; @@ -47,16 +44,17 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback;  import com.android.keyguard.KeyguardViewController;  import com.android.keyguard.logging.BiometricUnlockLogger;  import com.android.systemui.Dumpable; -import com.android.systemui.res.R;  import com.android.systemui.biometrics.AuthController;  import com.android.systemui.dagger.SysUISingleton;  import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor;  import com.android.systemui.dump.DumpManager;  import com.android.systemui.flags.FeatureFlags;  import com.android.systemui.keyguard.KeyguardViewMediator;  import com.android.systemui.keyguard.WakefulnessLifecycle;  import com.android.systemui.log.SessionTracker;  import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.res.R;  import com.android.systemui.statusbar.NotificationMediaManager;  import com.android.systemui.statusbar.NotificationShadeWindowController;  import com.android.systemui.statusbar.VibratorHelper; @@ -73,12 +71,14 @@ import java.util.Set;  import javax.inject.Inject; +import kotlinx.coroutines.ExperimentalCoroutinesApi; +  /**   * Controller which coordinates all the biometric unlocking actions with the UI.   */ +@ExperimentalCoroutinesApi  @SysUISingleton  public class BiometricUnlockController extends KeyguardUpdateMonitorCallback implements Dumpable { -    private static final long RECENT_POWER_BUTTON_PRESS_THRESHOLD_MS = 400L;      private static final long BIOMETRIC_WAKELOCK_TIMEOUT_MS = 15 * 1000;      private static final String BIOMETRIC_WAKE_LOCK_NAME = "wake-and-unlock:wakelock";      private static final UiEventLogger UI_EVENT_LOGGER = new UiEventLoggerImpl(); @@ -175,6 +175,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp      private final BiometricUnlockLogger mLogger;      private final SystemClock mSystemClock;      private final boolean mOrderUnlockAndWake; +    private final DeviceEntryHapticsInteractor mHapticsInteractor;      private long mLastFpFailureUptimeMillis;      private int mNumConsecutiveFpFailures; @@ -284,7 +285,8 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp              ScreenOffAnimationController screenOffAnimationController,              VibratorHelper vibrator,              SystemClock systemClock, -            FeatureFlags featureFlags +            FeatureFlags featureFlags, +            DeviceEntryHapticsInteractor hapticsInteractor      ) {          mPowerManager = powerManager;          mUpdateMonitor = keyguardUpdateMonitor; @@ -314,6 +316,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp          mFeatureFlags = featureFlags;          mOrderUnlockAndWake = resources.getBoolean(                  com.android.internal.R.bool.config_orderUnlockAndWake); +        mHapticsInteractor = hapticsInteractor;          dumpManager.registerDumpable(this);      } @@ -434,7 +437,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp          if (mode == MODE_WAKE_AND_UNLOCK                  || mode == MODE_WAKE_AND_UNLOCK_PULSING || mode == MODE_UNLOCK_COLLAPSING                  || mode == MODE_WAKE_AND_UNLOCK_FROM_DREAM || mode == MODE_DISMISS_BOUNCER) { -            vibrateSuccess(biometricSourceType); +            mHapticsInteractor.vibrateSuccess();              onBiometricUnlockedWithKeyguardDismissal(biometricSourceType);          }          startWakeAndUnlock(mode); @@ -498,8 +501,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp              case MODE_WAKE_AND_UNLOCK:                  if (mMode == MODE_WAKE_AND_UNLOCK_PULSING) {                      Trace.beginSection("MODE_WAKE_AND_UNLOCK_PULSING"); -                    mMediaManager.updateMediaMetaData(false /* metaDataChanged */, -                            true /* allowEnterAnimation */); +                    mMediaManager.updateMediaMetaData(false /* metaDataChanged */);                  } else if (mMode == MODE_WAKE_AND_UNLOCK){                      Trace.beginSection("MODE_WAKE_AND_UNLOCK");                  } else { @@ -723,7 +725,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp                  && !mUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(                  KeyguardUpdateMonitor.getCurrentUser()))                  || (biometricSourceType == BiometricSourceType.FINGERPRINT)) { -            vibrateError(biometricSourceType); +            mHapticsInteractor.vibrateError();          }          cleanup(); @@ -750,45 +752,6 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp          cleanup();      } -    // these haptics are for device-entry only -    private void vibrateSuccess(BiometricSourceType type) { -        if (mAuthController.isSfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser()) -                && lastWakeupFromPowerButtonWithinHapticThreshold()) { -            mLogger.d("Skip auth success haptic. Power button was recently pressed."); -            return; -        } -        if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { -            mVibratorHelper.performHapticFeedback( -                    mKeyguardViewController.getViewRootImpl().getView(), -                    HapticFeedbackConstants.CONFIRM -            ); -        } else { -            mVibratorHelper.vibrateAuthSuccess( -                    getClass().getSimpleName() + ", type =" + type + "device-entry::success"); -        } -    } - -    private boolean lastWakeupFromPowerButtonWithinHapticThreshold() { -        final boolean lastWakeupFromPowerButton = mWakefulnessLifecycle.getLastWakeReason() -                == PowerManager.WAKE_REASON_POWER_BUTTON; -        return lastWakeupFromPowerButton -                && mWakefulnessLifecycle.getLastWakeTime() != UNKNOWN_LAST_WAKE_TIME -                && mSystemClock.uptimeMillis() - mWakefulnessLifecycle.getLastWakeTime() -                < RECENT_POWER_BUTTON_PRESS_THRESHOLD_MS; -    } - -    private void vibrateError(BiometricSourceType type) { -        if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { -            mVibratorHelper.performHapticFeedback( -                    mKeyguardViewController.getViewRootImpl().getView(), -                    HapticFeedbackConstants.REJECT -            ); -        } else { -            mVibratorHelper.vibrateAuthError( -                    getClass().getSimpleName() + ", type =" + type + "device-entry::error"); -        } -    } -      private void cleanup() {          releaseBiometricWakeLock();      } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java index 1576aa2a4a0c..6f992ac815cb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java @@ -47,6 +47,11 @@ public class DarkIconDispatcherImpl implements SysuiDarkIconDispatcher,      private final ArrayMap<Object, DarkReceiver> mReceivers = new ArrayMap<>();      private int mIconTint = DEFAULT_ICON_TINT; +    private int mContrastTint = DEFAULT_INVERSE_ICON_TINT; + +    private int mDarkModeContrastColor = DEFAULT_ICON_TINT; +    private int mLightModeContrastColor = DEFAULT_INVERSE_ICON_TINT; +      private float mDarkIntensity;      private int mDarkModeIconColorSingleTone;      private int mLightModeIconColorSingleTone; @@ -83,6 +88,7 @@ public class DarkIconDispatcherImpl implements SysuiDarkIconDispatcher,      public void addDarkReceiver(DarkReceiver receiver) {          mReceivers.put(receiver, receiver);          receiver.onDarkChanged(mTintAreas, mDarkIntensity, mIconTint); +        receiver.onDarkChangedWithContrast(mTintAreas, mIconTint, mContrastTint);      }      public void addDarkReceiver(ImageView imageView) { @@ -90,6 +96,7 @@ public class DarkIconDispatcherImpl implements SysuiDarkIconDispatcher,                  ColorStateList.valueOf(getTint(mTintAreas, imageView, mIconTint)));          mReceivers.put(imageView, receiver);          receiver.onDarkChanged(mTintAreas, mDarkIntensity, mIconTint); +        receiver.onDarkChangedWithContrast(mTintAreas, mIconTint, mContrastTint);      }      public void removeDarkReceiver(DarkReceiver object) { @@ -102,6 +109,7 @@ public class DarkIconDispatcherImpl implements SysuiDarkIconDispatcher,      public void applyDark(DarkReceiver object) {          mReceivers.get(object).onDarkChanged(mTintAreas, mDarkIntensity, mIconTint); +        mReceivers.get(object).onDarkChangedWithContrast(mTintAreas, mIconTint, mContrastTint);      }      /** @@ -125,8 +133,13 @@ public class DarkIconDispatcherImpl implements SysuiDarkIconDispatcher,      @Override      public void applyDarkIntensity(float darkIntensity) {          mDarkIntensity = darkIntensity; -        mIconTint = (int) ArgbEvaluator.getInstance().evaluate(darkIntensity, +        ArgbEvaluator evaluator = ArgbEvaluator.getInstance(); + +        mIconTint = (int) evaluator.evaluate(darkIntensity,                  mLightModeIconColorSingleTone, mDarkModeIconColorSingleTone); +        mContrastTint = (int) evaluator +                .evaluate(darkIntensity, mLightModeContrastColor, mDarkModeContrastColor); +          applyIconTint();      } @@ -139,6 +152,7 @@ public class DarkIconDispatcherImpl implements SysuiDarkIconDispatcher,          mDarkChangeFlow.setValue(new DarkChange(mTintAreas, mDarkIntensity, mIconTint));          for (int i = 0; i < mReceivers.size(); i++) {              mReceivers.valueAt(i).onDarkChanged(mTintAreas, mDarkIntensity, mIconTint); +            mReceivers.valueAt(i).onDarkChangedWithContrast(mTintAreas, mIconTint, mContrastTint);          }      } @@ -146,6 +160,16 @@ public class DarkIconDispatcherImpl implements SysuiDarkIconDispatcher,      public void dump(PrintWriter pw, String[] args) {          pw.println("DarkIconDispatcher: ");          pw.println("  mIconTint: 0x" + Integer.toHexString(mIconTint)); +        pw.println("  mContrastTint: 0x" + Integer.toHexString(mContrastTint)); + +        pw.println("  mDarkModeIconColorSingleTone: 0x" +                + Integer.toHexString(mDarkModeIconColorSingleTone)); +        pw.println("  mLightModeIconColorSingleTone: 0x" +                + Integer.toHexString(mLightModeIconColorSingleTone)); + +        pw.println("  mDarkModeContrastColor: 0x" + Integer.toHexString(mDarkModeContrastColor)); +        pw.println("  mLightModeContrastColor: 0x" + Integer.toHexString(mLightModeContrastColor)); +          pw.println("  mDarkIntensity: " + mDarkIntensity + "f");          pw.println("  mTintAreas: " + mTintAreas);      } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java index de9854afdba1..5deb08a75dff 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java @@ -28,10 +28,10 @@ import android.view.ViewGroup;  import android.widget.LinearLayout;  import com.android.internal.statusbar.StatusBarIcon; -import com.android.systemui.res.R;  import com.android.systemui.demomode.DemoMode;  import com.android.systemui.plugins.DarkIconDispatcher;  import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; +import com.android.systemui.res.R;  import com.android.systemui.statusbar.StatusBarIconView;  import com.android.systemui.statusbar.StatusIconDisplayable;  import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger; @@ -54,6 +54,7 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da      private ModernStatusBarWifiView mModernWifiView;      private boolean mDemoMode;      private int mColor; +    private int mContrastColor;      private final MobileIconsViewModel mMobileIconsViewModel;      private final StatusBarLocation mLocation; @@ -68,6 +69,7 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da          mStatusIcons = statusIcons;          mIconSize = iconSize;          mColor = DarkIconDispatcher.DEFAULT_ICON_TINT; +        mContrastColor = DarkIconDispatcher.DEFAULT_INVERSE_ICON_TINT;          mMobileIconsViewModel = mobileIconsViewModel;          mLocation = location; @@ -89,15 +91,17 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da          ((ViewGroup) getParent()).removeView(this);      } -    public void setColor(int color) { +    /** Set the tint colors */ +    public void setColor(int color, int contrastColor) {          mColor = color; +        mContrastColor = contrastColor;          updateColors();      }      private void updateColors() {          for (int i = 0; i < getChildCount(); i++) {              StatusIconDisplayable child = (StatusIconDisplayable) getChildAt(i); -            child.setStaticDrawableColor(mColor); +            child.setStaticDrawableColor(mColor, mContrastColor);              child.setDecorColor(mColor);          }      } @@ -223,7 +227,7 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da          StatusBarIconView v = new StatusBarIconView(getContext(), slot, null, false);          v.setTag(slot);          v.set(icon); -        v.setStaticDrawableColor(mColor); +        v.setStaticDrawableColor(mColor, mContrastColor);          v.setDecorColor(mColor);          addView(v, 0, createLayoutParams());      } @@ -269,7 +273,7 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da          }          mModernWifiView = view; -        mModernWifiView.setStaticDrawableColor(mColor); +        mModernWifiView.setStaticDrawableColor(mColor, mContrastColor);          addView(view, viewIndex, createLayoutParams());      } @@ -305,14 +309,20 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da      }      @Override -    public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) { -        setColor(DarkIconDispatcher.getTint(areas, mStatusIcons, tint)); +    public void onDarkChangedWithContrast(ArrayList<Rect> areas, int tint, int contrastTint) { +        setColor(tint, contrastTint);          if (mModernWifiView != null) { -            mModernWifiView.onDarkChanged(areas, darkIntensity, tint); +            mModernWifiView.onDarkChangedWithContrast(areas, tint, contrastTint);          } +          for (ModernStatusBarMobileView view : mModernMobileViews) { -            view.onDarkChanged(areas, darkIntensity, tint); +            view.onDarkChangedWithContrast(areas, tint, contrastTint);          }      } + +    @Override +    public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) { +        // not needed +    }  } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java index fb5a530e3875..0a03af7d9387 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java @@ -26,14 +26,20 @@ import android.util.MathUtils;  import com.android.app.animation.Interpolators;  import com.android.keyguard.BouncerPanelExpansionCalculator;  import com.android.keyguard.KeyguardStatusView; +import com.android.systemui.log.LogBuffer; +import com.android.systemui.log.core.Logger; +import com.android.systemui.log.dagger.KeyguardClockLog;  import com.android.systemui.res.R;  import com.android.systemui.shade.ShadeViewController;  import com.android.systemui.statusbar.policy.KeyguardUserSwitcherListView; +import javax.inject.Inject; +  /**   * Utility class to calculate the clock position and top padding of notifications on Keyguard.   */  public class KeyguardClockPositionAlgorithm { +    private static final String TAG = "KeyguardClockPositionAlgorithm";      /**       * Margin between the bottom of the status view and the notification shade. @@ -147,6 +153,13 @@ public class KeyguardClockPositionAlgorithm {       */      private boolean mIsClockTopAligned; +    private Logger mLogger; + +    @Inject +    public KeyguardClockPositionAlgorithm(@KeyguardClockLog LogBuffer logBuffer) { +        mLogger = new Logger(logBuffer, TAG); +    } +      /**       * Refreshes the dimension values.       */ @@ -306,6 +319,20 @@ public class KeyguardClockPositionAlgorithm {                  + fullyDarkBurnInOffset                  + shift;          mCurrentBurnInOffsetY = MathUtils.lerp(0, fullyDarkBurnInOffset, darkAmount); +        final String inputs = "panelExpansion: " + panelExpansion + " darkAmount: " + darkAmount; +        final String outputs = "clockY: " + clockY +                + " burnInPreventionOffsetY: " + burnInPreventionOffsetY +                + " fullyDarkBurnInOffset: " + fullyDarkBurnInOffset +                + " shift: " + shift +                + " mOverStretchAmount: " + mOverStretchAmount +                + " mCurrentBurnInOffsetY: " + mCurrentBurnInOffsetY; +        mLogger.i(msg -> { +            return msg.getStr1() + " -> " + msg.getStr2(); +        }, msg -> { +            msg.setStr1(inputs); +            msg.setStr2(outputs); +            return kotlin.Unit.INSTANCE; +        });          return (int) (MathUtils.lerp(clockY, clockYDark, darkAmount) + mOverStretchAmount);      } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java index 58126ae41a1d..8a64a509a0e9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java @@ -436,10 +436,14 @@ public class KeyguardStatusBarView extends RelativeLayout {      private void updateIconsAndTextColors(StatusBarIconController.TintedIconManager iconManager) {          @ColorInt int textColor = Utils.getColorAttrDefaultColor(mContext,                  R.attr.wallpaperTextColor); +        float luminance = Color.luminance(textColor);          @ColorInt int iconColor = Utils.getColorStateListDefaultColor(mContext, -                Color.luminance(textColor) < 0.5 +                    luminance < 0.5                          ? com.android.settingslib.R.color.dark_mode_icon_color_single_tone                          : com.android.settingslib.R.color.light_mode_icon_color_single_tone); +        @ColorInt int contrastColor = luminance < 0.5 +                ? DarkIconDispatcherImpl.DEFAULT_ICON_TINT +                : DarkIconDispatcherImpl.DEFAULT_INVERSE_ICON_TINT;          float intensity = textColor == Color.WHITE ? 0 : 1;          mCarrierLabel.setTextColor(iconColor); @@ -451,7 +455,7 @@ public class KeyguardStatusBarView extends RelativeLayout {          }          if (iconManager != null) { -            iconManager.setTint(iconColor); +            iconManager.setTint(iconColor, contrastColor);          }          mDarkChange.setValue(new DarkChange(mEmptyTintRect, intensity, iconColor)); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java index 5553270ed0ae..f9856b0415e8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java @@ -205,14 +205,13 @@ public class LegacyNotificationIconAreaControllerImpl implements      }      public void setupShelf(NotificationShelfController notificationShelfController) { -        mShelfRefactor.assertDisabled(); +        mShelfRefactor.assertInLegacyMode();          mShelfIcons = notificationShelfController.getShelfIcons();      }      public void setShelfIcons(NotificationIconContainer icons) { -        if (mShelfRefactor.expectEnabled()) { -            mShelfIcons = icons; -        } +        if (mShelfRefactor.isUnexpectedlyInLegacyMode()) return; +        mShelfIcons = icons;      }      public void onDensityOrFontScaleChanged(@NotNull Context context) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java index 92c786fb569f..00fd9fbfffe3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java @@ -263,8 +263,7 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen                  if (result.success) {                      mCached = true;                      mCache = result.bitmap; -                    mMediaManager.updateMediaMetaData( -                            true /* metaDataChanged */, true /* allowEnterAnimation */); +                    mMediaManager.updateMediaMetaData(true /* metaDataChanged */);                  }                  mLoader = null;              } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java index ffeb1a8cb8b6..9ae41951bb74 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java @@ -31,11 +31,11 @@ import android.widget.LinearLayout.LayoutParams;  import androidx.annotation.VisibleForTesting;  import com.android.internal.statusbar.StatusBarIcon; -import com.android.systemui.res.R;  import com.android.systemui.dagger.SysUISingleton;  import com.android.systemui.demomode.DemoModeCommandReceiver;  import com.android.systemui.plugins.DarkIconDispatcher;  import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; +import com.android.systemui.res.R;  import com.android.systemui.statusbar.BaseStatusBarFrameLayout;  import com.android.systemui.statusbar.StatusBarIconView;  import com.android.systemui.statusbar.StatusIconDisplayable; @@ -248,7 +248,10 @@ public interface StatusBarIconController {       *       */      class TintedIconManager extends IconManager { +        // The main tint, used as the foreground in non layer drawables          private int mColor; +        // To be used as the main tint in drawables that wish to have a layer +        private int mForegroundColor;          public TintedIconManager(                  ViewGroup group, @@ -268,26 +271,41 @@ public interface StatusBarIconController {          protected void onIconAdded(int index, String slot, boolean blocked,                  StatusBarIconHolder holder) {              StatusIconDisplayable view = addHolder(index, slot, blocked, holder); -            view.setStaticDrawableColor(mColor); +            view.setStaticDrawableColor(mColor, mForegroundColor);              view.setDecorColor(mColor);          } -        public void setTint(int color) { -            mColor = color; +        /** +         * Most icons are a single layer, and tintColor will be used as the tint in those cases. +         * For icons that have a background, foregroundColor becomes the contrasting tint used +         * for the foreground. +         * +         * @param tintColor the main tint to use for the icons in the group +         * @param foregroundColor used as the main tint for layer-ish drawables where tintColor is +         *                        being used as the background +         */ +        public void setTint(int tintColor, int foregroundColor) { +            mColor = tintColor; +            mForegroundColor = foregroundColor; +              for (int i = 0; i < mGroup.getChildCount(); i++) {                  View child = mGroup.getChildAt(i);                  if (child instanceof StatusIconDisplayable) {                      StatusIconDisplayable icon = (StatusIconDisplayable) child; -                    icon.setStaticDrawableColor(mColor); +                    icon.setStaticDrawableColor(mColor, mForegroundColor);                      icon.setDecorColor(mColor);                  }              } + +            if (mDemoStatusIcons != null) { +                mDemoStatusIcons.setColor(tintColor, foregroundColor); +            }          }          @Override          protected DemoStatusIcons createDemoStatusIcons() {              DemoStatusIcons icons = super.createDemoStatusIcons(); -            icons.setColor(mColor); +            icons.setColor(mColor, mForegroundColor);              return icons;          } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 3adf3385e3cc..400ac7b415b5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -961,7 +961,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb                      SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__SHOWN);          }          if (isShowing) { -            mMediaManager.updateMediaMetaData(false, animate && !isOccluded); +            mMediaManager.updateMediaMetaData(false);          }          mNotificationShadeWindowController.setKeyguardOccluded(isOccluded); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java index 2d14f6b3c508..57a8e6fe0d91 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java @@ -221,7 +221,7 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu      @Override      public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) { -        mMediaManager.updateMediaMetaData(metaDataChanged, allowEnterAnimation); +        mMediaManager.updateMediaMetaData(metaDataChanged);      }      @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt new file mode 100644 index 000000000000..85fd2afed9ec --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.phone + +import android.app.Dialog +import android.content.Context +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.os.Bundle +import android.view.Gravity +import android.view.WindowManager +import com.android.systemui.res.R + +/** A dialog shown as a bottom sheet. */ +open class SystemUIBottomSheetDialog( +    context: Context, +    theme: Int = R.style.Theme_SystemUI_Dialog, +) : Dialog(context, theme) { +    override fun onCreate(savedInstanceState: Bundle?) { +        super.onCreate(savedInstanceState) + +        window?.apply { +            setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL) +            addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS) + +            setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) +            setGravity(Gravity.BOTTOM) +            val edgeToEdgeHorizontally = +                context.resources.getBoolean(R.bool.config_edgeToEdgeBottomSheetDialog) +            if (edgeToEdgeHorizontally) { +                decorView.setPadding(0, 0, 0, 0) +                setLayout( +                    WindowManager.LayoutParams.MATCH_PARENT, +                    WindowManager.LayoutParams.WRAP_CONTENT +                ) + +                val lp = attributes +                lp.fitInsetsSides = 0 +                lp.horizontalMargin = 0f +                attributes = lp +            } +        } +        setCanceledOnTouchOutside(true) +    } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt index 2c15e27b8148..de37170b1f7d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt @@ -34,6 +34,8 @@ import com.android.systemui.statusbar.policy.KeyguardStateController  import com.android.systemui.util.TraceUtils  import com.android.systemui.util.settings.GlobalSettings  import javax.inject.Inject +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags  /**   * When to show the keyguard (AOD) view. This should be once the light reveal scrim is barely @@ -65,7 +67,8 @@ class UnlockedScreenOffAnimationController @Inject constructor(      private val notifShadeWindowControllerLazy: dagger.Lazy<NotificationShadeWindowController>,      private val interactionJankMonitor: InteractionJankMonitor,      private val powerManager: PowerManager, -    private val handler: Handler = Handler() +    private val handler: Handler = Handler(), +    private val featureFlags: FeatureFlags,  ) : WakefulnessLifecycle.Observer, ScreenOffAnimation {      private lateinit var centralSurfaces: CentralSurfaces      private lateinit var shadeViewController: ShadeViewController @@ -285,7 +288,11 @@ class UnlockedScreenOffAnimationController @Inject constructor(                  // up, with unpredictable consequences.                  if (!powerManager.isInteractive(Display.DEFAULT_DISPLAY) &&                          shouldAnimateInKeyguard) { -                    aodUiAnimationPlaying = true +                    if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) { +                        // Tracking this state should no longer be relevant, as the isInteractive +                        // check covers it +                        aodUiAnimationPlaying = true +                    }                      // Show AOD. That'll cause the KeyguardVisibilityHelper to call                      // #animateInKeyguard. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt index aacdc6323179..3522b9a13989 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt @@ -190,6 +190,24 @@ constructor(      fun logOnSimStateChanged() {          buffer.log(TAG, LogLevel.INFO, "onSimStateChanged")      } + +    fun logPrioritizedNetworkAvailable(netId: Int) { +        buffer.log( +            TAG, +            LogLevel.INFO, +            { int1 = netId }, +            { "Found prioritized network (nedId=$int1)" }, +        ) +    } + +    fun logPrioritizedNetworkLost(netId: Int) { +        buffer.log( +            TAG, +            LogLevel.INFO, +            { int1 = netId }, +            { "Lost prioritized network (nedId=$int1)" }, +        ) +    }  }  private const val TAG = "MobileInputLog" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt index a89b1b2db6b3..679426db99c0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt @@ -131,6 +131,12 @@ interface MobileConnectionRepository {       */      val isAllowedDuringAirplaneMode: StateFlow<Boolean> +    /** +     * True if this network has NET_CAPABILITIY_PRIORITIZE_LATENCY, and can be considered to be a +     * network slice +     */ +    val hasPrioritizedNetworkCapabilities: StateFlow<Boolean> +      companion object {          /** The default number of levels to use for [numberOfLevels]. */          const val DEFAULT_NUM_LEVELS = 4 diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt index c576b822da15..caa9d1a8f0c3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt @@ -191,6 +191,8 @@ class DemoMobileConnectionRepository(      override val isAllowedDuringAirplaneMode = MutableStateFlow(false) +    override val hasPrioritizedNetworkCapabilities = MutableStateFlow(false) +      /**       * Process a new demo mobile event. Note that [resolvedNetworkType] must be passed in separately       * from the event, due to the requirement to reverse the mobile mappings lookup in the top-level @@ -225,6 +227,7 @@ class DemoMobileConnectionRepository(          _resolvedNetworkType.value = resolvedNetworkType          isAllowedDuringAirplaneMode.value = false +        hasPrioritizedNetworkCapabilities.value = event.slice      }      fun processCarrierMergedEvent(event: FakeWifiEventModel.CarrierMerged) { @@ -250,6 +253,7 @@ class DemoMobileConnectionRepository(          _isGsm.value = false          _carrierNetworkChangeActive.value = false          isAllowedDuringAirplaneMode.value = true +        hasPrioritizedNetworkCapabilities.value = false      }      companion object { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt index d4ddb856eecf..4cd877eb1a14 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt @@ -76,6 +76,7 @@ constructor(          val carrierNetworkChange = getString("carriernetworkchange") == "show"          val roaming = getString("roam") == "show"          val name = getString("networkname") ?: "demo mode" +        val slice = getString("slice").toBoolean()          return Mobile(              level = level, @@ -87,6 +88,7 @@ constructor(              carrierNetworkChange = carrierNetworkChange,              roaming = roaming,              name = name, +            slice = slice,          )      }  } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt index 8b03f71a2729..0aa95f8821cc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt @@ -36,6 +36,7 @@ sealed interface FakeNetworkEventModel {          val carrierNetworkChange: Boolean,          val roaming: Boolean,          val name: String, +        val slice: Boolean = false,      ) : FakeNetworkEventModel      data class MobileDisabled( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt index 28be3be28928..27edd1ec0caa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt @@ -174,6 +174,13 @@ class CarrierMergedConnectionRepository(       */      override val isAllowedDuringAirplaneMode = MutableStateFlow(true).asStateFlow() +    /** +     * It's not currently considered possible that a carrier merged network can have these +     * prioritized capabilities. If we need to track them, we can add the same check as is in +     * [MobileConnectionRepositoryImpl]. +     */ +    override val hasPrioritizedNetworkCapabilities = MutableStateFlow(false).asStateFlow() +      override val dataEnabled: StateFlow<Boolean> = wifiRepository.isWifiEnabled      companion object { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt index ee11c06ef3f5..6b6192186e0f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt @@ -309,6 +309,15 @@ class FullMobileConnectionRepository(                  activeRepo.value.isAllowedDuringAirplaneMode.value,              ) +    override val hasPrioritizedNetworkCapabilities = +        activeRepo +            .flatMapLatest { it.hasPrioritizedNetworkCapabilities } +            .stateIn( +                scope, +                SharingStarted.WhileSubscribed(), +                activeRepo.value.hasPrioritizedNetworkCapabilities.value, +            ) +      class Factory      @Inject      constructor( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt index dc50990d002a..760dd7eba3cf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt @@ -21,6 +21,11 @@ import android.content.BroadcastReceiver  import android.content.Context  import android.content.Intent  import android.content.IntentFilter +import android.net.ConnectivityManager +import android.net.ConnectivityManager.NetworkCallback +import android.net.Network +import android.net.NetworkCapabilities +import android.net.NetworkRequest  import android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN  import android.telephony.CellSignalStrengthCdma  import android.telephony.ServiceState @@ -91,6 +96,7 @@ class MobileConnectionRepositoryImpl(      subscriptionModel: StateFlow<SubscriptionModel?>,      defaultNetworkName: NetworkNameModel,      networkNameSeparator: String, +    connectivityManager: ConnectivityManager,      private val telephonyManager: TelephonyManager,      systemUiCarrierConfig: SystemUiCarrierConfig,      broadcastDispatcher: BroadcastDispatcher, @@ -374,11 +380,50 @@ class MobileConnectionRepositoryImpl(      /** Typical mobile connections aren't available during airplane mode. */      override val isAllowedDuringAirplaneMode = MutableStateFlow(false).asStateFlow() +    /** +     * Currently, a network with NET_CAPABILITY_PRIORITIZE_LATENCY is the only type of network that +     * we consider to be a "network slice". _PRIORITIZE_BANDWIDTH may be added in the future. Any of +     * these capabilities that are used here must also be represented in the +     * self_certified_network_capabilities.xml config file +     */ +    @SuppressLint("WrongConstant") +    private val networkSliceRequest = +        NetworkRequest.Builder() +            .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY) +            .setSubscriptionIds(setOf(subId)) +            .build() + +    @SuppressLint("MissingPermission") +    override val hasPrioritizedNetworkCapabilities: StateFlow<Boolean> = +        conflatedCallbackFlow { +                // Our network callback listens only for this.subId && net_cap_prioritize_latency +                // therefore our state is a simple mapping of whether or not that network exists +                val callback = +                    object : NetworkCallback() { +                        override fun onAvailable(network: Network) { +                            logger.logPrioritizedNetworkAvailable(network.netId) +                            trySend(true) +                        } + +                        override fun onLost(network: Network) { +                            logger.logPrioritizedNetworkLost(network.netId) +                            trySend(false) +                        } +                    } + +                connectivityManager.registerNetworkCallback(networkSliceRequest, callback) + +                awaitClose { connectivityManager.unregisterNetworkCallback(callback) } +            } +            .flowOn(bgDispatcher) +            .stateIn(scope, SharingStarted.WhileSubscribed(), false) +      class Factory      @Inject      constructor(          private val context: Context,          private val broadcastDispatcher: BroadcastDispatcher, +        private val connectivityManager: ConnectivityManager,          private val telephonyManager: TelephonyManager,          private val logger: MobileInputLogger,          private val carrierConfigRepository: CarrierConfigRepository, @@ -399,6 +444,7 @@ class MobileConnectionRepositoryImpl(                  subscriptionModel,                  defaultNetworkName,                  networkNameSeparator, +                connectivityManager,                  telephonyManager.createForSubscriptionId(subId),                  carrierConfigRepository.getOrCreateConfigForSubId(subId),                  broadcastDispatcher, @@ -421,11 +467,17 @@ private fun Intent.carrierId(): Int =   */  sealed interface CallbackEvent {      data class OnCarrierNetworkChange(val active: Boolean) : CallbackEvent +      data class OnDataActivity(val direction: Int) : CallbackEvent +      data class OnDataConnectionStateChanged(val dataState: Int) : CallbackEvent +      data class OnDataEnabledChanged(val enabled: Boolean) : CallbackEvent +      data class OnDisplayInfoChanged(val telephonyDisplayInfo: TelephonyDisplayInfo) : CallbackEvent +      data class OnServiceStateChanged(val serviceState: ServiceState) : CallbackEvent +      data class OnSignalStrengthChanged(val signalStrength: SignalStrength) : CallbackEvent  } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt index 4bf297cd088c..fe49c0791370 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt @@ -76,6 +76,9 @@ interface MobileIconInteractor {      /** Observable for RAT type (network type) indicator */      val networkTypeIconGroup: StateFlow<NetworkTypeIconModel> +    /** Whether or not to show the slice attribution */ +    val showSliceAttribution: StateFlow<Boolean> +      /**       * Provider name for this network connection. The name can be one of 3 values:       * 1. The default network name, if one is configured @@ -238,6 +241,9 @@ class MobileIconInteractorImpl(                  DefaultIcon(defaultMobileIconGroup.value),              ) +    override val showSliceAttribution: StateFlow<Boolean> = +        connectionRepository.hasPrioritizedNetworkCapabilities +      override val isRoaming: StateFlow<Boolean> =          combine(                  connectionRepository.carrierNetworkChangeActive, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt index f0470ca0a1ad..b93e44378280 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt @@ -16,11 +16,13 @@  package com.android.systemui.statusbar.pipeline.mobile.ui.binder +import android.annotation.ColorInt  import android.content.res.ColorStateList  import android.view.View  import android.view.View.GONE  import android.view.View.VISIBLE  import android.view.ViewGroup +import android.widget.FrameLayout  import android.widget.ImageView  import android.widget.Space  import androidx.core.view.isVisible @@ -28,10 +30,11 @@ import androidx.lifecycle.Lifecycle  import androidx.lifecycle.lifecycleScope  import androidx.lifecycle.repeatOnLifecycle  import com.android.settingslib.graph.SignalDrawable -import com.android.systemui.res.R  import com.android.systemui.common.ui.binder.ContentDescriptionViewBinder  import com.android.systemui.common.ui.binder.IconViewBinder  import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.plugins.DarkIconDispatcher +import com.android.systemui.res.R  import com.android.systemui.statusbar.StatusBarIconView  import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN  import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger @@ -43,6 +46,11 @@ import kotlinx.coroutines.flow.MutableStateFlow  import kotlinx.coroutines.flow.distinctUntilChanged  import kotlinx.coroutines.launch +private data class Colors( +    @ColorInt val tint: Int, +    @ColorInt val contrast: Int, +) +  object MobileIconBinder {      /** Binds the view to the view-model, continuing to update the former based on the latter */      @JvmStatic @@ -57,6 +65,7 @@ object MobileIconBinder {          val activityIn = view.requireViewById<ImageView>(R.id.mobile_in)          val activityOut = view.requireViewById<ImageView>(R.id.mobile_out)          val networkTypeView = view.requireViewById<ImageView>(R.id.mobile_type) +        val networkTypeContainer = view.requireViewById<FrameLayout>(R.id.mobile_type_container)          val iconView = view.requireViewById<ImageView>(R.id.mobile_signal)          val mobileDrawable = SignalDrawable(view.context).also { iconView.setImageDrawable(it) }          val roamingView = view.requireViewById<ImageView>(R.id.mobile_roaming) @@ -70,7 +79,13 @@ object MobileIconBinder {          @StatusBarIconView.VisibleState          val visibilityState: MutableStateFlow<Int> = MutableStateFlow(initialVisibilityState) -        val iconTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor) +        val iconTint: MutableStateFlow<Colors> = +            MutableStateFlow( +                Colors( +                    tint = DarkIconDispatcher.DEFAULT_ICON_TINT, +                    contrast = DarkIconDispatcher.DEFAULT_INVERSE_ICON_TINT +                ) +            )          val decorTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor)          var isCollecting = false @@ -139,7 +154,26 @@ object MobileIconBinder {                                  dataTypeId,                              )                              dataTypeId?.let { IconViewBinder.bind(dataTypeId, networkTypeView) } -                            networkTypeView.visibility = if (dataTypeId != null) VISIBLE else GONE +                            networkTypeContainer.visibility = +                                if (dataTypeId != null) VISIBLE else GONE +                        } +                    } + +                    // Set the network type background +                    launch { +                        viewModel.networkTypeBackground.collect { background -> +                            networkTypeContainer.setBackgroundResource(background?.res ?: 0) + +                            // Tint will invert when this bit changes +                            if (background?.res != null) { +                                networkTypeContainer.backgroundTintList = +                                    ColorStateList.valueOf(iconTint.value.tint) +                                networkTypeView.imageTintList = +                                    ColorStateList.valueOf(iconTint.value.contrast) +                            } else { +                                networkTypeView.imageTintList = +                                    ColorStateList.valueOf(iconTint.value.tint) +                            }                          }                      } @@ -164,14 +198,24 @@ object MobileIconBinder {                      // Set the tint                      launch { -                        iconTint.collect { tint -> -                            val tintList = ColorStateList.valueOf(tint) -                            iconView.imageTintList = tintList -                            networkTypeView.imageTintList = tintList -                            roamingView.imageTintList = tintList -                            activityIn.imageTintList = tintList -                            activityOut.imageTintList = tintList -                            dotView.setDecorColor(tint) +                        iconTint.collect { colors -> +                            val tint = ColorStateList.valueOf(colors.tint) +                            val contrast = ColorStateList.valueOf(colors.contrast) + +                            iconView.imageTintList = tint + +                            // If the bg is visible, tint it and use the contrast for the fg +                            if (viewModel.networkTypeBackground.value != null) { +                                networkTypeContainer.backgroundTintList = tint +                                networkTypeView.imageTintList = contrast +                            } else { +                                networkTypeView.imageTintList = tint +                            } + +                            roamingView.imageTintList = tint +                            activityIn.imageTintList = tint +                            activityOut.imageTintList = tint +                            dotView.setDecorColor(colors.tint)                          }                      } @@ -196,8 +240,8 @@ object MobileIconBinder {                  visibilityState.value = state              } -            override fun onIconTintChanged(newTint: Int) { -                iconTint.value = newTint +            override fun onIconTintChanged(newTint: Int, contrastTint: Int) { +                iconTint.value = Colors(tint = newTint, contrast = contrastTint)              }              override fun onDecorTintChanged(newTint: Int) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt index dfabeea96082..d88c9efdac76 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt @@ -19,7 +19,10 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel  import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH  import com.android.systemui.common.shared.model.ContentDescription  import com.android.systemui.common.shared.model.Icon +import com.android.systemui.flags.FeatureFlagsClassic +import com.android.systemui.flags.Flags.NEW_NETWORK_SLICE_UI  import com.android.systemui.log.table.logDiffsForTable +import com.android.systemui.res.R  import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor  import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor  import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor @@ -47,6 +50,8 @@ interface MobileIconViewModelCommon {      val roaming: Flow<Boolean>      /** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */      val networkTypeIcon: Flow<Icon.Resource?> +    /** The slice attribution. Drawn as a background layer */ +    val networkTypeBackground: StateFlow<Icon.Resource?>      val activityInVisible: Flow<Boolean>      val activityOutVisible: Flow<Boolean>      val activityContainerVisible: Flow<Boolean> @@ -67,12 +72,12 @@ interface MobileIconViewModelCommon {   */  @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")  @OptIn(ExperimentalCoroutinesApi::class) -class MobileIconViewModel -constructor( +class MobileIconViewModel(      override val subscriptionId: Int,      iconInteractor: MobileIconInteractor,      airplaneModeInteractor: AirplaneModeInteractor,      constants: ConnectivityConstants, +    flags: FeatureFlagsClassic,      scope: CoroutineScope,  ) : MobileIconViewModelCommon {      override val isVisible: StateFlow<Boolean> = @@ -152,6 +157,20 @@ constructor(              .distinctUntilChanged()              .stateIn(scope, SharingStarted.WhileSubscribed(), null) +    override val networkTypeBackground = +        if (!flags.isEnabled(NEW_NETWORK_SLICE_UI)) { +                flowOf(null) +            } else { +                iconInteractor.showSliceAttribution.map { +                    if (it) { +                        Icon.Resource(R.drawable.mobile_network_type_background, null) +                    } else { +                        null +                    } +                } +            } +            .stateIn(scope, SharingStarted.WhileSubscribed(), null) +      override val roaming: StateFlow<Boolean> =          iconInteractor.isRoaming              .logDiffsForTable( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt index 0f55910d8779..be843ba8699f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel  import androidx.annotation.VisibleForTesting  import com.android.systemui.dagger.SysUISingleton  import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.flags.FeatureFlagsClassic  import com.android.systemui.statusbar.phone.StatusBarLocation  import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor  import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor @@ -54,6 +55,7 @@ constructor(      private val interactor: MobileIconsInteractor,      private val airplaneModeInteractor: AirplaneModeInteractor,      private val constants: ConnectivityConstants, +    private val flags: FeatureFlagsClassic,      @Application private val scope: CoroutineScope,  ) {      @VisibleForTesting val mobileIconSubIdCache = mutableMapOf<Int, MobileIconViewModel>() @@ -113,6 +115,7 @@ constructor(                      interactor.getMobileConnectionInteractorForSubId(subId),                      airplaneModeInteractor,                      constants, +                    flags,                      scope,                  )                  .also { mobileIconSubIdCache[subId] = it } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt index 81f8683411ca..790596ea5520 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt @@ -32,8 +32,8 @@ interface ModernStatusBarViewBinding {      /** Notifies that the visibility state has changed. */      fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int) -    /** Notifies that the icon tint has been updated. */ -    fun onIconTintChanged(newTint: Int) +    /** Notifies that the icon tint has been updated. Includes a contrast for layered drawables */ +    fun onIconTintChanged(newTint: Int, contrastTint: Int)      /** Notifies that the decor tint has been updated (used only for the dot). */      fun onDecorTintChanged(newTint: Int) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt index fe69d818dedb..3b87bed2e0ef 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt @@ -20,8 +20,8 @@ import android.content.Context  import android.graphics.Rect  import android.util.AttributeSet  import android.view.Gravity -import com.android.systemui.res.R  import com.android.systemui.plugins.DarkIconDispatcher +import com.android.systemui.res.R  import com.android.systemui.statusbar.BaseStatusBarFrameLayout  import com.android.systemui.statusbar.StatusBarIconView  import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT @@ -51,13 +51,23 @@ open class ModernStatusBarView(context: Context, attrs: AttributeSet?) :      override fun getSlot() = slot      override fun onDarkChanged(areas: ArrayList<Rect>?, darkIntensity: Float, tint: Int) { +        // nop +    } + +    override fun onDarkChangedWithContrast(areas: ArrayList<Rect>, tint: Int, contrastTint: Int) {          val newTint = DarkIconDispatcher.getTint(areas, this, tint) -        binding.onIconTintChanged(newTint) +        val contrast = DarkIconDispatcher.getInverseTint(areas, this, contrastTint) + +        binding.onIconTintChanged(newTint, contrast)          binding.onDecorTintChanged(newTint)      }      override fun setStaticDrawableColor(color: Int) { -        binding.onIconTintChanged(color) +        // nop +    } + +    override fun setStaticDrawableColor(color: Int, foregroundColor: Int) { +        binding.onIconTintChanged(color, foregroundColor)      }      override fun setDecorColor(color: Int) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt index e9e52a2397e1..1670dd39ba24 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt @@ -104,7 +104,7 @@ constructor(                  val callback =                      object : WifiPickerTracker.WifiPickerTrackerCallback {                          override fun onWifiEntriesChanged() { -                            val connectedEntry = wifiPickerTracker?.connectedWifiEntry +                            val connectedEntry = wifiPickerTracker.mergedOrPrimaryConnection                              logOnWifiEntriesChanged(connectedEntry)                              val secondaryNetworks = @@ -217,6 +217,21 @@ constructor(              .stateIn(scope, SharingStarted.Eagerly, emptyList())      /** +     * [WifiPickerTracker.getConnectedWifiEntry] stores a [MergedCarrierEntry] separately from the +     * [WifiEntry] for the primary connection. Therefore, we have to prefer the carrier-merged entry +     * if it exists, falling back on the connected entry if null +     */ +    private val WifiPickerTracker?.mergedOrPrimaryConnection: WifiEntry? +        get() { +            val mergedEntry: MergedCarrierEntry? = this?.mergedCarrierEntry +            return if (mergedEntry != null && mergedEntry.isDefaultNetwork) { +                mergedEntry +            } else { +                this?.connectedWifiEntry +            } +        } + +    /**       * Converts WifiTrackerLib's [WifiEntry] into our internal model only if the entry is the       * primary network. Returns an inactive network if it's not primary.       */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt index a9ac51d7c472..60055279055c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt @@ -23,9 +23,9 @@ import android.widget.ImageView  import androidx.core.view.isVisible  import androidx.lifecycle.Lifecycle  import androidx.lifecycle.repeatOnLifecycle -import com.android.systemui.res.R  import com.android.systemui.common.ui.binder.IconViewBinder  import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.res.R  import com.android.systemui.statusbar.StatusBarIconView  import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN  import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding @@ -165,7 +165,7 @@ object WifiViewBinder {                  visibilityState.value = state              } -            override fun onIconTintChanged(newTint: Int) { +            override fun onIconTintChanged(newTint: Int, contrastTint: Int /* unused */) {                  iconTint.value = newTint              } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java index 62e238164514..b614b6d0547d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java @@ -38,6 +38,7 @@ import com.android.settingslib.drawable.CircleFramedDrawable;  import com.android.systemui.res.R;  import com.android.systemui.animation.Expandable;  import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.flags.FeatureFlags;  import com.android.systemui.plugins.FalsingManager;  import com.android.systemui.plugins.statusbar.StatusBarStateController;  import com.android.systemui.qs.user.UserSwitchDialogController; @@ -148,6 +149,7 @@ public class KeyguardQsUserSwitchController extends ViewController<FrameLayout>              DozeParameters dozeParameters,              ScreenOffAnimationController screenOffAnimationController,              UserSwitchDialogController userSwitchDialogController, +            FeatureFlags featureFlags,              UiEventLogger uiEventLogger) {          super(view);          if (DEBUG) Log.d(TAG, "New KeyguardQsUserSwitchController"); @@ -160,7 +162,8 @@ public class KeyguardQsUserSwitchController extends ViewController<FrameLayout>          mStatusBarStateController = statusBarStateController;          mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView,                  keyguardStateController, dozeParameters, -                screenOffAnimationController,  /* animateYPos= */ false, /* logBuffer= */ null); +                screenOffAnimationController,  /* animateYPos= */ false, +                featureFlags, /* logBuffer= */ null);          mUserSwitchDialogController = userSwitchDialogController;          mUiEventLogger = uiEventLogger;      } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java index bb074ac33ddb..dfe26865f978 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java @@ -38,10 +38,11 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback;  import com.android.keyguard.KeyguardVisibilityHelper;  import com.android.keyguard.dagger.KeyguardUserSwitcherScope;  import com.android.settingslib.drawable.CircleFramedDrawable; -import com.android.systemui.res.R;  import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.flags.FeatureFlags;  import com.android.systemui.keyguard.ScreenLifecycle;  import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.res.R;  import com.android.systemui.statusbar.SysuiStatusBarStateController;  import com.android.systemui.statusbar.notification.AnimatableProperty;  import com.android.systemui.statusbar.notification.PropertyAnimator; @@ -160,6 +161,7 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS              KeyguardStateController keyguardStateController,              SysuiStatusBarStateController statusBarStateController,              KeyguardUpdateMonitor keyguardUpdateMonitor, +            FeatureFlags featureFlags,              DozeParameters dozeParameters,              ScreenOffAnimationController screenOffAnimationController) {          super(keyguardUserSwitcherView); @@ -174,7 +176,8 @@ public class KeyguardUserSwitcherController extends ViewController<KeyguardUserS                  mUserSwitcherController, this);          mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView,                  keyguardStateController, dozeParameters, -                screenOffAnimationController, /* animateYPos= */ false, /* logBuffer= */ null); +                screenOffAnimationController, /* animateYPos= */ false, +                featureFlags, /* logBuffer= */ null);          mBackground = new KeyguardUserSwitcherScrim(context);      } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java index 53fed3d2438f..ceed81a182aa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java @@ -305,7 +305,8 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene                              && editTextRootWindowInsets.isVisible(WindowInsets.Type.ime());                      if (!mEntry.mRemoteEditImeVisible && !mEditText.mShowImeOnInputConnection) {                          // Pass null to ensure all inputs are cleared for this entry b/227115380 -                        mController.removeRemoteInput(mEntry, null); +                            mController.removeRemoteInput(mEntry, null, +                                    /* reason= */"RemoteInputView$WindowInsetAnimation#onEnd");                      }                  }              } @@ -426,7 +427,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene      @VisibleForTesting      void onDefocus(boolean animate, boolean logClose, @Nullable Runnable doAfterDefocus) { -        mController.removeRemoteInput(mEntry, mToken); +        mController.removeRemoteInput(mEntry, mToken, /* reason= */"RemoteInputView#onDefocus");          mEntry.remoteInputText = mEditText.getText();          // During removal, we get reattached and lose focus. Not hiding in that @@ -536,7 +537,8 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene          if (mEntry.getRow().isChangingPosition() || isTemporarilyDetached()) {              return;          } -        mController.removeRemoteInput(mEntry, mToken); +        mController.removeRemoteInput(mEntry, mToken, +                /* reason= */"RemoteInputView#onDetachedFromWindow");          mController.removeSpinning(mEntry.getKey(), mToken);      } @@ -655,7 +657,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene          mEditText.setText(mEntry.remoteInputText);          mEditText.setSelection(mEditText.length());          mEditText.requestFocus(); -        mController.addRemoteInput(mEntry, mToken); +        mController.addRemoteInput(mEntry, mToken, "RemoteInputView#focus");          setAttachment(mEntry.remoteInputAttachment);          updateSendButton(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt index a50fd6f86e09..6c0d43394074 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt @@ -255,7 +255,8 @@ class RemoteInputViewControllerImpl @Inject constructor(          entry.lastRemoteInputSent = SystemClock.elapsedRealtime()          entry.mRemoteEditImeAnimatingAway = true          remoteInputController.addSpinning(entry.key, view.mToken) -        remoteInputController.removeRemoteInput(entry, view.mToken) +        remoteInputController.removeRemoteInput(entry, view.mToken, +               /* reason= */ "RemoteInputViewController#sendRemoteInput")          remoteInputController.remoteInputSent(entry)          entry.setHasSentReply() diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java index 9b06a37e681f..d56672564f6f 100644 --- a/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java +++ b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java @@ -191,7 +191,7 @@ public class AsyncSensorManager extends SensorManager      }      @Override -    protected boolean initDataInjectionImpl(boolean enable) { +    protected boolean initDataInjectionImpl(boolean enable, @DataInjectionMode int mode) {          throw new UnsupportedOperationException("not implemented");      } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt index 6afa5256523c..4d8768f5e9e0 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt @@ -25,9 +25,11 @@ import com.android.systemui.SysuiTestCase  import com.android.systemui.broadcast.BroadcastDispatcher  import com.android.systemui.flags.Flags  import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository -import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository  import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep  import com.android.systemui.log.LogBuffer  import com.android.systemui.plugins.ClockAnimations  import com.android.systemui.plugins.ClockController @@ -47,8 +49,8 @@ import com.android.systemui.util.mockito.mock  import java.util.TimeZone  import java.util.concurrent.Executor  import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow  import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.TestScope  import kotlinx.coroutines.yield  import org.junit.Assert.assertEquals  import org.junit.Before @@ -90,10 +92,10 @@ class ClockEventControllerTest : SysuiTestCase() {      @Mock private lateinit var smallClockEvents: ClockFaceEvents      @Mock private lateinit var largeClockEvents: ClockFaceEvents      @Mock private lateinit var parentView: View -    @Mock private lateinit var transitionRepository: KeyguardTransitionRepository      private lateinit var repository: FakeKeyguardRepository      @Mock private lateinit var smallLogBuffer: LogBuffer      @Mock private lateinit var largeLogBuffer: LogBuffer +    @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor      private lateinit var underTest: ClockEventController      @Before @@ -125,17 +127,13 @@ class ClockEventControllerTest : SysuiTestCase() {          withDeps.featureFlags.apply {              set(Flags.REGION_SAMPLING, false) -            set(Flags.DOZING_MIGRATION_1, false) +            set(Flags.MIGRATE_KEYGUARD_STATUS_VIEW, false)              set(Flags.FACE_AUTH_REFACTOR, false)          }          underTest =              ClockEventController(                  withDeps.keyguardInteractor, -                KeyguardTransitionInteractorFactory.create( -                        scope = TestScope().backgroundScope, -                        featureFlags = withDeps.featureFlags, -                    ) -                    .keyguardTransitionInteractor, +                keyguardTransitionInteractor,                  broadcastDispatcher,                  batteryController,                  keyguardUpdateMonitor, @@ -316,6 +314,68 @@ class ClockEventControllerTest : SysuiTestCase() {          }      @Test +    fun listenForDozeAmountTransition_updatesClockDozeAmount() = +        runBlocking(IMMEDIATE) { +            val transitionStep = MutableStateFlow(TransitionStep()) +            whenever(keyguardTransitionInteractor.dozeAmountTransition).thenReturn(transitionStep) + +            val job = underTest.listenForDozeAmountTransition(this) +            transitionStep.value = +                TransitionStep( +                    from = KeyguardState.LOCKSCREEN, +                    to = KeyguardState.AOD, +                    value = 0.4f +                ) +            yield() + +            verify(animations, times(2)).doze(0.4f) + +            job.cancel() +        } + +    @Test +    fun listenForTransitionToAodFromGone_updatesClockDozeAmountToOne() = +        runBlocking(IMMEDIATE) { +            val transitionStep = MutableStateFlow(TransitionStep()) +            whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.AOD)) +                .thenReturn(transitionStep) + +            val job = underTest.listenForAnyStateToAodTransition(this) +            transitionStep.value = +                TransitionStep( +                    from = KeyguardState.GONE, +                    to = KeyguardState.AOD, +                    transitionState = TransitionState.STARTED, +                ) +            yield() + +            verify(animations, times(2)).doze(1f) + +            job.cancel() +        } + +    @Test +    fun listenForTransitionToAodFromLockscreen_neverUpdatesClockDozeAmount() = +        runBlocking(IMMEDIATE) { +            val transitionStep = MutableStateFlow(TransitionStep()) +            whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.AOD)) +                .thenReturn(transitionStep) + +            val job = underTest.listenForAnyStateToAodTransition(this) +            transitionStep.value = +                TransitionStep( +                    from = KeyguardState.LOCKSCREEN, +                    to = KeyguardState.AOD, +                    transitionState = TransitionState.STARTED, +                ) +            yield() + +            verify(animations, never()).doze(1f) + +            job.cancel() +        } + +    @Test      fun unregisterListeners_validate() =          runBlocking(IMMEDIATE) {              underTest.unregisterListeners() diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java index 3b8e02f7455a..22c75d85d4a1 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java @@ -16,6 +16,8 @@  package com.android.keyguard; +import static kotlinx.coroutines.flow.FlowKt.emptyFlow; +  import static org.mockito.Mockito.atLeast;  import static org.mockito.Mockito.verify;  import static org.mockito.Mockito.when; @@ -31,6 +33,7 @@ import com.android.systemui.dump.DumpManager;  import com.android.systemui.flags.FeatureFlags;  import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;  import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory; +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;  import com.android.systemui.power.data.repository.FakePowerRepository;  import com.android.systemui.power.domain.interactor.PowerInteractorFactory;  import com.android.systemui.statusbar.notification.AnimatableProperty; @@ -60,6 +63,7 @@ public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase {      @Mock protected FeatureFlags mFeatureFlags;      @Mock protected InteractionJankMonitor mInteractionJankMonitor;      @Mock protected ViewTreeObserver mViewTreeObserver; +    @Mock protected KeyguardTransitionInteractor mKeyguardTransitionInteractor;      @Mock protected DumpManager mDumpManager;      protected FakeKeyguardRepository mFakeKeyguardRepository;      protected FakePowerRepository mFakePowerRepository; @@ -90,6 +94,7 @@ public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase {                  mFeatureFlags,                  mInteractionJankMonitor,                  deps.getKeyguardInteractor(), +                mKeyguardTransitionInteractor,                  mDumpManager,                  PowerInteractorFactory.create(                          mFakePowerRepository @@ -105,8 +110,8 @@ public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase {                  };          when(mKeyguardStatusView.getViewTreeObserver()).thenReturn(mViewTreeObserver); -          when(mKeyguardClockSwitchController.getView()).thenReturn(mKeyguardClockSwitch); +        when(mKeyguardTransitionInteractor.getGoneToAodTransition()).thenReturn(emptyFlow());      }      protected void givenViewAttached() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java index 3da72618fb60..5346db1aa067 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java @@ -19,6 +19,9 @@ package com.android.systemui.accessibility.floatingmenu;  import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;  import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR; +import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG; +import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault; +  import static com.google.common.truth.Truth.assertThat;  import static org.mockito.ArgumentMatchers.any; @@ -87,6 +90,7 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase {      @Before      public void setUp() throws Exception { +        setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);          MockitoAnnotations.initMocks(this);          mContextWrapper = new ContextWrapper(mContext) {              @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java index fd258e38a00f..3b2ea0f5ce86 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java @@ -16,6 +16,9 @@  package com.android.systemui.accessibility.floatingmenu; +import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG; +import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault; +  import static org.mockito.Mockito.mock;  import static org.mockito.Mockito.spy;  import static org.mockito.Mockito.verify; @@ -56,6 +59,7 @@ public class DismissAnimationControllerTest extends SysuiTestCase {      @Before      public void setUp() throws Exception { +        setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);          final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);          final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,                  mock(SecureSettings.class)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java index 3a8bcd0ec2f0..76a3153e648a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java @@ -16,6 +16,9 @@  package com.android.systemui.accessibility.floatingmenu; +import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG; +import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault; +  import static com.google.common.truth.Truth.assertThat;  import static org.mockito.Mockito.any; @@ -75,6 +78,7 @@ public class MenuAnimationControllerTest extends SysuiTestCase {      @Before      public void setUp() throws Exception { +        setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);          final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);          final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,                  stubWindowManager); @@ -96,6 +100,7 @@ public class MenuAnimationControllerTest extends SysuiTestCase {          Prefs.putBoolean(mContext, Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED,                  mLastIsMoveToTucked);          mEndListenerCaptor.getAllValues().clear(); +        mMenuAnimationController.mPositionAnimations.values().forEach(DynamicAnimation::cancel);      }      @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java index 9b819479ec70..83bcd8d50407 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java @@ -16,6 +16,9 @@  package com.android.systemui.accessibility.floatingmenu; +import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG; +import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault; +  import static com.google.common.truth.Truth.assertThat;  import android.content.res.Resources; @@ -43,6 +46,7 @@ public class MenuEduTooltipViewTest extends SysuiTestCase {      @Before      public void setUp() throws Exception { +        setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);          final WindowManager windowManager = mContext.getSystemService(WindowManager.class);          mMenuViewAppearance = new MenuViewAppearance(mContext, windowManager);          mMenuEduTooltipView = new MenuEduTooltipView(mContext, mMenuViewAppearance); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java index 5764839d160d..e01f1b76df79 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java @@ -19,6 +19,9 @@ package com.android.systemui.accessibility.floatingmenu;  import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS;  import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS; +import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG; +import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault; +  import static com.google.common.truth.Truth.assertThat;  import static org.mockito.Mockito.doReturn; @@ -72,6 +75,7 @@ public class MenuItemAccessibilityDelegateTest extends SysuiTestCase {      @Before      public void setUp() { +        setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);          final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);          final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,                  stubWindowManager); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java index 98be49f9d493..a88ee108141b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java @@ -18,6 +18,9 @@ package com.android.systemui.accessibility.floatingmenu;  import static android.view.View.OVER_SCROLL_NEVER; +import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG; +import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault; +  import static com.google.common.truth.Truth.assertThat;  import static org.mockito.ArgumentMatchers.anyFloat; @@ -33,6 +36,7 @@ import android.view.MotionEvent;  import android.view.WindowManager;  import android.view.accessibility.AccessibilityManager; +import androidx.dynamicanimation.animation.DynamicAnimation;  import androidx.recyclerview.widget.RecyclerView;  import androidx.test.filters.SmallTest; @@ -79,6 +83,7 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase {      @Before      public void setUp() throws Exception { +        setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);          final WindowManager windowManager = mContext.getSystemService(WindowManager.class);          final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,                  mock(SecureSettings.class)); @@ -213,5 +218,6 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase {      @After      public void tearDown() {          mMotionEventHelper.recycleEvents(); +        mMenuAnimationController.mPositionAnimations.values().forEach(DynamicAnimation::cancel);      }  } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java index 31824ecaa432..41e5c209e344 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java @@ -19,6 +19,9 @@ package com.android.systemui.accessibility.floatingmenu;  import static android.view.WindowInsets.Type.displayCutout;  import static android.view.WindowInsets.Type.systemBars; +import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG; +import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault; +  import static org.mockito.ArgumentMatchers.any;  import static org.mockito.Mockito.doAnswer;  import static org.mockito.Mockito.verify; @@ -73,6 +76,7 @@ public class MenuViewLayerControllerTest extends SysuiTestCase {      @Before      public void setUp() throws Exception { +        setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);          final WindowManager wm = mContext.getSystemService(WindowManager.class);          doAnswer(invocation -> wm.getMaximumWindowMetrics()).when(                  mWindowManager).getMaximumWindowMetrics(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java index 5bb5e01675be..b0776c9a5b58 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java @@ -22,7 +22,9 @@ import static android.view.WindowInsets.Type.displayCutout;  import static android.view.WindowInsets.Type.ime;  import static android.view.WindowInsets.Type.systemBars; +import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG;  import static com.android.systemui.accessibility.floatingmenu.MenuViewLayer.LayerIndex; +import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;  import static com.google.common.truth.Truth.assertThat; @@ -50,6 +52,7 @@ import android.view.WindowManager;  import android.view.WindowMetrics;  import android.view.accessibility.AccessibilityManager; +import androidx.dynamicanimation.animation.DynamicAnimation;  import androidx.test.filters.SmallTest;  import com.android.systemui.SysuiTestCase; @@ -110,6 +113,7 @@ public class MenuViewLayerTest extends SysuiTestCase {      @Before      public void setUp() throws Exception { +        setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);          final Rect mDisplayBounds = new Rect();          mDisplayBounds.set(/* left= */ 0, /* top= */ 0, DISPLAY_WINDOW_WIDTH,                  DISPLAY_WINDOW_HEIGHT); @@ -145,6 +149,7 @@ public class MenuViewLayerTest extends SysuiTestCase {                  UserHandle.USER_CURRENT);          mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ false); +        mMenuAnimationController.mPositionAnimations.values().forEach(DynamicAnimation::cancel);          mMenuViewLayer.onDetachedFromWindow();      } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java index 5cd0fd0bb42d..ac2bfaf8eddf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java @@ -18,6 +18,9 @@ package com.android.systemui.accessibility.floatingmenu;  import static android.app.UiModeManager.MODE_NIGHT_YES; +import static com.android.systemui.Flags.FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG; +import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault; +  import static com.google.common.truth.Truth.assertThat;  import static org.mockito.Mockito.mock; @@ -67,6 +70,7 @@ public class MenuViewTest extends SysuiTestCase {      @Before      public void setUp() throws Exception { +        setFlagDefault(mSetFlagsRule, FLAG_FLOATING_MENU_OVERLAPS_NAV_BARS_FLAG);          mUiModeManager = mContext.getSystemService(UiModeManager.class);          mNightMode = mUiModeManager.getNightMode();          mUiModeManager.setNightMode(MODE_NIGHT_YES); diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt deleted file mode 100644 index 712eef13421b..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - *      http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.biometrics.domain.interactor - -import android.hardware.biometrics.SensorLocationInternal -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository -import com.android.systemui.biometrics.shared.model.FingerprintSensorType -import com.android.systemui.biometrics.shared.model.SensorStrength -import com.android.systemui.coroutines.collectLastValue -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.test.StandardTestDispatcher -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.runTest -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.JUnit4 -import org.mockito.junit.MockitoJUnit - -@SmallTest -@RunWith(JUnit4::class) -class SideFpsOverlayInteractorTest : SysuiTestCase() { - -    @JvmField @Rule var mockitoRule = MockitoJUnit.rule() -    private lateinit var testScope: TestScope - -    private val fingerprintRepository = FakeFingerprintPropertyRepository() - -    private lateinit var interactor: SideFpsOverlayInteractor - -    @Before -    fun setup() { -        testScope = TestScope(StandardTestDispatcher()) -        interactor = SideFpsOverlayInteractorImpl(fingerprintRepository) -    } - -    @Test -    fun testOverlayOffsetUpdates() = -        testScope.runTest { -            fingerprintRepository.setProperties( -                sensorId = 1, -                strength = SensorStrength.STRONG, -                sensorType = FingerprintSensorType.REAR, -                sensorLocations = -                    mapOf( -                        "" to -                            SensorLocationInternal( -                                "" /* displayId */, -                                540 /* sensorLocationX */, -                                1636 /* sensorLocationY */, -                                130 /* sensorRadius */ -                            ), -                        "display_id_1" to -                            SensorLocationInternal( -                                "display_id_1" /* displayId */, -                                100 /* sensorLocationX */, -                                300 /* sensorLocationY */, -                                20 /* sensorRadius */ -                            ) -                    ) -            ) - -            val displayId by collectLastValue(interactor.displayId) -            val offsets by collectLastValue(interactor.overlayOffsets) - -            // Assert offsets of empty displayId. -            assertThat(displayId).isEqualTo("") -            assertThat(offsets?.displayId).isEqualTo("") -            assertThat(offsets?.sensorLocationX).isEqualTo(540) -            assertThat(offsets?.sensorLocationY).isEqualTo(1636) -            assertThat(offsets?.sensorRadius).isEqualTo(130) - -            // Offsets should be updated correctly. -            interactor.onDisplayChanged("display_id_1") -            assertThat(displayId).isEqualTo("display_id_1") -            assertThat(offsets?.displayId).isEqualTo("display_id_1") -            assertThat(offsets?.sensorLocationX).isEqualTo(100) -            assertThat(offsets?.sensorLocationY).isEqualTo(300) -            assertThat(offsets?.sensorRadius).isEqualTo(20) - -            // Should return default offset when the displayId is invalid. -            interactor.onDisplayChanged("invalid_display_id") -            assertThat(displayId).isEqualTo("invalid_display_id") -            assertThat(offsets?.displayId).isEqualTo(SensorLocationInternal.DEFAULT.displayId) -            assertThat(offsets?.sensorLocationX) -                .isEqualTo(SensorLocationInternal.DEFAULT.sensorLocationX) -            assertThat(offsets?.sensorLocationY) -                .isEqualTo(SensorLocationInternal.DEFAULT.sensorLocationY) -            assertThat(offsets?.sensorRadius).isEqualTo(SensorLocationInternal.DEFAULT.sensorRadius) -        } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt new file mode 100644 index 000000000000..99501c426e0c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt @@ -0,0 +1,410 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics.domain.interactor + +import android.graphics.Rect +import android.hardware.biometrics.SensorLocationInternal +import android.hardware.display.DisplayManagerGlobal +import android.view.Display +import android.view.DisplayInfo +import android.view.WindowInsets +import android.view.WindowManager +import android.view.WindowMetrics +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository +import com.android.systemui.biometrics.shared.model.DisplayRotation +import com.android.systemui.biometrics.shared.model.DisplayRotation.ROTATION_0 +import com.android.systemui.biometrics.shared.model.DisplayRotation.ROTATION_180 +import com.android.systemui.biometrics.shared.model.DisplayRotation.ROTATION_270 +import com.android.systemui.biometrics.shared.model.DisplayRotation.ROTATION_90 +import com.android.systemui.biometrics.shared.model.FingerprintSensorType +import com.android.systemui.biometrics.shared.model.SensorStrength +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.FakeFeatureFlagsClassic +import com.android.systemui.flags.Flags.REST_TO_UNLOCK +import com.android.systemui.res.R +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.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mock +import org.mockito.Mockito.mock +import org.mockito.Mockito.spy +import org.mockito.junit.MockitoJUnit + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(JUnit4::class) +class SideFpsSensorInteractorTest : SysuiTestCase() { + +    @JvmField @Rule var mockitoRule = MockitoJUnit.rule() +    private lateinit var testScope: TestScope + +    private val fingerprintRepository = FakeFingerprintPropertyRepository() + +    private lateinit var underTest: SideFpsSensorInteractor + +    @Mock private lateinit var windowManager: WindowManager +    @Mock private lateinit var displayStateInteractor: DisplayStateInteractor + +    private val contextDisplayInfo = DisplayInfo() +    private val displayChangeEvent = MutableStateFlow(0) +    private val currentRotation = MutableStateFlow(ROTATION_0) + +    @Before +    fun setup() { +        testScope = TestScope(StandardTestDispatcher()) +        mContext = spy(mContext) + +        val displayManager = mock(DisplayManagerGlobal::class.java) +        val resources = mContext.resources +        whenever(mContext.display) +            .thenReturn(Display(displayManager, 1, contextDisplayInfo, resources)) +        whenever(displayStateInteractor.displayChanges).thenReturn(displayChangeEvent) +        whenever(displayStateInteractor.currentRotation).thenReturn(currentRotation) + +        contextDisplayInfo.uniqueId = "current-display" + +        underTest = +            SideFpsSensorInteractor( +                mContext, +                fingerprintRepository, +                windowManager, +                displayStateInteractor, +                FakeFeatureFlagsClassic().apply { set(REST_TO_UNLOCK, true) } +            ) +    } + +    @Test +    fun testSfpsSensorAvailable() = +        testScope.runTest { +            val isAvailable by collectLastValue(underTest.isAvailable) + +            setupFingerprint(FingerprintSensorType.POWER_BUTTON) +            assertThat(isAvailable).isTrue() + +            setupFingerprint(FingerprintSensorType.HOME_BUTTON) +            assertThat(isAvailable).isFalse() + +            setupFingerprint(FingerprintSensorType.REAR) +            assertThat(isAvailable).isFalse() + +            setupFingerprint(FingerprintSensorType.UDFPS_OPTICAL) +            assertThat(isAvailable).isFalse() + +            setupFingerprint(FingerprintSensorType.UDFPS_ULTRASONIC) +            assertThat(isAvailable).isFalse() + +            setupFingerprint(FingerprintSensorType.UNKNOWN) +            assertThat(isAvailable).isFalse() +        } + +    @Test +    fun authenticationDurationIsAvailableWhenSFPSSensorIsAvailable() = +        testScope.runTest { +            assertThat(collectLastValue(underTest.authenticationDuration)()) +                .isEqualTo(context.resources.getInteger(R.integer.config_restToUnlockDuration)) +        } + +    @Test +    fun verticalSensorLocationIsAdjustedToScreenPositionForRotation0() = +        testScope.runTest { +            /* +            (0,0)                (1000,0) +               ------------------ +              |  ^^^^^           |  (1000, 200) +              |   status bar    || <--- start of sensor at Rotation_0 +              |                 || <--- end of sensor +              |                  |  (1000, 300) +              |                  | +               ------------------ (1000, 800) +             */ +            setupFPLocationAndDisplaySize( +                width = 1000, +                height = 800, +                rotation = ROTATION_0, +                sensorLocationY = 200, +                sensorWidth = 100, +            ) + +            val sensorLocation by collectLastValue(underTest.sensorLocation) +            assertThat(sensorLocation!!.left).isEqualTo(1000) +            assertThat(sensorLocation!!.top).isEqualTo(200) +            assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(true) +            assertThat(sensorLocation!!.width).isEqualTo(100) +        } + +    @Test +    fun verticalSensorLocationIsAdjustedToScreenPositionForRotation270() = +        testScope.runTest { +            /* +            (800,0)                     (800, 1000) +                  --------------------- +                 |                     | (600, 1000) +                 |   <                || <--- end of sensor at Rotation_270 +                 |   < status bar     || <--- start of sensor +                 |   <                 | (500, 1000) +                 |   <                 | +            (0,0) --------------------- +             */ +            setupFPLocationAndDisplaySize( +                width = 800, +                height = 1000, +                rotation = ROTATION_270, +                sensorLocationY = 200, +                sensorWidth = 100, +            ) + +            val sensorLocation by collectLastValue(underTest.sensorLocation) +            assertThat(sensorLocation!!.left).isEqualTo(500) +            assertThat(sensorLocation!!.top).isEqualTo(1000) +            assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(true) +            assertThat(sensorLocation!!.width).isEqualTo(100) +        } + +    @Test +    fun verticalSensorLocationIsAdjustedToScreenPositionForRotation90() = +        testScope.runTest { +            /* +                                            (0,0) +                       --------------------- +                      |                     | (200, 0) +                      |               >    || <--- end of sensor at Rotation_270 +                      |    status bar >    || <--- start of sensor +                      |               >     | (300, 0) +                      |               >     | +            (800,1000) --------------------- +             */ +            setupFPLocationAndDisplaySize( +                width = 800, +                height = 1000, +                rotation = ROTATION_90, +                sensorLocationY = 200, +                sensorWidth = 100, +            ) + +            val sensorLocation by collectLastValue(underTest.sensorLocation) +            assertThat(sensorLocation!!.left).isEqualTo(200) +            assertThat(sensorLocation!!.top).isEqualTo(0) +            assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(true) +            assertThat(sensorLocation!!.width).isEqualTo(100) +        } + +    @Test +    fun verticalSensorLocationIsAdjustedToScreenPositionForRotation180() = +        testScope.runTest { +            /* + +            (1000,800) --------------------- +                      |                     | (0, 600) +                      |                    || <--- end of sensor at Rotation_270 +                      |    status bar      || <--- start of sensor +                      |   \/\/\/\/\/\/\/    | (0, 500) +                      |                     | +                       ---------------------  (0,0) +             */ +            setupFPLocationAndDisplaySize( +                width = 1000, +                height = 800, +                rotation = ROTATION_180, +                sensorLocationY = 200, +                sensorWidth = 100, +            ) + +            val sensorLocation by collectLastValue(underTest.sensorLocation) +            assertThat(sensorLocation!!.left).isEqualTo(0) +            assertThat(sensorLocation!!.top).isEqualTo(500) +        } + +    @Test +    fun horizontalSensorLocationIsAdjustedToScreenPositionForRotation0() = +        testScope.runTest { +            /* +            (0,0)   (500,0)   (600,0)   (1000,0) +               ____________===_________ +              |                        | +              |  ^^^^^                 | +              |   status bar           | +              |                        | +               ------------------------ (1000, 800) +             */ +            setupFPLocationAndDisplaySize( +                width = 1000, +                height = 800, +                rotation = ROTATION_0, +                sensorLocationX = 500, +                sensorWidth = 100, +            ) + +            val sensorLocation by collectLastValue(underTest.sensorLocation) +            assertThat(sensorLocation!!.left).isEqualTo(500) +            assertThat(sensorLocation!!.top).isEqualTo(0) +            assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(false) +            assertThat(sensorLocation!!.width).isEqualTo(100) +        } + +    @Test +    fun horizontalSensorLocationIsAdjustedToScreenPositionForRotation90() = +        testScope.runTest { +            /* +                  (0,1000)   (0,500)   (0,400)   (0,0) +                        ____________===_________ +                       |                        | +                       |               >        | +                       |   status bar  >        | +                       |               >        | +            (800, 1000) ------------------------ +             */ +            setupFPLocationAndDisplaySize( +                width = 800, +                height = 1000, +                rotation = ROTATION_90, +                sensorLocationX = 500, +                sensorWidth = 100, +            ) + +            val sensorLocation by collectLastValue(underTest.sensorLocation) +            assertThat(sensorLocation!!.left).isEqualTo(0) +            assertThat(sensorLocation!!.top).isEqualTo(400) +            assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(false) +            assertThat(sensorLocation!!.width).isEqualTo(100) +        } + +    @Test +    fun horizontalSensorLocationIsAdjustedToScreenPositionForRotation180() = +        testScope.runTest { +            /* +            (1000, 800)  (500, 800)   (400, 800)   (0,800) +                       ____________===_________ +                      |                        | +                      |                        | +                      |   status bar           | +                      |  \/ \/ \/ \/ \/ \/ \/  | +                       ------------------------ (0,0) +             */ +            setupFPLocationAndDisplaySize( +                width = 1000, +                height = 800, +                rotation = ROTATION_180, +                sensorLocationX = 500, +                sensorWidth = 100, +            ) + +            val sensorLocation by collectLastValue(underTest.sensorLocation) +            assertThat(sensorLocation!!.left).isEqualTo(400) +            assertThat(sensorLocation!!.top).isEqualTo(800) +            assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(false) +            assertThat(sensorLocation!!.width).isEqualTo(100) +        } + +    @Test +    fun horizontalSensorLocationIsAdjustedToScreenPositionForRotation270() = +        testScope.runTest { +            /* +                        (800, 500)  (800, 600) +            (800, 0) ____________===_________ (800,1000) +                    |  <                     | +                    |  <                     | +                    |  < status bar          | +                    |  <                     | +               (0,0) ------------------------ +             */ +            setupFPLocationAndDisplaySize( +                width = 800, +                height = 1000, +                rotation = ROTATION_270, +                sensorLocationX = 500, +                sensorWidth = 100, +            ) + +            val sensorLocation by collectLastValue(underTest.sensorLocation) +            assertThat(sensorLocation!!.left).isEqualTo(800) +            assertThat(sensorLocation!!.top).isEqualTo(500) +            assertThat(sensorLocation!!.isSensorVerticalInDefaultOrientation).isEqualTo(false) +            assertThat(sensorLocation!!.width).isEqualTo(100) +        } + +    private suspend fun TestScope.setupFPLocationAndDisplaySize( +        width: Int, +        height: Int, +        sensorLocationX: Int = 0, +        sensorLocationY: Int = 0, +        rotation: DisplayRotation, +        sensorWidth: Int +    ) { +        overrideResource(R.integer.config_sfpsSensorWidth, sensorWidth) +        setupDisplayDimensions(width, height) +        currentRotation.value = rotation +        setupFingerprint(x = sensorLocationX, y = sensorLocationY, displayId = "expanded_display") +    } + +    private fun setupDisplayDimensions(displayWidth: Int, displayHeight: Int) { +        whenever(windowManager.maximumWindowMetrics) +            .thenReturn( +                WindowMetrics( +                    Rect(0, 0, displayWidth, displayHeight), +                    mock(WindowInsets::class.java) +                ) +            ) +    } + +    private suspend fun TestScope.setupFingerprint( +        fingerprintSensorType: FingerprintSensorType = FingerprintSensorType.POWER_BUTTON, +        x: Int = 0, +        y: Int = 0, +        displayId: String = "display_id_1" +    ) { +        contextDisplayInfo.uniqueId = displayId +        fingerprintRepository.setProperties( +            sensorId = 1, +            strength = SensorStrength.STRONG, +            sensorType = fingerprintSensorType, +            sensorLocations = +                mapOf( +                    "someOtherDisplayId" to +                        SensorLocationInternal( +                            "someOtherDisplayId", +                            x + 100, +                            y + 100, +                            0, +                        ), +                    displayId to +                        SensorLocationInternal( +                            displayId, +                            x, +                            y, +                            0, +                        ) +                ) +        ) +        // Emit a display change event, this happens whenever any display related change happens, +        // rotation, active display changing etc, display switched off/on. +        displayChangeEvent.emit(1) + +        runCurrent() +    } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt index 3df9cbb29e4a..91409a376556 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt @@ -11,11 +11,14 @@ import androidx.test.ext.junit.runners.AndroidJUnit4  import androidx.test.filters.SmallTest  import com.android.systemui.SysuiTestCase  import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.communal.data.model.CommunalWidgetMetadata +import com.android.systemui.communal.shared.CommunalContentSize  import com.android.systemui.coroutines.collectLastValue  import com.android.systemui.flags.FeatureFlags  import com.android.systemui.flags.Flags  import com.android.systemui.log.LogBuffer  import com.android.systemui.log.core.FakeLogBuffer +import com.android.systemui.res.R  import com.android.systemui.settings.UserTracker  import com.android.systemui.util.mockito.any  import com.android.systemui.util.mockito.kotlinArgumentCaptor @@ -59,9 +62,12 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {      @Mock private lateinit var stopwatchProviderInfo: AppWidgetProviderInfo +    private lateinit var communalRepository: FakeCommunalRepository +      private lateinit var logBuffer: LogBuffer      private val testDispatcher = StandardTestDispatcher() +      private val testScope = TestScope(testDispatcher)      @Before @@ -71,6 +77,14 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {          logBuffer = FakeLogBuffer.Factory.create()          featureFlagEnabled(true) +        communalRepository = FakeCommunalRepository() +        communalRepository.setIsCommunalEnabled(true) + +        overrideResource( +            R.array.config_communalWidgetAllowlist, +            arrayOf(componentName1, componentName2) +        ) +          whenever(stopwatchProviderInfo.loadLabel(any())).thenReturn("Stopwatch")          whenever(userTracker.userHandle).thenReturn(userHandle)      } @@ -219,11 +233,36 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {              Mockito.verify(appWidgetHost).stopListening()          } +    @Test +    fun getCommunalWidgetAllowList_onInit() { +        testScope.runTest { +            val repository = initCommunalWidgetRepository() +            val communalWidgetAllowlist = repository.communalWidgetAllowlist +            assertThat( +                    listOf( +                        CommunalWidgetMetadata( +                            componentName = componentName1, +                            priority = 2, +                            sizes = listOf(CommunalContentSize.HALF) +                        ), +                        CommunalWidgetMetadata( +                            componentName = componentName2, +                            priority = 1, +                            sizes = listOf(CommunalContentSize.HALF) +                        ) +                    ) +                ) +                .containsExactly(*communalWidgetAllowlist.toTypedArray()) +        } +    } +      private fun initCommunalWidgetRepository(): CommunalWidgetRepositoryImpl {          return CommunalWidgetRepositoryImpl( +            context,              appWidgetManager,              appWidgetHost,              broadcastDispatcher, +            communalRepository,              packageManager,              userManager,              userTracker, @@ -282,4 +321,9 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {      private fun installedProviders(providers: List<AppWidgetProviderInfo>) {          whenever(appWidgetManager.installedProviders).thenReturn(providers)      } + +    companion object { +        const val componentName1 = "component name 1" +        const val componentName2 = "component name 2" +    }  } diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt new file mode 100644 index 000000000000..9b8e581d1ba4 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.deviceentry.data.repository + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.keyguard.logging.BiometricUnlockLogger +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository +import com.android.systemui.biometrics.shared.model.FingerprintSensorType +import com.android.systemui.biometrics.shared.model.SensorStrength +import com.android.systemui.classifier.FalsingCollector +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor +import com.android.systemui.keyevent.data.repository.FakeKeyEventRepository +import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor +import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.power.data.repository.FakePowerRepository +import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.power.shared.model.WakeSleepReason +import com.android.systemui.power.shared.model.WakefulnessState +import com.android.systemui.statusbar.phone.ScreenOffAnimationController +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.mock + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class DeviceEntryHapticsInteractorTest : SysuiTestCase() { + +    private lateinit var repository: DeviceEntryHapticsRepository +    private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository +    private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository +    private lateinit var keyEventRepository: FakeKeyEventRepository +    private lateinit var powerRepository: FakePowerRepository +    private lateinit var systemClock: FakeSystemClock +    private lateinit var underTest: DeviceEntryHapticsInteractor + +    @Before +    fun setUp() { +        repository = DeviceEntryHapticsRepositoryImpl() +        fingerprintPropertyRepository = FakeFingerprintPropertyRepository() +        biometricSettingsRepository = FakeBiometricSettingsRepository() +        keyEventRepository = FakeKeyEventRepository() +        powerRepository = FakePowerRepository() +        systemClock = FakeSystemClock() +        underTest = +            DeviceEntryHapticsInteractor( +                repository = repository, +                fingerprintPropertyRepository = fingerprintPropertyRepository, +                biometricSettingsRepository = biometricSettingsRepository, +                keyEventInteractor = KeyEventInteractor(keyEventRepository), +                powerInteractor = +                    PowerInteractor( +                        powerRepository, +                        mock(FalsingCollector::class.java), +                        mock(ScreenOffAnimationController::class.java), +                        mock(StatusBarStateController::class.java), +                    ), +                systemClock = systemClock, +                logger = mock(BiometricUnlockLogger::class.java), +            ) +    } + +    @Test +    fun nonPowerButtonFPS_vibrateSuccess() = runTest { +        val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) +        setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC) +        underTest.vibrateSuccess() +        assertThat(playSuccessHaptic).isTrue() +    } + +    @Test +    fun powerButtonFPS_vibrateSuccess() = runTest { +        val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) +        setPowerButtonFingerprintProperty() +        setFingerprintEnrolled() +        keyEventRepository.setPowerButtonDown(false) + +        // It's been 10 seconds since the last power button wakeup +        setAwakeFromPowerButton() +        runCurrent() +        systemClock.setUptimeMillis(systemClock.uptimeMillis() + 10000) + +        underTest.vibrateSuccess() +        assertThat(playSuccessHaptic).isTrue() +    } + +    @Test +    fun powerButtonFPS_powerDown_doNotVibrateSuccess() = runTest { +        val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) +        setPowerButtonFingerprintProperty() +        setFingerprintEnrolled() +        keyEventRepository.setPowerButtonDown(true) // power button is currently DOWN + +        // It's been 10 seconds since the last power button wakeup +        setAwakeFromPowerButton() +        runCurrent() +        systemClock.setUptimeMillis(systemClock.uptimeMillis() + 10000) + +        underTest.vibrateSuccess() +        assertThat(playSuccessHaptic).isFalse() +    } + +    @Test +    fun powerButtonFPS_powerButtonRecentlyPressed_doNotVibrateSuccess() = runTest { +        val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) +        setPowerButtonFingerprintProperty() +        setFingerprintEnrolled() +        keyEventRepository.setPowerButtonDown(false) + +        // It's only been 50ms since the last power button wakeup +        setAwakeFromPowerButton() +        runCurrent() +        systemClock.setUptimeMillis(systemClock.uptimeMillis() + 50) + +        underTest.vibrateSuccess() +        assertThat(playSuccessHaptic).isFalse() +    } + +    @Test +    fun nonPowerButtonFPS_vibrateError() = runTest { +        val playErrorHaptic by collectLastValue(underTest.playErrorHaptic) +        setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC) +        underTest.vibrateError() +        assertThat(playErrorHaptic).isTrue() +    } + +    @Test +    fun powerButtonFPS_vibrateError() = runTest { +        val playErrorHaptic by collectLastValue(underTest.playErrorHaptic) +        setPowerButtonFingerprintProperty() +        setFingerprintEnrolled() +        underTest.vibrateError() +        assertThat(playErrorHaptic).isTrue() +    } + +    @Test +    fun powerButtonFPS_powerDown_doNotVibrateError() = runTest { +        val playErrorHaptic by collectLastValue(underTest.playErrorHaptic) +        setPowerButtonFingerprintProperty() +        setFingerprintEnrolled() +        keyEventRepository.setPowerButtonDown(true) +        underTest.vibrateError() +        assertThat(playErrorHaptic).isFalse() +    } + +    private fun setFingerprintSensorType(fingerprintSensorType: FingerprintSensorType) { +        fingerprintPropertyRepository.setProperties( +            sensorId = 0, +            strength = SensorStrength.STRONG, +            sensorType = fingerprintSensorType, +            sensorLocations = mapOf(), +        ) +    } + +    private fun setPowerButtonFingerprintProperty() { +        setFingerprintSensorType(FingerprintSensorType.POWER_BUTTON) +    } + +    private fun setFingerprintEnrolled() { +        biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) +    } + +    private fun setAwakeFromPowerButton() { +        powerRepository.updateWakefulness( +            WakefulnessState.AWAKE, +            WakeSleepReason.POWER_BUTTON, +            WakeSleepReason.POWER_BUTTON, +            powerButtonLaunchGestureTriggered = false, +        ) +    } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt index 37c70d8f25e0..2bd2bff80951 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt @@ -43,10 +43,10 @@ class FakeFeatureFlagsTest : SysuiTestCase() {      fun accessingUnspecifiedFlags_throwsException() {          val flags: FeatureFlags = FakeFeatureFlags()          try { -            assertThat(flags.isEnabled(Flags.TEAMFOOD)).isFalse() +            assertThat(flags.isEnabled(Flags.NULL_FLAG)).isFalse()              fail("Expected an exception when accessing an unspecified flag.")          } catch (ex: IllegalStateException) { -            assertThat(ex.message).contains("UNKNOWN(teamfood)") +            assertThat(ex.message).contains("UNKNOWN(null_flag)")          }          try {              assertThat(flags.isEnabled(unreleasedFlag)).isFalse() diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt index c12a581fb2c1..f51745b9f17c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt @@ -22,6 +22,7 @@ import android.content.pm.PackageManager.NameNotFoundException  import android.content.res.Resources  import android.content.res.Resources.NotFoundException  import android.test.suitebuilder.annotation.SmallTest +import com.android.systemui.FakeFeatureFlagsImpl  import com.android.systemui.SysuiTestCase  import com.android.systemui.util.mockito.any  import com.android.systemui.util.mockito.eq @@ -65,6 +66,7 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() {      private lateinit var broadcastReceiver: BroadcastReceiver      private lateinit var clearCacheAction: Consumer<String>      private val serverFlagReader = ServerFlagReaderFake() +    private val fakeGantryFlags = FakeFeatureFlagsImpl()      private val teamfoodableFlagA = UnreleasedFlag(name = "a", namespace = "test", teamfood = true)      private val teamfoodableFlagB = ReleasedFlag(name = "b", namespace = "test", teamfood = true) @@ -72,7 +74,7 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() {      @Before      fun setup() {          MockitoAnnotations.initMocks(this) -        flagMap.put(Flags.TEAMFOOD.name, Flags.TEAMFOOD) +        fakeGantryFlags.setFlag("com.android.systemui.sysui_teamfood", false)          flagMap.put(teamfoodableFlagA.name, teamfoodableFlagA)          flagMap.put(teamfoodableFlagB.name, teamfoodableFlagB)          mFeatureFlagsClassicDebug = @@ -84,6 +86,7 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() {                  resources,                  serverFlagReader,                  flagMap, +                fakeGantryFlags,                  restarter              )          mFeatureFlagsClassicDebug.init() @@ -121,8 +124,6 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() {      @Test      fun teamFoodFlag_False() { -        whenever(flagManager.readFlagValue<Boolean>(eq(Flags.TEAMFOOD.name), any())) -            .thenReturn(false)          assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagA)).isFalse()          assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagB)).isTrue() @@ -133,8 +134,7 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() {      @Test      fun teamFoodFlag_True() { -        whenever(flagManager.readFlagValue<Boolean>(eq(Flags.TEAMFOOD.name), any())) -            .thenReturn(true) +        fakeGantryFlags.setFlag("com.android.systemui.sysui_teamfood", true)          assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagA)).isTrue()          assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagB)).isTrue() @@ -149,8 +149,7 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() {              .thenReturn(true)          whenever(flagManager.readFlagValue<Boolean>(eq(teamfoodableFlagB.name), any()))              .thenReturn(false) -        whenever(flagManager.readFlagValue<Boolean>(eq(Flags.TEAMFOOD.name), any())) -            .thenReturn(true) +        fakeGantryFlags.setFlag("com.android.systemui.sysui_teamfood", true)          assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagA)).isTrue()          assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagB)).isFalse() diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt new file mode 100644 index 000000000000..bb6786ade21a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.flags + +import android.platform.test.flag.junit.SetFlagsRule + +fun SetFlagsRule.setFlagDefault(flagName: String) { +    if (getFlagDefault(flagName)) { +        enableFlags(flagName) +    } else { +        disableFlags(flagName) +    } +} + +// NOTE: This code uses reflection to gain access to private members of aconfig generated +//  classes (in the same way SetFlagsRule does internally) because this is the only way to get +//  at the underlying information and read the current value of the flag. +// If aconfig had flag constants with accessible default values, this would be unnecessary. +private fun getFlagDefault(name: String): Boolean { +    val flagPackage = name.substringBeforeLast(".") +    val featureFlagsImplClass = Class.forName("$flagPackage.FeatureFlagsImpl") +    val featureFlagsImpl = featureFlagsImplClass.getConstructor().newInstance() +    val flagMethodName = name.substringAfterLast(".").snakeToCamelCase() +    val flagGetter = featureFlagsImplClass.getDeclaredMethod(flagMethodName) +    return flagGetter.invoke(featureFlagsImpl) as Boolean +} + +private fun String.snakeToCamelCase(): String { +    val pattern = "_[a-z]".toRegex() +    return replace(pattern) { it.value.last().uppercase() } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt index 0ee348e0d23d..7750d25de753 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt @@ -28,6 +28,8 @@ import com.android.systemui.util.mockito.any  import com.android.systemui.util.mockito.eq  import com.android.systemui.util.mockito.whenever  import com.android.systemui.util.time.FakeSystemClock +import kotlin.math.max +import kotlin.test.assertEquals  import org.junit.Before  import org.junit.Test  import org.junit.runner.RunWith @@ -149,26 +151,52 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() {      }      @Test +    fun playHapticAtProgress_beforeNextDragThreshold_playsLowTicksOnce() { +        // GIVEN max velocity and a slider progress at half progress +        val firstProgress = 0.5f +        val firstTicks = generateTicksComposition(config.maxVelocityToScale, firstProgress) + +        // Given a second slider progress event smaller than the progress threshold +        val secondProgress = firstProgress + max(0f, config.deltaProgressForDragThreshold - 0.01f) + +        // GIVEN system running for 1s +        clock.advanceTime(1000) + +        // WHEN two calls to play occur with the required threshold separation (time and progress) +        sliderHapticFeedbackProvider.onProgress(firstProgress) +        clock.advanceTime(dragTextureThresholdMillis.toLong()) +        sliderHapticFeedbackProvider.onProgress(secondProgress) + +        // THEN Only the first compositions plays +        verify(vibratorHelper, times(1)) +            .vibrate(eq(firstTicks), any(VibrationAttributes::class.java)) +        verify(vibratorHelper, times(1)) +            .vibrate(any(VibrationEffect::class.java), any(VibrationAttributes::class.java)) +    } + +    @Test      fun playHapticAtProgress_afterNextDragThreshold_playsLowTicksTwice() { -        // GIVEN max velocity and slider progress -        val progress = 1f -        val expectedScale = scaleAtProgressChange(config.maxVelocityToScale.toFloat(), progress) -        val ticks = VibrationEffect.startComposition() -        repeat(config.numberOfLowTicks) { -            ticks.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, expectedScale) -        } +        // GIVEN max velocity and a slider progress at half progress +        val firstProgress = 0.5f +        val firstTicks = generateTicksComposition(config.maxVelocityToScale, firstProgress) + +        // Given a second slider progress event beyond progress threshold +        val secondProgress = firstProgress + config.deltaProgressForDragThreshold + 0.01f +        val secondTicks = generateTicksComposition(config.maxVelocityToScale, secondProgress)          // GIVEN system running for 1s          clock.advanceTime(1000) -        // WHEN two calls to play occur with the required threshold separation -        sliderHapticFeedbackProvider.onProgress(progress) +        // WHEN two calls to play occur with the required threshold separation (time and progress) +        sliderHapticFeedbackProvider.onProgress(firstProgress)          clock.advanceTime(dragTextureThresholdMillis.toLong()) -        sliderHapticFeedbackProvider.onProgress(progress) +        sliderHapticFeedbackProvider.onProgress(secondProgress) -        // THEN the correct composition plays two times -        verify(vibratorHelper, times(2)) -            .vibrate(eq(ticks.compose()), any(VibrationAttributes::class.java)) +        // THEN the correct compositions play +        verify(vibratorHelper, times(1)) +            .vibrate(eq(firstTicks), any(VibrationAttributes::class.java)) +        verify(vibratorHelper, times(1)) +            .vibrate(eq(secondTicks), any(VibrationAttributes::class.java))      }      @Test @@ -229,6 +257,38 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() {              .vibrate(eq(bookendVibration), any(VibrationAttributes::class.java))      } +    fun dragTextureLastProgress_afterDragTextureHaptics_keepsLastDragTextureProgress() { +        // GIVEN max velocity and a slider progress at half progress +        val progress = 0.5f + +        // GIVEN system running for 1s +        clock.advanceTime(1000) + +        // WHEN a drag texture plays +        sliderHapticFeedbackProvider.onProgress(progress) + +        // THEN the dragTextureLastProgress remembers the latest progress +        assertEquals(progress, sliderHapticFeedbackProvider.dragTextureLastProgress) +    } + +    @Test +    fun dragTextureLastProgress_afterDragTextureHaptics_resetsOnHandleReleased() { +        // GIVEN max velocity and a slider progress at half progress +        val progress = 0.5f + +        // GIVEN system running for 1s +        clock.advanceTime(1000) + +        // WHEN a drag texture plays +        sliderHapticFeedbackProvider.onProgress(progress) + +        // WHEN the handle is released +        sliderHapticFeedbackProvider.onHandleReleasedFromTouch() + +        // THEN the dragTextureLastProgress tracker is reset +        assertEquals(-1f, sliderHapticFeedbackProvider.dragTextureLastProgress) +    } +      private fun scaleAtBookends(velocity: Float): Float {          val range = config.upperBookendScale - config.lowerBookendScale          val interpolatedVelocity = @@ -244,4 +304,15 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() {          val bump = interpolatedVelocity * config.additionalVelocityMaxBump          return interpolatedProgress * range + config.progressBasedDragMinScale + bump      } + +    private fun generateTicksComposition(velocity: Float, progress: Float): VibrationEffect { +        val ticks = VibrationEffect.startComposition() +        repeat(config.numberOfLowTicks) { +            ticks.addPrimitive( +                VibrationEffect.Composition.PRIMITIVE_LOW_TICK, +                scaleAtProgressChange(velocity, progress) +            ) +        } +        return ticks.compose() +    }  } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt index d8cdf29284ed..90fd6523ec12 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt @@ -147,7 +147,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {              emptyMap()          )          verify(lockPatternUtils).registerStrongAuthTracker(strongAuthTracker.capture()) -        verify(authController, atLeastOnce()).addCallback(authControllerCallback.capture()) +        verify(authController, times(2)).addCallback(authControllerCallback.capture())      }      @Test @@ -314,18 +314,18 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {      fun faceEnrollmentChangeIsPropagatedForTheCurrentUser() =          testScope.runTest {              createBiometricSettingsRepository() +            val faceAuthAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled) +              faceAuthIsEnabledByBiometricManager()              doNotDisableKeyguardAuthFeatures(PRIMARY_USER_ID)              runCurrent() -            clearInvocations(authController) -            whenever(authController.isFaceAuthEnrolled(PRIMARY_USER_ID)).thenReturn(false) -            val faceAuthAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled) +            enrollmentChange(FACE, PRIMARY_USER_ID, false)              assertThat(faceAuthAllowed()).isFalse() -            verify(authController).addCallback(authControllerCallback.capture()) +              enrollmentChange(REAR_FINGERPRINT, PRIMARY_USER_ID, true)              assertThat(faceAuthAllowed()).isFalse() @@ -375,25 +375,20 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {      fun faceEnrollmentChangesArePropagatedAfterUserSwitch() =          testScope.runTest {              createBiometricSettingsRepository() +            val faceAuthAllowed by collectLastValue(underTest.isFaceAuthEnrolledAndEnabled) +              verify(biometricManager)                  .registerEnabledOnKeyguardCallback(biometricManagerCallback.capture())              userRepository.setSelectedUserInfo(ANOTHER_USER)              doNotDisableKeyguardAuthFeatures(ANOTHER_USER_ID)              biometricManagerCallback.value.onChanged(true, ANOTHER_USER_ID) - -            runCurrent() -            clearInvocations(authController) - -            val faceAuthAllowed = collectLastValue(underTest.isFaceAuthEnrolledAndEnabled) -            runCurrent() - -            verify(authController).addCallback(authControllerCallback.capture()) - +            onNonStrongAuthChanged(true, ANOTHER_USER_ID)              whenever(authController.isFaceAuthEnrolled(ANOTHER_USER_ID)).thenReturn(true)              enrollmentChange(FACE, ANOTHER_USER_ID, true) +            runCurrent() -            assertThat(faceAuthAllowed()).isTrue() +            assertThat(faceAuthAllowed).isTrue()          }      @Test @@ -637,6 +632,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {              val isFaceAuthCurrentlyAllowed by collectLastValue(underTest.isFaceAuthCurrentlyAllowed)              faceAuthIsEnrolled() +            enrollmentChange(FACE, PRIMARY_USER_ID, true)              deviceIsInPostureThatSupportsFaceAuth()              doNotDisableKeyguardAuthFeatures()              faceAuthIsStrongBiometric() @@ -660,6 +656,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {              val isFaceAuthCurrentlyAllowed by collectLastValue(underTest.isFaceAuthCurrentlyAllowed)              faceAuthIsEnrolled() +            enrollmentChange(FACE, PRIMARY_USER_ID, true)              deviceIsInPostureThatSupportsFaceAuth()              doNotDisableKeyguardAuthFeatures()              faceAuthIsNonStrongBiometric() @@ -753,7 +750,9 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {          }      private fun enrollmentChange(biometricType: BiometricType, userId: Int, enabled: Boolean) { -        authControllerCallback.value.onEnrollmentsChanged(biometricType, userId, enabled) +        authControllerCallback.allValues.forEach { +            it.onEnrollmentsChanged(biometricType, userId, enabled) +        }      }      private fun doNotDisableKeyguardAuthFeatures(userId: Int = PRIMARY_USER_ID) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt index 06eb0dd364b5..6ad2d731f375 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt @@ -43,7 +43,7 @@ import com.android.systemui.dump.logcatLogBuffer  import com.android.systemui.flags.FakeFeatureFlags  import com.android.systemui.flags.Flags  import com.android.systemui.keyguard.DismissCallbackRegistry -import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository +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.FakeKeyguardRepository @@ -95,6 +95,7 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() {          FakeDeviceEntryFingerprintAuthRepository      private lateinit var fakeKeyguardRepository: FakeKeyguardRepository      private lateinit var powerInteractor: PowerInteractor +    private lateinit var fakeBiometricSettingsRepository: FakeBiometricSettingsRepository      @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor      @Mock private lateinit var faceWakeUpTriggersConfig: FaceWakeUpTriggersConfig @@ -123,6 +124,8 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() {          facePropertyRepository = FakeFacePropertyRepository()          fakeKeyguardRepository = FakeKeyguardRepository()          powerInteractor = PowerInteractorFactory.create().powerInteractor +        fakeBiometricSettingsRepository = FakeBiometricSettingsRepository() +          underTest =              SystemUIKeyguardFaceAuthInteractor(                  mContext, @@ -147,7 +150,7 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() {                      mock(StatusBarStateController::class.java),                      mock(KeyguardStateController::class.java),                      bouncerRepository, -                    mock(BiometricSettingsRepository::class.java), +                    fakeBiometricSettingsRepository,                      FakeSystemClock(),                      keyguardUpdateMonitor,                  ), @@ -160,6 +163,7 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() {                  facePropertyRepository,                  faceWakeUpTriggersConfig,                  powerInteractor, +                fakeBiometricSettingsRepository,              )      } @@ -481,6 +485,7 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() {      fun faceUnlockIsDisabledWhenFpIsLockedOut() =          testScope.runTest {              underTest.start() +            fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)              fakeDeviceEntryFingerprintAuthRepository.setLockedOut(true)              runCurrent() diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt new file mode 100644 index 000000000000..255f4df17244 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +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.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.google.common.collect.Range +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class GoneToAodTransitionViewModelTest : SysuiTestCase() { +    private lateinit var underTest: GoneToAodTransitionViewModel +    private lateinit var repository: FakeKeyguardTransitionRepository +    private lateinit var testScope: TestScope + +    @Before +    fun setUp() { +        val testDispatcher = StandardTestDispatcher() +        testScope = TestScope(testDispatcher) + +        repository = FakeKeyguardTransitionRepository() +        val interactor = +            KeyguardTransitionInteractorFactory.create( +                    scope = testScope.backgroundScope, +                    repository = repository, +                ) +                .keyguardTransitionInteractor +        underTest = GoneToAodTransitionViewModel(interactor) +    } + +    @Test +    fun enterFromTopTranslationY() = +        testScope.runTest { +            val pixels = -100f +            val enterFromTopTranslationY by +                collectLastValue(underTest.enterFromTopTranslationY(pixels.toInt())) + +            // The animation should only start > halfway through +            repository.sendTransitionStep(step(0f, TransitionState.STARTED)) +            assertThat(enterFromTopTranslationY).isEqualTo(pixels) + +            repository.sendTransitionStep(step(0.5f)) +            assertThat(enterFromTopTranslationY).isEqualTo(pixels) + +            repository.sendTransitionStep(step(.85f)) +            assertThat(enterFromTopTranslationY).isIn(Range.closed(pixels, 0f)) + +            // At the end, the translation should be complete and set to zero +            repository.sendTransitionStep(step(1f)) +            assertThat(enterFromTopTranslationY).isEqualTo(0f) +        } + +    @Test +    fun enterFromTopAnimationAlpha() = +        testScope.runTest { +            val enterFromTopAnimationAlpha by collectLastValue(underTest.enterFromTopAnimationAlpha) + +            // The animation should only start > halfway through +            repository.sendTransitionStep(step(0f, TransitionState.STARTED)) +            assertThat(enterFromTopAnimationAlpha).isEqualTo(0f) + +            repository.sendTransitionStep(step(0.5f)) +            assertThat(enterFromTopAnimationAlpha).isEqualTo(0f) + +            repository.sendTransitionStep(step(.85f)) +            assertThat(enterFromTopAnimationAlpha).isIn(Range.closed(0f, 1f)) + +            repository.sendTransitionStep(step(1f)) +            assertThat(enterFromTopAnimationAlpha).isEqualTo(1f) +        } + +    private fun step( +        value: Float, +        state: TransitionState = TransitionState.RUNNING +    ): TransitionStep { +        return TransitionStep( +            from = KeyguardState.GONE, +            to = KeyguardState.AOD, +            value = value, +            transitionState = state, +            ownerName = "GoneToAodTransitionViewModelTest" +        ) +    } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt index 71688db76a8b..4f545cb0e288 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt @@ -17,8 +17,10 @@  package com.android.systemui.keyguard.ui.viewmodel +import android.view.View  import androidx.test.filters.SmallTest  import com.android.systemui.SysuiTestCase +import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository  import com.android.systemui.coroutines.collectLastValue  import com.android.systemui.flags.FakeFeatureFlags  import com.android.systemui.flags.Flags @@ -26,12 +28,17 @@ import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository  import com.android.systemui.keyguard.domain.interactor.BurnInInteractor  import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor  import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor  import com.android.systemui.keyguard.shared.model.BurnInModel +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionStep  import com.android.systemui.plugins.ClockController  import com.android.systemui.util.mockito.whenever  import com.google.common.truth.Truth.assertThat  import javax.inject.Provider +import kotlinx.coroutines.flow.MutableSharedFlow  import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.emptyFlow  import kotlinx.coroutines.test.StandardTestDispatcher  import kotlinx.coroutines.test.TestScope  import kotlinx.coroutines.test.runTest @@ -41,6 +48,7 @@ import org.junit.runner.RunWith  import org.junit.runners.JUnit4  import org.mockito.Answers  import org.mockito.Mock +import org.mockito.Mockito.anyInt  import org.mockito.MockitoAnnotations  @SmallTest @@ -51,10 +59,20 @@ class KeyguardRootViewModelTest : SysuiTestCase() {      private lateinit var testScope: TestScope      private lateinit var repository: FakeKeyguardRepository      private lateinit var keyguardInteractor: KeyguardInteractor +    private lateinit var configurationRepository: FakeConfigurationRepository      @Mock private lateinit var burnInInteractor: BurnInInteractor +    @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor +    @Mock private lateinit var goneToAodTransitionViewModel: GoneToAodTransitionViewModel +    @Mock +    private lateinit var aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel      @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var clockController: ClockController      private val burnInFlow = MutableStateFlow(BurnInModel()) +    private val goneToAodTransitionViewModelVisibility = MutableStateFlow(0) +    private val enterFromTopAnimationAlpha = MutableStateFlow(0f) +    private val goneToAodTransitionStep = MutableSharedFlow<TransitionStep>(replay = 1) +    private val dozeAmountTransitionStep = MutableSharedFlow<TransitionStep>(replay = 1) +    private val startedKeyguardState = MutableStateFlow(KeyguardState.GONE)      @Before      fun setUp() { @@ -71,9 +89,30 @@ class KeyguardRootViewModelTest : SysuiTestCase() {          val withDeps = KeyguardInteractorFactory.create(featureFlags = featureFlags)          keyguardInteractor = withDeps.keyguardInteractor          repository = withDeps.repository +        configurationRepository = withDeps.configurationRepository + +        whenever(goneToAodTransitionViewModel.enterFromTopTranslationY(anyInt())) +            .thenReturn(emptyFlow<Float>()) +        whenever(goneToAodTransitionViewModel.enterFromTopAnimationAlpha) +            .thenReturn(enterFromTopAnimationAlpha)          whenever(burnInInteractor.keyguardBurnIn).thenReturn(burnInFlow) -        underTest = KeyguardRootViewModel(keyguardInteractor, burnInInteractor) + +        whenever(keyguardTransitionInteractor.goneToAodTransition) +            .thenReturn(goneToAodTransitionStep) +        whenever(keyguardTransitionInteractor.dozeAmountTransition) +            .thenReturn(dozeAmountTransitionStep) +        whenever(keyguardTransitionInteractor.startedKeyguardState).thenReturn(startedKeyguardState) + +        underTest = +            KeyguardRootViewModel( +                context, +                keyguardInteractor, +                burnInInteractor, +                goneToAodTransitionViewModel, +                aodToLockscreenTransitionViewModel, +                keyguardTransitionInteractor, +            )          underTest.clockControllerProvider = Provider { clockController }      } @@ -118,7 +157,7 @@ class KeyguardRootViewModelTest : SysuiTestCase() {              val scale by collectLastValue(underTest.scale)              // Set to not dozing (on lockscreen) -            repository.setDozeAmount(0f) +            dozeAmountTransitionStep.emit(TransitionStep(value = 0f))              // Trigger a change to the burn-in model              burnInFlow.value = @@ -141,8 +180,7 @@ class KeyguardRootViewModelTest : SysuiTestCase() {              val scale by collectLastValue(underTest.scale)              // Set to dozing (on AOD) -            repository.setDozeAmount(1f) - +            dozeAmountTransitionStep.emit(TransitionStep(value = 1f))              // Trigger a change to the burn-in model              burnInFlow.value =                  BurnInModel( @@ -150,10 +188,15 @@ class KeyguardRootViewModelTest : SysuiTestCase() {                      translationY = 30,                      scale = 0.5f,                  ) -              assertThat(translationX).isEqualTo(20)              assertThat(translationY).isEqualTo(30)              assertThat(scale).isEqualTo(Pair(0.5f, true /* scaleClockOnly */)) + +            // Set to the beginning of GONE->AOD transition +            goneToAodTransitionStep.emit(TransitionStep(value = 0f)) +            assertThat(translationX).isEqualTo(0) +            assertThat(translationY).isEqualTo(0) +            assertThat(scale).isEqualTo(Pair(1f, true /* scaleClockOnly */))          }      @Test @@ -166,7 +209,7 @@ class KeyguardRootViewModelTest : SysuiTestCase() {              val scale by collectLastValue(underTest.scale)              // Set to dozing (on AOD) -            repository.setDozeAmount(1f) +            dozeAmountTransitionStep.emit(TransitionStep(value = 1f))              // Trigger a change to the burn-in model              burnInFlow.value = @@ -180,4 +223,28 @@ class KeyguardRootViewModelTest : SysuiTestCase() {              assertThat(translationY).isEqualTo(0)              assertThat(scale).isEqualTo(Pair(0.5f, false /* scaleClockOnly */))          } + +    @Test +    fun burnInLayerVisibility() = +        testScope.runTest { +            val burnInLayerVisibility by collectLastValue(underTest.burnInLayerVisibility) + +            startedKeyguardState.value = KeyguardState.OCCLUDED +            assertThat(burnInLayerVisibility).isNull() + +            startedKeyguardState.value = KeyguardState.AOD +            assertThat(burnInLayerVisibility).isEqualTo(View.VISIBLE) +        } + +    @Test +    fun burnInLayerAlpha() = +        testScope.runTest { +            val burnInLayerAlpha by collectLastValue(underTest.burnInLayerAlpha) + +            enterFromTopAnimationAlpha.value = 0.2f +            assertThat(burnInLayerAlpha).isEqualTo(0.2f) + +            enterFromTopAnimationAlpha.value = 1f +            assertThat(burnInLayerAlpha).isEqualTo(1f) +        }  } diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt index 34360d2ddd5c..6cdf4efd67da 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt @@ -4,7 +4,9 @@ import android.content.ComponentName  import android.os.UserHandle  import android.testing.AndroidTestingRunner  import androidx.test.filters.SmallTest +import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_APP_SELECTOR_DISPLAYED as STATE_APP_SELECTOR_DISPLAYED  import com.android.systemui.SysuiTestCase +import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger  import com.android.systemui.mediaprojection.appselector.data.RecentTask  import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider  import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader @@ -20,6 +22,7 @@ import kotlinx.coroutines.test.runTest  import org.junit.Before  import org.junit.Test  import org.junit.runner.RunWith +import org.mockito.Mockito.never  import org.mockito.Mockito.verify  @RunWith(AndroidTestingRunner::class) @@ -37,10 +40,11 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() {      private val view: MediaProjectionAppSelectorView = mock()      private val policyResolver: ScreenCaptureDevicePolicyResolver = mock() +    private val logger = mock<MediaProjectionMetricsLogger>()      private val thumbnailLoader = FakeThumbnailLoader() -    private val controller = +    private fun createController(isFirstStart: Boolean = true) =          MediaProjectionAppSelectorController(              taskListProvider,              view, @@ -50,6 +54,8 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() {              appSelectorComponentName,              callerPackageName,              thumbnailLoader, +            isFirstStart, +            logger          )      @Before @@ -61,7 +67,7 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() {      fun initNoRecentTasks_bindsEmptyList() {          taskListProvider.tasks = emptyList() -        controller.init() +        createController().init()          verify(view).bind(emptyList())      } @@ -70,7 +76,7 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() {      fun initOneRecentTask_bindsList() {          taskListProvider.tasks = listOf(createRecentTask(taskId = 1)) -        controller.init() +        createController().init()          verify(view).bind(listOf(createRecentTask(taskId = 1)))      } @@ -86,7 +92,7 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() {              )          taskListProvider.tasks = tasks -        controller.init() +        createController().init()          assertThat(thumbnailLoader.capturedTaskIds).containsExactly(2, 3)      } @@ -101,7 +107,7 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() {              )          taskListProvider.tasks = tasks -        controller.init() +        createController().init()          verify(view)              .bind( @@ -124,7 +130,7 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() {              )          taskListProvider.tasks = tasks -        controller.init() +        createController().init()          verify(view)              .bind( @@ -147,7 +153,7 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() {              )          taskListProvider.tasks = tasks -        controller.init() +        createController().init()          verify(view)              .bind( @@ -172,7 +178,7 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() {              )          taskListProvider.tasks = tasks -        controller.init() +        createController().init()          verify(view)              .bind( @@ -199,11 +205,29 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() {          taskListProvider.tasks = tasks          givenCaptureAllowed(isAllow = false) -        controller.init() +        createController().init()          verify(view).bind(emptyList())      } +    @Test +    fun init_firstStart_logsAppSelectorDisplayed() { +        val controller = createController(isFirstStart = true) + +        controller.init() + +        verify(logger).notifyPermissionProgress(STATE_APP_SELECTOR_DISPLAYED) +    } + +    @Test +    fun init_notFirstStart_doesNotLogAppSelectorDisplayed() { +        val controller = createController(isFirstStart = false) + +        controller.init() + +        verify(logger, never()).notifyPermissionProgress(STATE_APP_SELECTOR_DISPLAYED) +    } +      private fun givenCaptureAllowed(isAllow: Boolean) {          whenever(policyResolver.isScreenCaptureAllowed(any(), any())).thenReturn(isAllow)      } @@ -216,6 +240,7 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() {      ): RecentTask {          return RecentTask(              taskId = taskId, +            displayId = 0,              topActivityComponent = topActivityComponent,              baseIntentComponent = ComponentName("com", "Test"),              userId = userId, diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt index 2c7ee56e9408..d75553fe57ab 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt @@ -128,6 +128,7 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() {      private fun createRecentTask(taskId: Int): RecentTask =          RecentTask(              taskId = taskId, +            displayId = 0,              userId = 0,              topActivityComponent = null,              baseIntentComponent = null, diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java index e537131bad01..4c5a2144941a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java @@ -54,11 +54,7 @@ import com.android.systemui.statusbar.connectivity.NetworkController;  import com.android.systemui.statusbar.connectivity.SignalCallback;  import com.android.systemui.statusbar.connectivity.WifiIndicators;  import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository; -import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository;  import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor; -import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl; -import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel; -import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Inactive;  import com.android.systemui.statusbar.policy.CastController;  import com.android.systemui.statusbar.policy.CastController.CastDevice;  import com.android.systemui.statusbar.policy.HotspotController; @@ -111,7 +107,8 @@ public class CastTileTest extends SysuiTestCase {      private WifiInteractor mWifiInteractor;      private final TileJavaAdapter mJavaAdapter = new TileJavaAdapter(); -    private final FakeWifiRepository mWifiRepository = new FakeWifiRepository(); +    private final FakeConnectivityRepository mConnectivityRepository = +            new FakeConnectivityRepository();      private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();      private final TestScope mTestScope = TestScopeProvider.getTestScope(); @@ -124,12 +121,6 @@ public class CastTileTest extends SysuiTestCase {          mTestableLooper = TestableLooper.get(this);          when(mHost.getContext()).thenReturn(mContext); - -        mWifiInteractor = new WifiInteractorImpl( -                new FakeConnectivityRepository(), -                mWifiRepository, -                mTestScope -        );      }      @After @@ -204,25 +195,41 @@ public class CastTileTest extends SysuiTestCase {      // SIGNAL_CALLBACK_DEPRECATION flag set to true      @Test -    public void stateUnavailable_wifiDisabled_newPipeline() { +    public void stateUnavailable_noDefaultNetworks_newPipeline() {          createAndStartTileNewImpl(); -        mWifiRepository.setIsWifiEnabled(false);          mTestableLooper.processAllMessages();          assertEquals(Tile.STATE_UNAVAILABLE, mCastTile.getState().state);      }      @Test -    public void stateUnavailable_wifiEnabled_notConnected_newPipeline() { +    public void stateUnavailable_mobileConnected_newPipeline() {          createAndStartTileNewImpl(); -        mWifiRepository.setIsWifiEnabled(true); -        mWifiRepository.setWifiNetwork(Inactive.INSTANCE); +        mConnectivityRepository.setMobileConnected(true);          mTestableLooper.processAllMessages();          assertEquals(Tile.STATE_UNAVAILABLE, mCastTile.getState().state);      }      @Test +    public void stateInactive_wifiConnected_newPipeline() { +        createAndStartTileNewImpl(); +        mConnectivityRepository.setWifiConnected(true); +        mTestableLooper.processAllMessages(); + +        assertEquals(Tile.STATE_INACTIVE, mCastTile.getState().state); +    } + +    @Test +    public void stateInactive_ethernetConnected_newPipeline() { +        createAndStartTileNewImpl(); +        mConnectivityRepository.setEthernetConnected(true); +        mTestableLooper.processAllMessages(); + +        assertEquals(Tile.STATE_INACTIVE, mCastTile.getState().state); +    } + +    @Test      public void stateActive_wifiConnectedAndCasting_newPipeline() {          createAndStartTileNewImpl();          CastController.CastDevice device = new CastController.CastDevice(); @@ -231,40 +238,27 @@ public class CastTileTest extends SysuiTestCase {          devices.add(device);          when(mController.getCastDevices()).thenReturn(devices); -        mWifiRepository.setWifiNetwork( -                new WifiNetworkModel.Active( -                        1 /* networkId */, -                        true /* isValidated */, -                        3 /* level */, -                        "test" /* ssid */, -                        WifiNetworkModel.HotspotDeviceType.NONE, -                        false /* isPasspointAccessPoint */, -                        false /* isOnlineSignUpforPasspointAccessPoint */, -                        null /* passpointProviderFriendlyName */ -                )); +        mConnectivityRepository.setWifiConnected(true); +          mTestableLooper.processAllMessages();          assertEquals(Tile.STATE_ACTIVE, mCastTile.getState().state);      }      @Test -    public void stateInactive_wifiConnectedNotCasting_newPipeline() { +    public void stateActive_ethernetConnectedAndCasting_newPipeline() {          createAndStartTileNewImpl(); +        CastController.CastDevice device = new CastController.CastDevice(); +        device.state = CastDevice.STATE_CONNECTED; +        List<CastDevice> devices = new ArrayList<>(); +        devices.add(device); +        when(mController.getCastDevices()).thenReturn(devices); + +        mConnectivityRepository.setEthernetConnected(true); -        mWifiRepository.setWifiNetwork( -                new WifiNetworkModel.Active( -                        1 /* networkId */, -                        true /* isValidated */, -                        3 /* level */, -                        "test" /* ssid */, -                        WifiNetworkModel.HotspotDeviceType.NONE, -                        false /* isPasspointAccessPoint */, -                        false /* isOnlineSignUpforPasspointAccessPoint */, -                        null /* passpointProviderFriendlyName */ -                ));          mTestableLooper.processAllMessages(); -        assertEquals(Tile.STATE_INACTIVE, mCastTile.getState().state); +        assertEquals(Tile.STATE_ACTIVE, mCastTile.getState().state);      }      // ------------------------------------------------- @@ -512,7 +506,7 @@ public class CastTileTest extends SysuiTestCase {                  mNetworkController,                  mHotspotController,                  mDialogLaunchAnimator, -                mWifiInteractor, +                mConnectivityRepository,                  mJavaAdapter,                  mFeatureFlags          ); @@ -555,7 +549,7 @@ public class CastTileTest extends SysuiTestCase {                  mNetworkController,                  mHotspotController,                  mDialogLaunchAnimator, -                mWifiInteractor, +                mConnectivityRepository,                  mJavaAdapter,                  mFeatureFlags          ); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt index 58e36be2b9fd..10c7c439e5f4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt @@ -21,6 +21,8 @@ import androidx.test.filters.SmallTest  import com.android.systemui.SysuiTestCase  import com.android.systemui.authentication.data.model.AuthenticationMethodModel  import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.FakeFeatureFlagsClassic +import com.android.systemui.flags.Flags  import com.android.systemui.scene.SceneTestUtils  import com.android.systemui.scene.shared.model.SceneKey  import com.android.systemui.scene.shared.model.SceneModel @@ -49,6 +51,7 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() {      private val testScope = utils.testScope      private val sceneInteractor = utils.sceneInteractor()      private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock()) +    private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }      private var mobileIconsViewModel: MobileIconsViewModel =          MobileIconsViewModel( @@ -61,6 +64,7 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() {                      FakeConnectivityRepository(),                  ),              constants = mock(), +            flags,              scope = testScope.backgroundScope,          ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index 61dd69a8126b..88a5c17f4994 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -25,6 +25,8 @@ import com.android.systemui.authentication.data.repository.FakeAuthenticationRep  import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel  import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel  import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.FakeFeatureFlagsClassic +import com.android.systemui.flags.Flags  import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel  import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel  import com.android.systemui.model.SysUiState @@ -137,6 +139,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {          )      private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock()) +    private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }      private var mobileIconsViewModel: MobileIconsViewModel =          MobileIconsViewModel( @@ -149,6 +152,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {                      FakeConnectivityRepository(),                  ),              constants = mock(), +            flags,              scope = testScope.backgroundScope,          ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java index 6e6833dc0944..90d2e78a411f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java @@ -17,8 +17,10 @@  package com.android.systemui.screenrecord;  import static com.google.common.truth.Truth.assertThat; +  import static junit.framework.Assert.assertFalse;  import static junit.framework.Assert.assertTrue; +  import static org.mockito.ArgumentMatchers.any;  import static org.mockito.ArgumentMatchers.eq;  import static org.mockito.Mockito.verify; @@ -37,6 +39,8 @@ import com.android.systemui.animation.DialogLaunchAnimator;  import com.android.systemui.broadcast.BroadcastDispatcher;  import com.android.systemui.flags.FakeFeatureFlags;  import com.android.systemui.flags.Flags; +import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger; +import com.android.systemui.mediaprojection.SessionCreationSource;  import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;  import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog;  import com.android.systemui.plugins.ActivityStarter; @@ -76,6 +80,8 @@ public class RecordingControllerTest extends SysuiTestCase {      private ActivityStarter mActivityStarter;      @Mock      private UserTracker mUserTracker; +    @Mock +    private MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;      private FakeFeatureFlags mFeatureFlags;      private RecordingController mController; @@ -86,8 +92,15 @@ public class RecordingControllerTest extends SysuiTestCase {      public void setUp() {          MockitoAnnotations.initMocks(this);          mFeatureFlags = new FakeFeatureFlags(); -        mController = new RecordingController(mMainExecutor, mBroadcastDispatcher, mContext, -                mFeatureFlags, mUserContextProvider, () -> mDevicePolicyResolver, mUserTracker); +        mController = new RecordingController( +                mMainExecutor, +                mBroadcastDispatcher, +                mContext, +                mFeatureFlags, +                mUserContextProvider, +                () -> mDevicePolicyResolver, +                mUserTracker, +                mMediaProjectionMetricsLogger);          mController.addCallback(mCallback);      } @@ -269,4 +282,21 @@ public class RecordingControllerTest extends SysuiTestCase {          assertThat(dialog).isInstanceOf(ScreenRecordPermissionDialog.class);      } + +    @Test +    public void testPoliciesFlagEnabled_screenCapturingAllowed_logsProjectionInitiated() { +        if (Looper.myLooper() == null) { +            Looper.prepare(); +        } + +        mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true); +        mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true); +        when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(false); + +        mController.createScreenRecordDialog(mContext, mFeatureFlags, +                mDialogLaunchAnimator, mActivityStarter, /* onStartRecordingClicked= */ null); + +        verify(mMediaProjectionMetricsLogger) +                .notifyProjectionInitiated(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER); +    }  } diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt index 3ae1f35b8134..da4dc850fcae 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt @@ -32,6 +32,7 @@ import com.android.systemui.res.R  import com.android.systemui.settings.UserContextProvider  import com.android.systemui.util.mockito.mock  import com.google.common.truth.Truth.assertThat +import junit.framework.Assert.assertEquals  import org.junit.After  import org.junit.Before  import org.junit.Test @@ -111,6 +112,15 @@ class ScreenRecordPermissionDialogTest : SysuiTestCase() {      }      @Test +    fun showDialog_singleAppIsDefault() { +        dialog.show() + +        val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner) +        val singleApp = context.getString(R.string.screen_share_permission_dialog_option_single_app) +        assertEquals(spinner.adapter.getItem(0), singleApp) +    } + +    @Test      fun showDialog_cancelClicked_dialogIsDismissed() {          dialog.show() diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt index 0d694ee80def..7e41745936f8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt @@ -28,7 +28,6 @@ import android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER  import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN  import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE  import com.android.internal.util.ScreenshotRequest -import com.android.systemui.flags.FakeFeatureFlags  import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo  import com.google.common.truth.Truth.assertThat  import kotlinx.coroutines.CoroutineScope @@ -47,7 +46,6 @@ class RequestProcessorTest {      private val scope = CoroutineScope(Dispatchers.Unconfined)      private val policy = FakeScreenshotPolicy() -    private val flags = FakeFeatureFlags()      /** Tests the Java-compatible function wrapper, ensures callback is invoked. */      @Test @@ -58,7 +56,7 @@ class RequestProcessorTest {                      .setBitmap(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888))                      .build()              ) -        val processor = RequestProcessor(imageCapture, policy, flags, scope) +        val processor = RequestProcessor(imageCapture, policy, scope)          var result: ScreenshotData? = null          var callbackCount = 0 @@ -86,7 +84,7 @@ class RequestProcessorTest {          val request =              ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_OTHER).build() -        val processor = RequestProcessor(imageCapture, policy, flags, scope) +        val processor = RequestProcessor(imageCapture, policy, scope)          val processedData = processor.process(ScreenshotData.fromRequest(request)) @@ -111,7 +109,7 @@ class RequestProcessorTest {          val request =              ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER).build() -        val processor = RequestProcessor(imageCapture, policy, flags, scope) +        val processor = RequestProcessor(imageCapture, policy, scope)          val processedData = processor.process(ScreenshotData.fromRequest(request)) @@ -138,7 +136,7 @@ class RequestProcessorTest {          val request =              ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER).build() -        val processor = RequestProcessor(imageCapture, policy, flags, scope) +        val processor = RequestProcessor(imageCapture, policy, scope)          Assert.assertThrows(IllegalStateException::class.java) {              runBlocking { processor.process(ScreenshotData.fromRequest(request)) } @@ -148,7 +146,7 @@ class RequestProcessorTest {      @Test      fun testProvidedImageScreenshot() = runBlocking {          val bounds = Rect(50, 50, 150, 150) -        val processor = RequestProcessor(imageCapture, policy, flags, scope) +        val processor = RequestProcessor(imageCapture, policy, scope)          policy.setManagedProfile(USER_ID, false) @@ -173,7 +171,7 @@ class RequestProcessorTest {      @Test      fun testProvidedImageScreenshot_managedProfile() = runBlocking {          val bounds = Rect(50, 50, 150, 150) -        val processor = RequestProcessor(imageCapture, policy, flags, scope) +        val processor = RequestProcessor(imageCapture, policy, scope)          // Indicate that the screenshot belongs to a manged profile          policy.setManagedProfile(USER_ID, true) diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt index d8821aa6aa00..3dc90374206a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt @@ -25,6 +25,7 @@ import com.android.systemui.util.mockito.mock  import com.android.systemui.util.mockito.nullable  import com.android.systemui.util.mockito.whenever  import com.google.common.truth.Truth.assertThat +import java.lang.IllegalStateException  import kotlinx.coroutines.test.TestScope  import kotlinx.coroutines.test.UnconfinedTestDispatcher  import kotlinx.coroutines.test.runCurrent @@ -43,8 +44,11 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {      private val controller0 = mock<ScreenshotController>()      private val controller1 = mock<ScreenshotController>() +    private val notificationsController0 = mock<ScreenshotNotificationsController>() +    private val notificationsController1 = mock<ScreenshotNotificationsController>()      private val controllerFactory = mock<ScreenshotController.Factory>()      private val callback = mock<TakeScreenshotService.RequestCallback>() +    private val notificationControllerFactory = mock<ScreenshotNotificationsController.Factory>()      private val fakeDisplayRepository = FakeDisplayRepository()      private val requestProcessor = FakeRequestProcessor() @@ -59,12 +63,15 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {              testScope,              requestProcessor,              eventLogger, +            notificationControllerFactory          )      @Before      fun setUp() {          whenever(controllerFactory.create(eq(0), any())).thenReturn(controller0)          whenever(controllerFactory.create(eq(1), any())).thenReturn(controller1) +        whenever(notificationControllerFactory.create(eq(0))).thenReturn(notificationsController0) +        whenever(notificationControllerFactory.create(eq(1))).thenReturn(notificationsController1)      }      @Test @@ -310,6 +317,123 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {              screenshotExecutor.onDestroy()          } +    @Test +    fun executeScreenshots_errorFromProcessor_logsScreenshotRequested() = +        testScope.runTest { +            setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1)) +            val onSaved = { _: Uri -> } +            requestProcessor.shouldThrowException = true + +            screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) + +            val screenshotRequested = +                eventLogger.logs.filter { +                    it.eventId == ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_OTHER.id +                } +            assertThat(screenshotRequested).hasSize(2) +            screenshotExecutor.onDestroy() +        } + +    @Test +    fun executeScreenshots_errorFromProcessor_logsUiError() = +        testScope.runTest { +            setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1)) +            val onSaved = { _: Uri -> } +            requestProcessor.shouldThrowException = true + +            screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) + +            val screenshotRequested = +                eventLogger.logs.filter { +                    it.eventId == ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED.id +                } +            assertThat(screenshotRequested).hasSize(2) +            screenshotExecutor.onDestroy() +        } + +    @Test +    fun executeScreenshots_errorFromProcessorOnDefaultDisplay_showsErrorNotification() = +        testScope.runTest { +            setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1)) +            val onSaved = { _: Uri -> } +            requestProcessor.shouldThrowException = true + +            screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) + +            verify(notificationsController0).notifyScreenshotError(any()) +            screenshotExecutor.onDestroy() +        } + +    @Test +    fun executeScreenshots_errorFromProcessorOnSecondaryDisplay_showsErrorNotification() = +        testScope.runTest { +            setDisplays(display(TYPE_INTERNAL, id = 0)) +            val onSaved = { _: Uri -> } +            requestProcessor.shouldThrowException = true + +            screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) + +            verify(notificationsController0).notifyScreenshotError(any()) +            screenshotExecutor.onDestroy() +        } + +    @Test +    fun executeScreenshots_errorFromScreenshotController_reportsRequested() = +        testScope.runTest { +            setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1)) +            val onSaved = { _: Uri -> } +            whenever(controller0.handleScreenshot(any(), any(), any())) +                .thenThrow(IllegalStateException::class.java) +            whenever(controller1.handleScreenshot(any(), any(), any())) +                .thenThrow(IllegalStateException::class.java) + +            screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) + +            val screenshotRequested = +                eventLogger.logs.filter { +                    it.eventId == ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_OTHER.id +                } +            assertThat(screenshotRequested).hasSize(2) +            screenshotExecutor.onDestroy() +        } + +    @Test +    fun executeScreenshots_errorFromScreenshotController_reportsError() = +        testScope.runTest { +            setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1)) +            val onSaved = { _: Uri -> } +            whenever(controller0.handleScreenshot(any(), any(), any())) +                .thenThrow(IllegalStateException::class.java) +            whenever(controller1.handleScreenshot(any(), any(), any())) +                .thenThrow(IllegalStateException::class.java) + +            screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) + +            val screenshotRequested = +                eventLogger.logs.filter { +                    it.eventId == ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED.id +                } +            assertThat(screenshotRequested).hasSize(2) +            screenshotExecutor.onDestroy() +        } + +    @Test +    fun executeScreenshots_errorFromScreenshotController_showsErrorNotification() = +        testScope.runTest { +            setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1)) +            val onSaved = { _: Uri -> } +            whenever(controller0.handleScreenshot(any(), any(), any())) +                .thenThrow(IllegalStateException::class.java) +            whenever(controller1.handleScreenshot(any(), any(), any())) +                .thenThrow(IllegalStateException::class.java) + +            screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) + +            verify(notificationsController0).notifyScreenshotError(any()) +            verify(notificationsController1).notifyScreenshotError(any()) +            screenshotExecutor.onDestroy() +        } +      private suspend fun TestScope.setDisplays(vararg displays: Display) {          fakeDisplayRepository.emit(displays.toSet())          runCurrent() @@ -328,8 +452,9 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {      private class FakeRequestProcessor : ScreenshotRequestProcessor {          var processed: ScreenshotData? = null          var toReturn: ScreenshotData? = null - +        var shouldThrowException = false          override suspend fun process(screenshot: ScreenshotData): ScreenshotData { +            if (shouldThrowException) throw RequestProcessorException("")              processed = screenshot              return toReturn ?: screenshot          } diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt index 5091a7004f79..f3809aad4de3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt @@ -65,6 +65,7 @@ class TakeScreenshotServiceTest : SysuiTestCase() {      private val requestProcessor = mock<RequestProcessor>()      private val devicePolicyManager = mock<DevicePolicyManager>()      private val devicePolicyResourcesManager = mock<DevicePolicyResourcesManager>() +    private val notificationsControllerFactory = mock<ScreenshotNotificationsController.Factory>()      private val notificationsController = mock<ScreenshotNotificationsController>()      private val callback = mock<RequestCallback>() @@ -87,6 +88,7 @@ class TakeScreenshotServiceTest : SysuiTestCase() {              .thenReturn(false)          whenever(userManager.isUserUnlocked).thenReturn(true)          whenever(controllerFactory.create(any(), any())).thenReturn(controller) +        whenever(notificationsControllerFactory.create(any())).thenReturn(notificationsController)          // Stub request processor as a synchronous no-op for tests with the flag enabled          doAnswer { @@ -323,7 +325,7 @@ class TakeScreenshotServiceTest : SysuiTestCase() {                  userManager,                  devicePolicyManager,                  eventLogger, -                notificationsController, +                notificationsControllerFactory,                  mContext,                  Runnable::run,                  flags, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index 8d8c70e26ab2..6223e250d603 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -157,6 +157,7 @@ import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;  import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;  import com.android.systemui.statusbar.phone.KeyguardBottomAreaViewController;  import com.android.systemui.statusbar.phone.KeyguardBypassController; +import com.android.systemui.statusbar.phone.KeyguardClockPositionAlgorithm;  import com.android.systemui.statusbar.phone.KeyguardStatusBarView;  import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController;  import com.android.systemui.statusbar.phone.LightBarController; @@ -328,6 +329,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {      @Mock private CastController mCastController;      @Mock private KeyguardRootView mKeyguardRootView;      @Mock private SharedNotificationContainerInteractor mSharedNotificationContainerInteractor; +    @Mock private KeyguardClockPositionAlgorithm mKeyguardClockPositionAlgorithm;      protected final int mMaxUdfpsBurnInOffsetY = 5;      protected KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor; @@ -388,6 +390,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {                  mFeatureFlags,                  mInteractionJankMonitor,                  mKeyguardInteractor, +                mKeyguardTransitionInteractor,                  mDumpManager,                  mPowerInteractor)); @@ -667,7 +670,8 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {                  mKeyguardViewConfigurator,                  mKeyguardFaceAuthInteractor,                  new ResourcesSplitShadeStateController(), -                mPowerInteractor); +                mPowerInteractor, +                mKeyguardClockPositionAlgorithm);          mNotificationPanelViewController.initDependencies(                  mCentralSurfaces,                  null, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt index 607cdab12f56..df38f936f4f2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt @@ -4,6 +4,8 @@ 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.flags.FakeFeatureFlagsClassic +import com.android.systemui.flags.Flags  import com.android.systemui.scene.SceneTestUtils  import com.android.systemui.scene.shared.model.ObservableTransitionState  import com.android.systemui.scene.shared.model.SceneKey @@ -31,6 +33,7 @@ class ShadeHeaderViewModelTest : SysuiTestCase() {      private val sceneInteractor = utils.sceneInteractor()      private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock()) +    private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }      private var mobileIconsViewModel: MobileIconsViewModel =          MobileIconsViewModel( @@ -43,6 +46,7 @@ class ShadeHeaderViewModelTest : SysuiTestCase() {                      FakeConnectivityRepository(),                  ),              constants = mock(), +            flags,              scope = testScope.backgroundScope,          ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt index c4237827c3ea..3064f4b2b355 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt @@ -21,6 +21,8 @@ import androidx.test.filters.SmallTest  import com.android.systemui.SysuiTestCase  import com.android.systemui.authentication.data.model.AuthenticationMethodModel  import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.FakeFeatureFlagsClassic +import com.android.systemui.flags.Flags  import com.android.systemui.scene.SceneTestUtils  import com.android.systemui.scene.shared.model.SceneKey  import com.android.systemui.scene.shared.model.SceneModel @@ -55,6 +57,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() {          )      private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock()) +    private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }      private var mobileIconsViewModel: MobileIconsViewModel =          MobileIconsViewModel( @@ -67,6 +70,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() {                      FakeConnectivityRepository(),                  ),              constants = mock(), +            flags,              scope = testScope.backgroundScope,          ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt deleted file mode 100644 index e4da53a1a0a4..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (C) 2019 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 - -import com.google.common.truth.Truth.assertThat - -import android.graphics.Bitmap -import android.graphics.Canvas -import android.graphics.Color -import android.graphics.Point -import android.testing.AndroidTestingRunner -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import org.junit.After -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith - -private const val WIDTH = 200 -private const val HEIGHT = 200 - -@RunWith(AndroidTestingRunner::class) -@SmallTest -class MediaArtworkProcessorTest : SysuiTestCase() { - -    private var screenWidth = 0 -    private var screenHeight = 0 - -    private lateinit var processor: MediaArtworkProcessor - -    @Before -    fun setUp() { -        processor = MediaArtworkProcessor() - -        val point = Point() -        checkNotNull(context.display).getSize(point) -        screenWidth = point.x -        screenHeight = point.y -    } - -    @After -    fun tearDown() { -        processor.clearCache() -    } - -    @Test -    fun testProcessArtwork() { -        // GIVEN some "artwork", which is just a solid blue image -        val artwork = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ARGB_8888) -        Canvas(artwork).drawColor(Color.BLUE) -        // WHEN the background is created from the artwork -        val background = processor.processArtwork(context, artwork)!! -        // THEN the background has the size of the screen that has been downsamples -        assertThat(background.height).isLessThan(screenHeight) -        assertThat(background.width).isLessThan(screenWidth) -        assertThat(background.config).isEqualTo(Bitmap.Config.ARGB_8888) -    } - -    @Test -    fun testCache() { -        // GIVEN a solid blue image -        val artwork = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ARGB_8888) -        Canvas(artwork).drawColor(Color.BLUE) -        // WHEN the background is processed twice -        val background1 = processor.processArtwork(context, artwork)!! -        val background2 = processor.processArtwork(context, artwork)!! -        // THEN the two bitmaps are the same -        // Note: This is currently broken and trying to use caching causes issues -        assertThat(background1).isNotSameInstanceAs(background2) -    } - -    @Test -    fun testConfig() { -        // GIVEN some which is not ARGB_8888 -        val artwork = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ALPHA_8) -        Canvas(artwork).drawColor(Color.BLUE) -        // WHEN the background is created from the artwork -        val background = processor.processArtwork(context, artwork)!! -        // THEN the background has Config ARGB_8888 -        assertThat(background.config).isEqualTo(Bitmap.Config.ARGB_8888) -    } - -    @Test -    fun testRecycledArtwork() { -        // GIVEN some "artwork", which is just a solid blue image -        val artwork = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ARGB_8888) -        Canvas(artwork).drawColor(Color.BLUE) -        // AND the artwork is recycled -        artwork.recycle() -        // WHEN the background is created from the artwork -        val background = processor.processArtwork(context, artwork) -        // THEN the processed bitmap is null -        assertThat(background).isNull() -    } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerMainThreadTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerMainThreadTest.java new file mode 100644 index 000000000000..3a9c24a7109c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerMainThreadTest.java @@ -0,0 +1,597 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar; + +import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED; +import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS; +import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS; +import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS; +import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS; + +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.ActivityManager; +import android.app.KeyguardManager; +import android.app.Notification; +import android.app.admin.DevicePolicyManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.pm.UserInfo; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Handler; +import android.os.Looper; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.Settings; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import androidx.test.filters.SmallTest; + +import com.android.internal.widget.LockPatternUtils; +import com.android.systemui.Dependency; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FakeFeatureFlagsClassic; +import com.android.systemui.flags.Flags; +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.settings.UserTracker; +import com.android.systemui.statusbar.NotificationLockscreenUserManager.NotificationStateChangedListener; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; +import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; +import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; +import com.android.systemui.statusbar.policy.DeviceProvisionedController; +import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.util.settings.FakeSettings; + +import com.google.android.collect.Lists; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.concurrent.Executor; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class NotificationLockscreenUserManagerMainThreadTest extends SysuiTestCase { +    @Mock +    private NotificationPresenter mPresenter; +    @Mock +    private UserManager mUserManager; +    @Mock +    private UserTracker mUserTracker; + +    // Dependency mocks: +    @Mock +    private NotificationVisibilityProvider mVisibilityProvider; +    @Mock +    private CommonNotifCollection mNotifCollection; +    @Mock +    private DevicePolicyManager mDevicePolicyManager; +    @Mock +    private NotificationClickNotifier mClickNotifier; +    @Mock +    private OverviewProxyService mOverviewProxyService; +    @Mock +    private KeyguardManager mKeyguardManager; +    @Mock +    private DeviceProvisionedController mDeviceProvisionedController; +    @Mock +    private StatusBarStateController mStatusBarStateController; +    @Mock +    private BroadcastDispatcher mBroadcastDispatcher; +    @Mock +    private KeyguardStateController mKeyguardStateController; + +    private UserInfo mCurrentUser; +    private UserInfo mSecondaryUser; +    private UserInfo mWorkUser; +    private FakeSettings mSettings; +    private TestNotificationLockscreenUserManager mLockscreenUserManager; +    private NotificationEntry mCurrentUserNotif; +    private NotificationEntry mSecondaryUserNotif; +    private NotificationEntry mWorkProfileNotif; +    private final FakeFeatureFlagsClassic mFakeFeatureFlags = new FakeFeatureFlagsClassic(); +    private Executor mBackgroundExecutor = Runnable::run; // Direct executor + +    @Before +    public void setUp() { +        MockitoAnnotations.initMocks(this); + +        mFakeFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, false); + +        int currentUserId = ActivityManager.getCurrentUser(); +        when(mUserTracker.getUserId()).thenReturn(currentUserId); +        mSettings = new FakeSettings(); +        mSettings.setUserId(ActivityManager.getCurrentUser()); +        mCurrentUser = new UserInfo(currentUserId, "", 0); +        mSecondaryUser = new UserInfo(currentUserId + 1, "", 0); +        mWorkUser = new UserInfo(currentUserId + 2, "" /* name */, null /* iconPath */, 0, +                UserManager.USER_TYPE_PROFILE_MANAGED); + +        when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(true); +        when(mUserManager.getProfiles(currentUserId)).thenReturn(Lists.newArrayList( +                mCurrentUser, mWorkUser)); +        when(mUserManager.getProfiles(mSecondaryUser.id)).thenReturn(Lists.newArrayList( +                mSecondaryUser)); +        mDependency.injectTestDependency(Dependency.MAIN_HANDLER, +                Handler.createAsync(Looper.myLooper())); + +        Notification notifWithPrivateVisibility = new Notification(); +        notifWithPrivateVisibility.visibility = Notification.VISIBILITY_PRIVATE; +        mCurrentUserNotif = new NotificationEntryBuilder() +                .setNotification(notifWithPrivateVisibility) +                .setUser(new UserHandle(mCurrentUser.id)) +                .build(); +        mSecondaryUserNotif = new NotificationEntryBuilder() +                .setNotification(notifWithPrivateVisibility) +                .setUser(new UserHandle(mSecondaryUser.id)) +                .build(); +        mWorkProfileNotif = new NotificationEntryBuilder() +                .setNotification(notifWithPrivateVisibility) +                .setUser(new UserHandle(mWorkUser.id)) +                .build(); + +        mLockscreenUserManager = new TestNotificationLockscreenUserManager(mContext); +        mLockscreenUserManager.setUpWithPresenter(mPresenter); +    } + +    private void changeSetting(String setting) { +        final Collection<Uri> lockScreenUris = new ArrayList<>(); +        lockScreenUris.add(Settings.Secure.getUriFor(setting)); +        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false, +            lockScreenUris, 0); +    } + +    @Test +    public void testLockScreenShowNotificationsFalse() { +        mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0); +        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); +        assertFalse(mLockscreenUserManager.shouldShowLockscreenNotifications()); +    } + +    @Test +    public void testLockScreenShowNotificationsTrue() { +        mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1); +        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); +        assertTrue(mLockscreenUserManager.shouldShowLockscreenNotifications()); +    } + +    @Test +    public void testLockScreenAllowPrivateNotificationsTrue() { +        mSettings.putInt(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1); +        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); +        assertTrue(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id)); +    } + +    @Test +    public void testLockScreenAllowPrivateNotificationsFalse() { +        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, +                mCurrentUser.id); +        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); +        assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id)); +    } + +    @Test +    public void testLockScreenAllowsWorkPrivateNotificationsFalse() { +        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, +                mWorkUser.id); +        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); +        assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id)); +    } + +    @Test +    public void testLockScreenAllowsWorkPrivateNotificationsTrue() { +        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, +                mWorkUser.id); +        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); +        assertTrue(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id)); +    } + +    @Test +    public void testCurrentUserPrivateNotificationsNotRedacted() { +        // GIVEN current user doesn't allow private notifications to show +        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, +                mCurrentUser.id); +        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + +        // THEN current user's notification is redacted +        assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif)); +    } + +    @Test +    public void testCurrentUserPrivateNotificationsRedacted() { +        // GIVEN current user allows private notifications to show +        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, +                mCurrentUser.id); +        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + +        // THEN current user's notification isn't redacted +        assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif)); +    } + +    @Test +    public void testWorkPrivateNotificationsRedacted() { +        // GIVEN work profile doesn't private notifications to show +        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, +                mWorkUser.id); +        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + +        // THEN work profile notification is redacted +        assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif)); +        assertFalse(mLockscreenUserManager.allowsManagedPrivateNotificationsInPublic()); +    } + +    @Test +    public void testWorkPrivateNotificationsNotRedacted() { +        // GIVEN work profile allows private notifications to show +        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, +                mWorkUser.id); +        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + +        // THEN work profile notification isn't redacted +        assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif)); +        assertTrue(mLockscreenUserManager.allowsManagedPrivateNotificationsInPublic()); +    } + +    @Test +    public void testWorkPrivateNotificationsNotRedacted_otherUsersRedacted() { +        // GIVEN work profile allows private notifications to show but the other users don't +        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, +                mWorkUser.id); +        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, +                mCurrentUser.id); +        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, +                mSecondaryUser.id); +        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); +        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); +        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + +        // THEN the work profile notification doesn't need to be redacted +        assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif)); + +        // THEN the current user and secondary user notifications do need to be redacted +        assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif)); +        assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif)); +    } + +    @Test +    public void testWorkProfileRedacted_otherUsersNotRedacted() { +        // GIVEN work profile doesn't allow private notifications to show but the other users do +        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, +                mWorkUser.id); +        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, +                mCurrentUser.id); +        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, +                mSecondaryUser.id); +        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); +        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); +        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + +        // THEN the work profile notification needs to be redacted +        assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif)); + +        // THEN the current user and secondary user notifications don't need to be redacted +        assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif)); +        assertFalse(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif)); +    } + +    @Test +    public void testSecondaryUserNotRedacted_currentUserRedacted() { +        // GIVEN secondary profile allows private notifications to show but the current user +        // doesn't allow private notifications to show +        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, +                mCurrentUser.id); +        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, +                mSecondaryUser.id); +        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); +        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + +        // THEN the secondary profile notification still needs to be redacted because the current +        // user's setting takes precedence +        assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif)); +    } + +    @Test +    public void testUserSwitchedCallsOnUserSwitching() { +        mLockscreenUserManager.getUserTrackerCallbackForTest().onUserChanging(mSecondaryUser.id, +                mContext); +        verify(mPresenter, times(1)).onUserSwitched(mSecondaryUser.id); +    } + +    @Test +    public void testIsLockscreenPublicMode() { +        assertFalse(mLockscreenUserManager.isLockscreenPublicMode(mCurrentUser.id)); +        mLockscreenUserManager.setLockscreenPublicMode(true, mCurrentUser.id); +        assertTrue(mLockscreenUserManager.isLockscreenPublicMode(mCurrentUser.id)); +    } + +    @Test +    public void testUpdateIsPublicMode() { +        when(mKeyguardStateController.isMethodSecure()).thenReturn(true); + +        NotificationStateChangedListener listener = mock(NotificationStateChangedListener.class); +        mLockscreenUserManager.addNotificationStateChangedListener(listener); +        mLockscreenUserManager.mCurrentProfiles.append(0, mock(UserInfo.class)); + +        // first call explicitly sets user 0 to not public; notifies +        mLockscreenUserManager.updatePublicMode(); +        TestableLooper.get(this).processAllMessages(); +        assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0)); +        verify(listener).onNotificationStateChanged(); +        clearInvocations(listener); + +        // calling again has no changes; does not notify +        mLockscreenUserManager.updatePublicMode(); +        TestableLooper.get(this).processAllMessages(); +        assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0)); +        verify(listener, never()).onNotificationStateChanged(); + +        // Calling again with keyguard now showing makes user 0 public; notifies +        when(mKeyguardStateController.isShowing()).thenReturn(true); +        mLockscreenUserManager.updatePublicMode(); +        TestableLooper.get(this).processAllMessages(); +        assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0)); +        verify(listener).onNotificationStateChanged(); +        clearInvocations(listener); + +        // calling again has no changes; does not notify +        mLockscreenUserManager.updatePublicMode(); +        TestableLooper.get(this).processAllMessages(); +        assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0)); +        verify(listener, never()).onNotificationStateChanged(); +    } + +    @Test +    public void testDevicePolicyDoesNotAllowNotifications() { +        // User allows them +        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); +        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + +        // DevicePolicy hides notifs on lockscreen +        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id)) +                .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS); + +        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( +                0, null, null, 0, true, false, null, mCurrentUser.id, 0); +        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); +        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, +                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); + +        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); +    } + +    @Test +    public void testDevicePolicyDoesNotAllowNotifications_secondary() { +        Mockito.clearInvocations(mDevicePolicyManager); +        // User allows notifications +        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id); +        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + +        // DevicePolicy hides notifications +        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id)) +                .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS); + +        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( +                0, null, null, 0, true, false, null, mSecondaryUser.id, 0); +        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); +        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, +                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); + +        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id)); +    } + +    @Test +    public void testDevicePolicy_noPrivateNotifications() { +        Mockito.clearInvocations(mDevicePolicyManager); +        // User allows notifications +        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); +        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + +        // DevicePolicy hides sensitive content +        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id)) +                .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS); + +        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( +                0, null, null, 0, true, false, null, mCurrentUser.id, 0); +        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); +        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, +                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); + +        assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif)); +    } + +    @Test +    public void testDevicePolicy_noPrivateNotifications_userAll() { +        // User allows notifications +        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); +        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + +        // DevicePolicy hides sensitive content +        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id)) +                .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS); + +        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( +                0, null, null, 0, true, false, null, mCurrentUser.id, 0); +        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); +        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, +                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); + +        assertTrue(mLockscreenUserManager.needsRedaction(new NotificationEntryBuilder() +                .setNotification(new Notification()) +                .setUser(UserHandle.ALL) +                .build())); +    } + +    @Test +    public void testDevicePolicyPrivateNotifications_secondary() { +        Mockito.clearInvocations(mDevicePolicyManager); +        // User allows notifications +        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id); +        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + +        // DevicePolicy hides sensitive content +        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id)) +                .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS); + +        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( +                0, null, null, 0, true, false, null, mSecondaryUser.id, 0); +        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); +        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, +                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); + +        mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext); +        assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif)); +    } + +    @Test +    public void testHideNotifications_primary() { +        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id); +        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + +        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); +    } + +    @Test +    public void testHideNotifications_secondary() { +        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id); +        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + +        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id)); +    } + +    @Test +    public void testHideNotifications_secondary_userSwitch() { +        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id); +        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + +        mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext); + +        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id)); +    } + +    @Test +    public void testShowNotifications_secondary_userSwitch() { +        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id); +        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + +        mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext); + +        assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id)); +    } + +    @Test +    public void testUserAllowsNotificationsInPublic_keyguardManagerNoPrivateNotifications() { +        // DevicePolicy allows notifications +        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id)) +                .thenReturn(0); +        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( +                0, null, null, 0, true, false, null, mCurrentUser.id, 0); +        mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr); +        mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext, +                new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); + +        // KeyguardManager does not allow notifications +        when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false); + +        // User allows notifications +        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); +        // We shouldn't need to call this method, but getPrivateNotificationsAllowed has no +        // callback, so it's only updated when the setting is +        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + +        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); +    } + +    @Test +    public void testUserAllowsNotificationsInPublic_settingsChange() { +        // User allows notifications +        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); +        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + +        assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); + +        // User disables +        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id); +        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); + +        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id)); +    } + +    private class TestNotificationLockscreenUserManager +            extends NotificationLockscreenUserManagerImpl { +        public TestNotificationLockscreenUserManager(Context context) { +            super( +                    context, +                    mBroadcastDispatcher, +                    mDevicePolicyManager, +                    mUserManager, +                    mUserTracker, +                    (() -> mVisibilityProvider), +                    (() -> mNotifCollection), +                    mClickNotifier, +                    (() -> mOverviewProxyService), +                    NotificationLockscreenUserManagerMainThreadTest.this.mKeyguardManager, +                    mStatusBarStateController, +                    Handler.createAsync(Looper.myLooper()), +                    Handler.createAsync(Looper.myLooper()), +                    mBackgroundExecutor, +                    mDeviceProvisionedController, +                    mKeyguardStateController, +                    mSettings, +                    mock(DumpManager.class), +                    mock(LockPatternUtils.class), +                    mFakeFeatureFlags); +        } + +        public BroadcastReceiver getBaseBroadcastReceiverForTest() { +            return mBaseBroadcastReceiver; +        } + +        public UserTracker.Callback getUserTrackerCallbackForTest() { +            return mUserChangedCallback; +        } + +        public ContentObserver getLockscreenSettingsObserverForTest() { +            return mLockscreenSettingsObserver; +        } + +        public ContentObserver getSettingsObserverForTest() { +            return mSettingsObserver; +        } +    } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java index 19863ecaf723..a5f5fc7e36f2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java @@ -16,17 +16,23 @@  package com.android.systemui.statusbar; +import static android.app.Notification.VISIBILITY_PRIVATE; +import static android.app.NotificationManager.IMPORTANCE_HIGH; +import static android.app.NotificationManager.VISIBILITY_NO_OVERRIDE;  import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;  import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS;  import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS;  import static android.os.UserHandle.USER_ALL; +import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS;  import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS;  import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertNotNull;  import static junit.framework.Assert.assertTrue;  import static org.mockito.ArgumentMatchers.any;  import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq;  import static org.mockito.Mockito.atMost;  import static org.mockito.Mockito.clearInvocations;  import static org.mockito.Mockito.mock; @@ -38,12 +44,15 @@ import static org.mockito.Mockito.when;  import android.app.ActivityManager;  import android.app.KeyguardManager;  import android.app.Notification; +import android.app.NotificationChannel;  import android.app.admin.DevicePolicyManager;  import android.content.BroadcastReceiver;  import android.content.Context;  import android.content.Intent;  import android.content.pm.UserInfo;  import android.database.ContentObserver; +import android.net.Uri; +import android.os.Bundle;  import android.os.Handler;  import android.os.Looper;  import android.os.UserHandle; @@ -59,6 +68,8 @@ import com.android.systemui.Dependency;  import com.android.systemui.SysuiTestCase;  import com.android.systemui.broadcast.BroadcastDispatcher;  import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FakeFeatureFlagsClassic; +import com.android.systemui.flags.Flags;  import com.android.systemui.plugins.statusbar.StatusBarStateController;  import com.android.systemui.recents.OverviewProxyService;  import com.android.systemui.settings.UserTracker; @@ -74,12 +85,16 @@ import com.android.systemui.util.settings.FakeSettings;  import com.google.android.collect.Lists;  import org.junit.Before; -import org.junit.Ignore;  import org.junit.Test;  import org.junit.runner.RunWith;  import org.mockito.Mock; +import org.mockito.Mockito;  import org.mockito.MockitoAnnotations; +import java.util.ArrayList; +import java.util.Collection; +import java.util.concurrent.Executor; +  @SmallTest  @RunWith(AndroidTestingRunner.class)  @TestableLooper.RunWithLooper @@ -121,11 +136,15 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {      private NotificationEntry mCurrentUserNotif;      private NotificationEntry mSecondaryUserNotif;      private NotificationEntry mWorkProfileNotif; +    private final FakeFeatureFlagsClassic mFakeFeatureFlags = new FakeFeatureFlagsClassic(); +    private Executor mBackgroundExecutor = Runnable::run; // Direct executor      @Before      public void setUp() {          MockitoAnnotations.initMocks(this); +        mFakeFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true); +          int currentUserId = ActivityManager.getCurrentUser();          when(mUserTracker.getUserId()).thenReturn(currentUserId);          mSettings = new FakeSettings(); @@ -138,103 +157,144 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {          when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(true);          when(mUserManager.getProfiles(currentUserId)).thenReturn(Lists.newArrayList(                  mCurrentUser, mWorkUser)); +        when(mUserManager.getUsers()).thenReturn(Lists.newArrayList( +                mCurrentUser, mWorkUser, mSecondaryUser));          when(mUserManager.getProfiles(mSecondaryUser.id)).thenReturn(Lists.newArrayList(                  mSecondaryUser));          mDependency.injectTestDependency(Dependency.MAIN_HANDLER,                  Handler.createAsync(Looper.myLooper()));          Notification notifWithPrivateVisibility = new Notification(); -        notifWithPrivateVisibility.visibility = Notification.VISIBILITY_PRIVATE; +        notifWithPrivateVisibility.visibility = VISIBILITY_PRIVATE;          mCurrentUserNotif = new NotificationEntryBuilder()                  .setNotification(notifWithPrivateVisibility)                  .setUser(new UserHandle(mCurrentUser.id))                  .build(); +        NotificationChannel channel = new NotificationChannel("1", "1", IMPORTANCE_HIGH); +        channel.setLockscreenVisibility(VISIBILITY_NO_OVERRIDE); +        mCurrentUserNotif.setRanking(new RankingBuilder(mCurrentUserNotif.getRanking()) +                .setChannel(channel) +                .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build()); +        when(mNotifCollection.getEntry(mCurrentUserNotif.getKey())).thenReturn(mCurrentUserNotif);          mSecondaryUserNotif = new NotificationEntryBuilder()                  .setNotification(notifWithPrivateVisibility)                  .setUser(new UserHandle(mSecondaryUser.id))                  .build(); +        mSecondaryUserNotif.setRanking(new RankingBuilder(mSecondaryUserNotif.getRanking()) +                .setChannel(channel) +                .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build()); +        when(mNotifCollection.getEntry( +                mSecondaryUserNotif.getKey())).thenReturn(mSecondaryUserNotif);          mWorkProfileNotif = new NotificationEntryBuilder()                  .setNotification(notifWithPrivateVisibility)                  .setUser(new UserHandle(mWorkUser.id))                  .build(); +        mWorkProfileNotif.setRanking(new RankingBuilder(mWorkProfileNotif.getRanking()) +                .setChannel(channel) +                .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build()); +        when(mNotifCollection.getEntry(mWorkProfileNotif.getKey())).thenReturn(mWorkProfileNotif);          mLockscreenUserManager = new TestNotificationLockscreenUserManager(mContext);          mLockscreenUserManager.setUpWithPresenter(mPresenter);      } +    private void changeSetting(String setting) { +        final Collection<Uri> lockScreenUris = new ArrayList<>(); +        lockScreenUris.add(Settings.Secure.getUriFor(setting)); +        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false, +            lockScreenUris, 0); +    } +      @Test      public void testLockScreenShowNotificationsFalse() {          mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0); -        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); +        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);          assertFalse(mLockscreenUserManager.shouldShowLockscreenNotifications());      }      @Test      public void testLockScreenShowNotificationsTrue() {          mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1); -        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); +        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);          assertTrue(mLockscreenUserManager.shouldShowLockscreenNotifications());      }      @Test      public void testLockScreenAllowPrivateNotificationsTrue() { -        mSettings.putInt(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1); -        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); +        mSettings.putInt(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1); +        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);          assertTrue(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id));      }      @Test      public void testLockScreenAllowPrivateNotificationsFalse() { -        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, +        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,                  mCurrentUser.id); -        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); +        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);          assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id));      }      @Test      public void testLockScreenAllowsWorkPrivateNotificationsFalse() { -        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, +        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,                  mWorkUser.id); -        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); +        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);          assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id));      }      @Test      public void testLockScreenAllowsWorkPrivateNotificationsTrue() { -        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, +        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,                  mWorkUser.id); -        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); +        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);          assertTrue(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id));      }      @Test -    public void testCurrentUserPrivateNotificationsNotRedacted() { +    public void testCurrentUserPrivateNotificationsRedacted() {          // GIVEN current user doesn't allow private notifications to show -        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, +        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,                  mCurrentUser.id); -        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); +        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);          // THEN current user's notification is redacted          assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));      }      @Test -    public void testCurrentUserPrivateNotificationsRedacted() { +    public void testCurrentUserPrivateNotificationsNotRedacted() {          // GIVEN current user allows private notifications to show -        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, +        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,                  mCurrentUser.id); -        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); +        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);          // THEN current user's notification isn't redacted          assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));      }      @Test +    public void testCurrentUserPrivateNotificationsRedactedChannel() { +        // GIVEN current user allows private notifications to show +        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, +                mCurrentUser.id); +        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); + +        // but doesn't allow it at the channel level +        NotificationChannel channel = new NotificationChannel("1", "1", IMPORTANCE_HIGH); +        channel.setLockscreenVisibility(VISIBILITY_PRIVATE); +        mCurrentUserNotif.setRanking(new RankingBuilder(mCurrentUserNotif.getRanking()) +                .setChannel(channel) +                .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build()); +        // THEN the notification is redacted +        assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif)); +    } + +    @Test      public void testWorkPrivateNotificationsRedacted() {          // GIVEN work profile doesn't private notifications to show -        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, +        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,                  mWorkUser.id); -        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); +        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);          // THEN work profile notification is redacted          assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif)); @@ -244,9 +304,9 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {      @Test      public void testWorkPrivateNotificationsNotRedacted() {          // GIVEN work profile allows private notifications to show -        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, +        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,                  mWorkUser.id); -        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); +        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);          // THEN work profile notification isn't redacted          assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif)); @@ -256,13 +316,15 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {      @Test      public void testWorkPrivateNotificationsNotRedacted_otherUsersRedacted() {          // GIVEN work profile allows private notifications to show but the other users don't -        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, +        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,                  mWorkUser.id); -        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, +        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,                  mCurrentUser.id); -        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, +        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,                  mSecondaryUser.id); -        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); +        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); +        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); +        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);          // THEN the work profile notification doesn't need to be redacted          assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif)); @@ -275,13 +337,15 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {      @Test      public void testWorkProfileRedacted_otherUsersNotRedacted() {          // GIVEN work profile doesn't allow private notifications to show but the other users do -        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, +        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,                  mWorkUser.id); -        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, +        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,                  mCurrentUser.id); -        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, +        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,                  mSecondaryUser.id); -        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); +        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); +        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); +        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);          // THEN the work profile notification needs to be redacted          assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif)); @@ -295,11 +359,12 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {      public void testSecondaryUserNotRedacted_currentUserRedacted() {          // GIVEN secondary profile allows private notifications to show but the current user          // doesn't allow private notifications to show -        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, +        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,                  mCurrentUser.id); -        mSettings.putIntForUser(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, +        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,                  mSecondaryUser.id); -        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); +        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS); +        changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);          // THEN the secondary profile notification still needs to be redacted because the current          // user's setting takes precedence @@ -323,6 +388,7 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {      @Test      public void testUpdateIsPublicMode() {          when(mKeyguardStateController.isMethodSecure()).thenReturn(true); +        when(mKeyguardStateController.isShowing()).thenReturn(false);          NotificationStateChangedListener listener = mock(NotificationStateChangedListener.class);          mLockscreenUserManager.addNotificationStateChangedListener(listener); @@ -330,24 +396,28 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {          // first call explicitly sets user 0 to not public; notifies          mLockscreenUserManager.updatePublicMode(); +        TestableLooper.get(this).processAllMessages();          assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0));          verify(listener).onNotificationStateChanged();          clearInvocations(listener);          // calling again has no changes; does not notify          mLockscreenUserManager.updatePublicMode(); +        TestableLooper.get(this).processAllMessages();          assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0));          verify(listener, never()).onNotificationStateChanged();          // Calling again with keyguard now showing makes user 0 public; notifies          when(mKeyguardStateController.isShowing()).thenReturn(true);          mLockscreenUserManager.updatePublicMode(); +        TestableLooper.get(this).processAllMessages();          assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0));          verify(listener).onNotificationStateChanged();          clearInvocations(listener);          // calling again has no changes; does not notify          mLockscreenUserManager.updatePublicMode(); +        TestableLooper.get(this).processAllMessages();          assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0));          verify(listener, never()).onNotificationStateChanged();      } @@ -356,14 +426,14 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {      public void testDevicePolicyDoesNotAllowNotifications() {          // User allows them          mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); -        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); +        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);          // DevicePolicy hides notifs on lockscreen          when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))                  .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS); -        BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class); -        when(pr.getSendingUserId()).thenReturn(mCurrentUser.id); +        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( +                0, null, null, 0, true, false, null, mCurrentUser.id, 0);          mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);          mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,                  new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); @@ -371,19 +441,18 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {          assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));      } -    @Ignore("b/286230167")      @Test      public void testDevicePolicyDoesNotAllowNotifications_userAll() {          // User allows them          mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); -        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); +        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);          // DevicePolicy hides notifications          when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))                  .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS); -        BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class); -        when(pr.getSendingUserId()).thenReturn(mCurrentUser.id); +        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( +                0, null, null, 0, true, false, null, mCurrentUser.id, 0);          mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);          mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,                  new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); @@ -392,98 +461,105 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {      }      @Test -    @Ignore("b/286230167")      public void testDevicePolicyDoesNotAllowNotifications_secondary() { +        Mockito.clearInvocations(mDevicePolicyManager);          // User allows notifications          mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id); -        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); +        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);          // DevicePolicy hides notifications          when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id))                  .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS); -        BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class); -        when(pr.getSendingUserId()).thenReturn(mSecondaryUser.id); +        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( +                0, null, null, 0, true, false, null, mSecondaryUser.id, 0);          mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);          mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,                  new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));          assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id)); -        // TODO (b/286230167): enable assertion          verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt());      }      @Test      public void testDevicePolicy_noPrivateNotifications() { +        Mockito.clearInvocations(mDevicePolicyManager);          // User allows notifications          mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); -        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); +        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);          // DevicePolicy hides sensitive content          when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))                  .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS); -        BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class); -        when(pr.getSendingUserId()).thenReturn(mCurrentUser.id); +        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( +                0, null, null, 0, true, false, null, mCurrentUser.id, 0);          mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);          mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,                  new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));          assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif)); -        // TODO (b/286230167): enable assertion. It's currently called 4 times. -        //verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt()); +        verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt());      }      @Test      public void testDevicePolicy_noPrivateNotifications_userAll() { +        NotificationChannel channel = new NotificationChannel("1", "1", IMPORTANCE_HIGH); +        channel.setLockscreenVisibility(VISIBILITY_NO_OVERRIDE); +        NotificationEntry notifEntry = new NotificationEntryBuilder() +                .setNotification(new Notification()) +                .setUser(UserHandle.ALL) +                .build(); +        notifEntry.setRanking(new RankingBuilder(notifEntry.getRanking()) +                .setChannel(channel) +                .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build()); +        when(mNotifCollection.getEntry(notifEntry.getKey())).thenReturn(notifEntry);          // User allows notifications          mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); -        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); +        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);          // DevicePolicy hides sensitive content          when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))                  .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS); -        BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class); -        when(pr.getSendingUserId()).thenReturn(mCurrentUser.id); +        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( +                0, null, null, 0, true, false, null, mCurrentUser.id, 0);          mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);          mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,                  new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); -        assertTrue(mLockscreenUserManager.needsRedaction(new NotificationEntryBuilder() -                .setNotification(new Notification()) -                .setUser(UserHandle.ALL) -                .build())); +        assertTrue(mLockscreenUserManager.needsRedaction(notifEntry));      }      @Test      public void testDevicePolicyPrivateNotifications_secondary() { +        Mockito.clearInvocations(mDevicePolicyManager);          // User allows notifications          mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id); -        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); +        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);          // DevicePolicy hides sensitive content          when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id))                  .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS); -        BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class); -        when(pr.getSendingUserId()).thenReturn(mSecondaryUser.id); +        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( +                0, null, null, 0, true, false, null, mSecondaryUser.id, 0);          mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);          mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,                  new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); +        mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);          assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif)); -        // TODO (b/286230167): enable assertion. It's currently called 5 times. -        //verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt()); +        verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), anyInt());      }      @Test      public void testHideNotifications_primary() {          mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id); -        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); +        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);          assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));      } @@ -491,16 +567,17 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {      @Test      public void testHideNotifications_secondary() {          mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id); -        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); +        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);          assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));      } -    @Ignore("b/286230167")      @Test      public void testHideNotifications_workProfile() { -        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mWorkUser.id); -        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); +        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id); +        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mWorkUser.id); +        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); +        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);          assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mWorkUser.id));      } @@ -508,67 +585,62 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {      @Test      public void testHideNotifications_secondary_userSwitch() {          mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id); +        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); -        TestNotificationLockscreenUserManager lockscreenUserManager -                = new TestNotificationLockscreenUserManager(mContext); -        lockscreenUserManager.setUpWithPresenter(mPresenter); - -        lockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext); +        mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext); -        assertFalse(lockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id)); +        assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));      }      @Test      public void testShowNotifications_secondary_userSwitch() {          mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id); +        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); -        TestNotificationLockscreenUserManager lockscreenUserManager -                = new TestNotificationLockscreenUserManager(mContext); -        lockscreenUserManager.setUpWithPresenter(mPresenter); - -        lockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext); +        mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext); -        assertTrue(lockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id)); +        assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));      } -    @Ignore("b/286230167")      @Test      public void testShouldShowLockscreenNotifications_keyguardManagerNoPrivateNotifications() { +        // KeyguardManager does not allow notifications +        when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false);          // User allows notifications          mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); -        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); +        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);          // DevicePolicy allows notifications          when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))                  .thenReturn(0); -        BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class); -        when(pr.getSendingUserId()).thenReturn(mCurrentUser.id); +        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( +                0, null, null, 0, true, false, null, mCurrentUser.id, 0);          mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);          mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,                  new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); -        // KeyguardManager does not -        when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false); -          assertFalse(mLockscreenUserManager.shouldShowLockscreenNotifications());      }      @Test      public void testUserAllowsNotificationsInPublic_keyguardManagerNoPrivateNotifications() { -        // User allows notifications -        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); -        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);          // DevicePolicy allows notifications          when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))                  .thenReturn(0); -        BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class); -        when(pr.getSendingUserId()).thenReturn(mCurrentUser.id); +        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( +                0, null, null, 0, true, false, null, mCurrentUser.id, 0);          mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);          mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,                  new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); -        // KeyguardManager does not +        // KeyguardManager does not allow notifications          when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false); +        // User allows notifications +        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); +        // We shouldn't need to call this method, but getPrivateNotificationsAllowed has no +        // callback, so it's only updated when the setting is +        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS); +          assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));      } @@ -576,31 +648,30 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {      public void testUserAllowsNotificationsInPublic_settingsChange() {          // User allows notifications          mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); -        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); +        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);          assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));          // User disables          mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id); -        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); +        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);          assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));      } -    @Ignore("b/286230167")      @Test      public void testUserAllowsNotificationsInPublic_devicePolicyChange() {          // User allows notifications          mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id); -        mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false); +        changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);          assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));          // DevicePolicy disables notifications          when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))                  .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS); -        BroadcastReceiver.PendingResult pr = mock(BroadcastReceiver.PendingResult.class); -        when(pr.getSendingUserId()).thenReturn(mCurrentUser.id); +        BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult( +                0, null, null, 0, true, false, null, mCurrentUser.id, 0);          mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);          mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,                  new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)); @@ -608,6 +679,28 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {          assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));      } +    @Test +    public void testNewUserAdded() { +        int newUserId = 14; +        mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, newUserId); +        mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, newUserId); +        when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, newUserId)) +                .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS); + +        BroadcastReceiver broadcastReceiver = +                mLockscreenUserManager.getBaseBroadcastReceiverForTest(); +        final Bundle extras = new Bundle(); +        final Intent intent = new Intent(Intent.ACTION_USER_ADDED); +        intent.putExtras(extras); +        intent.putExtra(Intent.EXTRA_USER_HANDLE, newUserId); +        broadcastReceiver.onReceive(mContext, intent); + +        verify(mDevicePolicyManager, atMost(1)).getKeyguardDisabledFeatures(any(), eq(newUserId)); + +        assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(newUserId)); +        assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(newUserId)); +    } +      private class TestNotificationLockscreenUserManager              extends NotificationLockscreenUserManagerImpl {          public TestNotificationLockscreenUserManager(Context context) { @@ -623,12 +716,17 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {                      (() -> mOverviewProxyService),                      NotificationLockscreenUserManagerTest.this.mKeyguardManager,                      mStatusBarStateController, -                    Handler.createAsync(Looper.myLooper()), +                    Handler.createAsync(TestableLooper.get( +                            NotificationLockscreenUserManagerTest.this).getLooper()), +                    Handler.createAsync(TestableLooper.get( +                            NotificationLockscreenUserManagerTest.this).getLooper()), +                    mBackgroundExecutor,                      mDeviceProvisionedController,                      mKeyguardStateController,                      mSettings,                      mock(DumpManager.class), -                    mock(LockPatternUtils.class)); +                    mock(LockPatternUtils.class), +                    mFakeFeatureFlags);          }          public BroadcastReceiver getBaseBroadcastReceiverForTest() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt index 9d6ea857fefc..cfcf4257ce28 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt @@ -48,9 +48,7 @@ class NotificationMediaManagerTest : SysuiTestCase() {      @Before      fun setUp() {          MockitoAnnotations.initMocks(this) -        doCallRealMethod() -            .whenever(notificationMediaManager) -            .updateMediaMetaData(anyBoolean(), anyBoolean()) +        doCallRealMethod().whenever(notificationMediaManager).updateMediaMetaData(anyBoolean())          doReturn(mockBackDropView).whenever(notificationMediaManager).backDropView      } @@ -62,7 +60,7 @@ class NotificationMediaManagerTest : SysuiTestCase() {          notificationMediaManager.mIsLockscreenLiveWallpaperEnabled = true          for (metaDataChanged in listOf(true, false)) {              for (allowEnterAnimation in listOf(true, false)) { -                notificationMediaManager.updateMediaMetaData(metaDataChanged, allowEnterAnimation) +                notificationMediaManager.updateMediaMetaData(metaDataChanged)                  verify(notificationMediaManager, never()).mediaMetadata              }          } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java deleted file mode 100644 index aeb5b037be0c..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - *      http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui.statusbar.notification; - -import static com.google.common.truth.Truth.assertThat; - -import android.annotation.Nullable; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.test.suitebuilder.annotation.SmallTest; - -import androidx.palette.graphics.Palette; -import androidx.test.runner.AndroidJUnit4; - -import com.android.systemui.SysuiTestCase; - -import org.junit.After; -import org.junit.Test; -import org.junit.runner.RunWith; - -@SmallTest -@RunWith(AndroidJUnit4.class) -public class MediaNotificationProcessorTest extends SysuiTestCase { - -    private static final int BITMAP_WIDTH = 10; -    private static final int BITMAP_HEIGHT = 10; - -    /** -     * Color tolerance is borrowed from the AndroidX test utilities for Palette. -     */ -    private static final int COLOR_TOLERANCE = 8; - -    @Nullable private Bitmap mArtwork; - -    @After -    public void tearDown() { -        if (mArtwork != null) { -            mArtwork.recycle(); -            mArtwork = null; -        } -    } - -    @Test -    public void findBackgroundSwatch_white() { -        // Given artwork that is completely white. -        mArtwork = Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, Bitmap.Config.ARGB_8888); -        Canvas canvas = new Canvas(mArtwork); -        canvas.drawColor(Color.WHITE); -        // WHEN the background swatch is computed -        Palette.Swatch swatch = MediaNotificationProcessor.findBackgroundSwatch(mArtwork); -        // THEN the swatch color is white -        assertCloseColors(swatch.getRgb(), Color.WHITE); -    } - -    @Test -    public void findBackgroundSwatch_red() { -        // Given artwork that is completely red. -        mArtwork = Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, Bitmap.Config.ARGB_8888); -        Canvas canvas = new Canvas(mArtwork); -        canvas.drawColor(Color.RED); -        // WHEN the background swatch is computed -        Palette.Swatch swatch = MediaNotificationProcessor.findBackgroundSwatch(mArtwork); -        // THEN the swatch color is red -        assertCloseColors(swatch.getRgb(), Color.RED); -    } - -    static void assertCloseColors(int expected, int actual) { -        assertThat((float) Color.red(expected)).isWithin(COLOR_TOLERANCE).of(Color.red(actual)); -        assertThat((float) Color.green(expected)).isWithin(COLOR_TOLERANCE).of(Color.green(actual)); -        assertThat((float) Color.blue(expected)).isWithin(COLOR_TOLERANCE).of(Color.blue(actual)); -    } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java index 83f2a5d4fbdf..b922ab39912b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java @@ -22,6 +22,7 @@ import static android.app.Notification.VISIBILITY_SECRET;  import static android.app.NotificationManager.IMPORTANCE_HIGH;  import static android.app.NotificationManager.IMPORTANCE_LOW;  import static android.app.NotificationManager.IMPORTANCE_MIN; +import static android.app.NotificationManager.VISIBILITY_NO_OVERRIDE;  import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;  import static com.android.systemui.statusbar.StatusBarState.SHADE; @@ -36,6 +37,7 @@ import static org.mockito.Mockito.mock;  import static org.mockito.Mockito.verify;  import static org.mockito.Mockito.when; +import android.app.NotificationChannel;  import android.content.Context;  import android.os.Handler;  import android.os.UserHandle; @@ -51,6 +53,9 @@ import com.android.systemui.CoreStartable;  import com.android.systemui.SysuiTestCase;  import com.android.systemui.dagger.SysUISingleton;  import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.flags.FakeFeatureFlagsClassic; +import com.android.systemui.flags.FeatureFlagsClassic; +import com.android.systemui.flags.Flags;  import com.android.systemui.plugins.statusbar.StatusBarStateController;  import com.android.systemui.settings.UserTracker;  import com.android.systemui.statusbar.NotificationLockscreenUserManager; @@ -96,6 +101,7 @@ public class KeyguardNotificationVisibilityProviderTest  extends SysuiTestCase {      @Mock private UserTracker mUserTracker;      private final FakeSettings mSecureSettings = new FakeSettings();      private final FakeGlobalSettings mGlobalSettings = new FakeGlobalSettings(); +    private final FakeFeatureFlagsClassic mFeatureFlags = new FakeFeatureFlagsClassic();      private KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider;      private NotificationEntry mEntry; @@ -116,7 +122,8 @@ public class KeyguardNotificationVisibilityProviderTest  extends SysuiTestCase {                                  mStatusBarStateController,                                  mUserTracker,                                  mSecureSettings, -                                mGlobalSettings); +                                mGlobalSettings, +                                mFeatureFlags);          mKeyguardNotificationVisibilityProvider = component.getProvider();          for (CoreStartable startable : component.getCoreStartables().values()) {              startable.start(); @@ -424,6 +431,7 @@ public class KeyguardNotificationVisibilityProviderTest  extends SysuiTestCase {      @Test      public void publicMode_settingsDisallow() { +        mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true);          // GIVEN an 'unfiltered-keyguard-showing' state          setupUnfilteredState(mEntry); @@ -433,12 +441,59 @@ public class KeyguardNotificationVisibilityProviderTest  extends SysuiTestCase {          when(mLockscreenUserManager.userAllowsNotificationsInPublic(NOTIF_USER_ID))                  .thenReturn(false); +        mEntry.setRanking(new RankingBuilder() +                .setChannel(new NotificationChannel("1", "1", 4)) +                .setVisibilityOverride(VISIBILITY_NO_OVERRIDE) +                .setKey(mEntry.getKey()).build()); + +        // THEN filter out the entry +        assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry)); +    } + +    @Test +    public void publicMode_settingsDisallow_mainThread() { +        mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, false); +        // GIVEN an 'unfiltered-keyguard-showing' state +        setupUnfilteredState(mEntry); + +        // WHEN the notification's user is in public mode and settings are configured to disallow +        // notifications in public mode +        when(mLockscreenUserManager.isLockscreenPublicMode(NOTIF_USER_ID)).thenReturn(true); +        when(mLockscreenUserManager.userAllowsNotificationsInPublic(NOTIF_USER_ID)) +                .thenReturn(false); + +        mEntry.setRanking(new RankingBuilder() +                .setChannel(new NotificationChannel("1", "1", 4)) +                .setVisibilityOverride(VISIBILITY_NO_OVERRIDE) +                .setKey(mEntry.getKey()).build()); +          // THEN filter out the entry          assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));      }      @Test      public void publicMode_notifDisallowed() { +        mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true); +        NotificationChannel channel = new NotificationChannel("1", "1", IMPORTANCE_HIGH); +        channel.setLockscreenVisibility(VISIBILITY_SECRET); +        // GIVEN an 'unfiltered-keyguard-showing' state +        setupUnfilteredState(mEntry); + +        // WHEN the notification's user is in public mode and settings are configured to disallow +        // notifications in public mode +        when(mLockscreenUserManager.isLockscreenPublicMode(CURR_USER_ID)).thenReturn(true); +        mEntry.setRanking(new RankingBuilder() +                .setKey(mEntry.getKey()) +                .setChannel(channel) +                .setVisibilityOverride(VISIBILITY_SECRET).build()); + +        // THEN filter out the entry +        assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry)); +    } + +    @Test +    public void publicMode_notifDisallowed_mainThread() { +        mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, false);          // GIVEN an 'unfiltered-keyguard-showing' state          setupUnfilteredState(mEntry); @@ -506,6 +561,54 @@ public class KeyguardNotificationVisibilityProviderTest  extends SysuiTestCase {      }      @Test +    public void notificationChannelVisibilityNoOverride() { +        mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true); +        // GIVEN a VISIBILITY_PRIVATE notification +        NotificationEntryBuilder entryBuilder = new NotificationEntryBuilder() +                .setUser(new UserHandle(NOTIF_USER_ID)); +        entryBuilder.modifyNotification(mContext) +                .setVisibility(VISIBILITY_PRIVATE); +        mEntry = entryBuilder.build(); +        // ranking says secret because of DPC or Setting +        mEntry.setRanking(new RankingBuilder() +                .setKey(mEntry.getKey()) +                .setVisibilityOverride(VISIBILITY_SECRET) +                .setImportance(IMPORTANCE_HIGH) +                .build()); + +        // WHEN we're in an 'unfiltered-keyguard-showing' state +        setupUnfilteredState(mEntry); + +        // THEN don't hide the entry based on visibility. (Redaction is handled elsewhere.) +        assertFalse(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry)); +    } + +    @Test +    public void notificationChannelVisibilitySecret() { +        mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true); +        // GIVEN a VISIBILITY_PRIVATE notification +        NotificationEntryBuilder entryBuilder = new NotificationEntryBuilder() +                .setUser(new UserHandle(NOTIF_USER_ID)); +        entryBuilder.modifyNotification(mContext) +                .setVisibility(VISIBILITY_PRIVATE); +        // And a VISIBILITY_SECRET NotificationChannel +        NotificationChannel channel = new NotificationChannel("id", "name", IMPORTANCE_HIGH); +        channel.setLockscreenVisibility(VISIBILITY_SECRET); +        mEntry = entryBuilder.build(); +        // WHEN we're in an 'unfiltered-keyguard-showing' state +        setupUnfilteredState(mEntry); +        when(mLockscreenUserManager.isLockscreenPublicMode(CURR_USER_ID)).thenReturn(true); +        when(mLockscreenUserManager.isLockscreenPublicMode(NOTIF_USER_ID)).thenReturn(true); + +        mEntry.setRanking(new RankingBuilder(mEntry.getRanking()) +                .setChannel(channel) +                .build()); + +        // THEN hide the entry based on visibility. +        assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry)); +    } + +    @Test      public void notificationVisibilityPrivate() {          // GIVEN a VISIBILITY_PRIVATE notification          NotificationEntryBuilder entryBuilder = new NotificationEntryBuilder() @@ -635,7 +738,8 @@ public class KeyguardNotificationVisibilityProviderTest  extends SysuiTestCase {                      @BindsInstance SysuiStatusBarStateController statusBarStateController,                      @BindsInstance UserTracker userTracker,                      @BindsInstance SecureSettings secureSettings, -                    @BindsInstance GlobalSettings globalSettings +                    @BindsInstance GlobalSettings globalSettings, +                    @BindsInstance FeatureFlagsClassic featureFlags              );          }      } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt index ac11ff29b83a..0a7dc4e05633 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt @@ -192,10 +192,26 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {              val isOnLockscreen by collectLastValue(underTest.isOnLockscreen)              keyguardTransitionRepository.sendTransitionStep( -                TransitionStep(to = KeyguardState.GONE, transitionState = TransitionState.FINISHED) +                TransitionStep( +                    from = KeyguardState.LOCKSCREEN, +                    to = KeyguardState.GONE, +                    value = 1f, +                    transitionState = TransitionState.FINISHED +                )              )              assertThat(isOnLockscreen).isFalse() +            // While progressing from lockscreen, should still be true +            keyguardTransitionRepository.sendTransitionStep( +                TransitionStep( +                    from = KeyguardState.LOCKSCREEN, +                    to = KeyguardState.GONE, +                    value = 0.8f, +                    transitionState = TransitionState.RUNNING +                ) +            ) +            assertThat(isOnLockscreen).isTrue() +              keyguardTransitionRepository.sendTransitionStep(                  TransitionStep(                      to = KeyguardState.LOCKSCREEN, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java index 700de5305778..8344cd143c5d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java @@ -18,7 +18,9 @@ package com.android.systemui.statusbar.phone;  import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;  import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK; +  import static com.google.common.truth.Truth.assertThat; +  import static org.mockito.ArgumentMatchers.any;  import static org.mockito.ArgumentMatchers.anyBoolean;  import static org.mockito.ArgumentMatchers.anyInt; @@ -38,7 +40,6 @@ import android.test.suitebuilder.annotation.SmallTest;  import android.testing.AndroidTestingRunner;  import android.testing.TestableLooper.RunWithLooper;  import android.testing.TestableResources; -import android.view.HapticFeedbackConstants;  import android.view.ViewRootImpl;  import com.android.internal.logging.MetricsLogger; @@ -47,6 +48,7 @@ import com.android.keyguard.KeyguardUpdateMonitor;  import com.android.keyguard.logging.BiometricUnlockLogger;  import com.android.systemui.SysuiTestCase;  import com.android.systemui.biometrics.AuthController; +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor;  import com.android.systemui.dump.DumpManager;  import com.android.systemui.flags.FakeFeatureFlags;  import com.android.systemui.keyguard.KeyguardViewMediator; @@ -122,6 +124,8 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase {      private BiometricUnlockLogger mLogger;      @Mock      private ViewRootImpl mViewRootImpl; +    @Mock +    private DeviceEntryHapticsInteractor mDeviceEntryHapticsInteractor;      private final FakeSystemClock mSystemClock = new FakeSystemClock();      private FakeFeatureFlags mFeatureFlags;      private BiometricUnlockController mBiometricUnlockController; @@ -158,7 +162,8 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase {                  mAuthController, mStatusBarStateController,                  mSessionTracker, mLatencyTracker, mScreenOffAnimationController, mVibratorHelper,                  mSystemClock, -                mFeatureFlags +                mFeatureFlags, +                mDeviceEntryHapticsInteractor          );          biometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);          biometricUnlockController.addListener(mBiometricUnlockEventsListener); @@ -462,145 +467,23 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase {      }      @Test -    public void onSideFingerprintSuccess_recentPowerButtonPress_noHaptic() { -        // GIVEN side fingerprint enrolled, last wake reason was power button -        when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); -        when(mWakefulnessLifecycle.getLastWakeReason()) -                .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON); - -        // GIVEN last wake time just occurred -        when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis()); - -        // WHEN biometric fingerprint succeeds -        givenFingerprintModeUnlockCollapsing(); -        mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT, -                true); - -        // THEN DO NOT vibrate the device -        verify(mVibratorHelper, never()).vibrateAuthSuccess(anyString()); -    } - -    @Test -    public void onSideFingerprintSuccess_oldPowerButtonPress_playHaptic() { -        // GIVEN side fingerprint enrolled, last wake reason was power button -        when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); -        when(mWakefulnessLifecycle.getLastWakeReason()) -                .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON); - -        // GIVEN last wake time was 500ms ago -        when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis()); -        mSystemClock.advanceTime(500); - -        // WHEN biometric fingerprint succeeds -        givenFingerprintModeUnlockCollapsing(); -        mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT, -                true); - -        // THEN vibrate the device -        verify(mVibratorHelper).vibrateAuthSuccess(anyString()); -    } - -    @Test -    public void onSideFingerprintSuccess_oldPowerButtonPress_playOneWayHaptic() { -        // GIVEN oneway haptics is enabled -        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true); -        // GIVEN side fingerprint enrolled, last wake reason was power button -        when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); -        when(mWakefulnessLifecycle.getLastWakeReason()) -                .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON); - -        // GIVEN last wake time was 500ms ago -        when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis()); -        mSystemClock.advanceTime(500); - +    public void onFingerprintSuccess_requestSuccessHaptic() {          // WHEN biometric fingerprint succeeds          givenFingerprintModeUnlockCollapsing();          mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT,                  true); -        // THEN vibrate the device -        verify(mVibratorHelper).performHapticFeedback( -                any(), -                eq(HapticFeedbackConstants.CONFIRM) -        ); -    } - -    @Test -    public void onSideFingerprintSuccess_recentGestureWakeUp_playHaptic() { -        // GIVEN side fingerprint enrolled, wakeup just happened -        when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); -        when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis()); - -        // GIVEN last wake reason was from a gesture -        when(mWakefulnessLifecycle.getLastWakeReason()) -                .thenReturn(PowerManager.WAKE_REASON_GESTURE); - -        // WHEN biometric fingerprint succeeds -        givenFingerprintModeUnlockCollapsing(); -        mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT, -                true); - -        // THEN vibrate the device -        verify(mVibratorHelper).vibrateAuthSuccess(anyString()); -    } - -    @Test -    public void onSideFingerprintSuccess_recentGestureWakeUp_playOnewayHaptic() { -        //GIVEN oneway haptics is enabled -        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true); -        // GIVEN side fingerprint enrolled, wakeup just happened -        when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); -        when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis()); - -        // GIVEN last wake reason was from a gesture -        when(mWakefulnessLifecycle.getLastWakeReason()) -                .thenReturn(PowerManager.WAKE_REASON_GESTURE); - -        // WHEN biometric fingerprint succeeds -        givenFingerprintModeUnlockCollapsing(); -        mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT, -                true); - -        // THEN vibrate the device -        verify(mVibratorHelper).performHapticFeedback( -                any(), -                eq(HapticFeedbackConstants.CONFIRM) -        ); -    } - -    @Test -    public void onSideFingerprintFail_alwaysPlaysHaptic() { -        // GIVEN side fingerprint enrolled, last wake reason was recent power button -        when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); -        when(mWakefulnessLifecycle.getLastWakeReason()) -                .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON); -        when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis()); - -        // WHEN biometric fingerprint fails -        mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT); -          // THEN always vibrate the device -        verify(mVibratorHelper).vibrateAuthError(anyString()); +        verify(mDeviceEntryHapticsInteractor).vibrateSuccess();      }      @Test -    public void onSideFingerprintFail_alwaysPlaysOneWayHaptic() { -        // GIVEN oneway haptics is enabled -        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true); -        // GIVEN side fingerprint enrolled, last wake reason was recent power button -        when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); -        when(mWakefulnessLifecycle.getLastWakeReason()) -                .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON); -        when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis()); - +    public void onFingerprintFail_requestErrorHaptic() {          // WHEN biometric fingerprint fails          mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);          // THEN always vibrate the device -        verify(mVibratorHelper).performHapticFeedback( -                any(), -                eq(HapticFeedbackConstants.REJECT) -        ); +        verify(mDeviceEntryHapticsInteractor).vibrateError();      }      @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java index 03f5f005ee47..3556703a2fa8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java @@ -30,9 +30,11 @@ import android.testing.AndroidTestingRunner;  import androidx.test.filters.SmallTest; -import com.android.systemui.res.R;  import com.android.systemui.SysuiTestCase;  import com.android.systemui.doze.util.BurnInHelperKt; +import com.android.systemui.log.LogBuffer; +import com.android.systemui.log.core.FakeLogBuffer; +import com.android.systemui.res.R;  import org.junit.After;  import org.junit.Before; @@ -51,8 +53,7 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase {      private static final float OPAQUE = 1.f;      private static final float TRANSPARENT = 0.f; -    @Mock -    private Resources mResources; +    @Mock private Resources mResources;      private KeyguardClockPositionAlgorithm mClockPositionAlgorithm;      private KeyguardClockPositionAlgorithm.Result mClockPosition; @@ -80,7 +81,8 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase {                  .mockStatic(BurnInHelperKt.class)                  .startMocking(); -        mClockPositionAlgorithm = new KeyguardClockPositionAlgorithm(); +        LogBuffer logBuffer = FakeLogBuffer.Factory.Companion.create(); +        mClockPositionAlgorithm = new KeyguardClockPositionAlgorithm(logBuffer);          when(mResources.getDimensionPixelSize(anyInt())).thenReturn(0);          mClockPositionAlgorithm.loadDimens(mResources); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt index e6f8c4861a94..9aafee4770de 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt @@ -16,6 +16,8 @@  package com.android.systemui.statusbar.phone +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags  import android.os.Handler  import android.os.PowerManager  import android.testing.AndroidTestingRunner @@ -80,10 +82,14 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() {      @Mock      private lateinit var handler: Handler +    private lateinit var featureFlags: FakeFeatureFlags +      @Before      fun setUp() {          MockitoAnnotations.initMocks(this) - +        featureFlags = FakeFeatureFlags().apply { +            set(Flags.MIGRATE_KEYGUARD_STATUS_VIEW, false) +        }          controller = UnlockedScreenOffAnimationController(                  context,                  wakefulnessLifecycle, @@ -95,7 +101,8 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() {                  dagger.Lazy<NotificationShadeWindowController> { notifShadeWindowController },                  interactionJankMonitor,                  powerManager, -                handler = handler +                handler = handler, +                featureFlags,          )          controller.initialize(centralSurfaces, shadeViewController, lightRevealScrim) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt index 812e91beee48..610fede3dfea 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt @@ -60,6 +60,8 @@ class FakeMobileConnectionRepository(      override val isAllowedDuringAirplaneMode = MutableStateFlow(false) +    override val hasPrioritizedNetworkCapabilities = MutableStateFlow(false) +      fun setDataEnabled(enabled: Boolean) {          _dataEnabled.value = enabled      } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt index 57f97ec66a00..1f8cc54211e6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt @@ -123,6 +123,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC                      carrierNetworkChange = testCase.carrierNetworkChange,                      roaming = testCase.roaming,                      name = "demo name", +                    slice = testCase.slice,                  )              fakeNetworkEventFlow.value = networkModel @@ -142,6 +143,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC              launch { conn.carrierName.collect {} }              launch { conn.isEmergencyOnly.collect {} }              launch { conn.dataConnectionState.collect {} } +            launch { conn.hasPrioritizedNetworkCapabilities.collect {} }          }          return job      } @@ -165,6 +167,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC                      .isEqualTo(NetworkNameModel.IntentDerived(model.name))                  assertThat(conn.carrierName.value)                      .isEqualTo(NetworkNameModel.SubscriptionDerived("${model.name} ${model.subId}")) +                assertThat(conn.hasPrioritizedNetworkCapabilities.value).isEqualTo(model.slice)                  // TODO(b/261029387): check these once we start handling them                  assertThat(conn.isEmergencyOnly.value).isFalse() @@ -190,6 +193,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC          val carrierNetworkChange: Boolean,          val roaming: Boolean,          val name: String, +        val slice: Boolean,      ) {          override fun toString(): String {              return "INPUT(level=$level, " + @@ -200,7 +204,8 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC                  "activity=$activity, " +                  "carrierNetworkChange=$carrierNetworkChange, " +                  "roaming=$roaming, " + -                "name=$name)" +                "name=$name," + +                "slice=$slice)"          }          // Convenience for iterating test data and creating new cases @@ -214,6 +219,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC              carrierNetworkChange: Boolean? = null,              roaming: Boolean? = null,              name: String? = null, +            slice: Boolean? = null,          ): TestCase =              TestCase(                  level = level ?: this.level, @@ -225,6 +231,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC                  carrierNetworkChange = carrierNetworkChange ?: this.carrierNetworkChange,                  roaming = roaming ?: this.roaming,                  name = name ?: this.name, +                slice = slice ?: this.slice,              )      } @@ -254,6 +261,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC          // false first so the base case doesn't have roaming set (more common)          private val roaming = listOf(false, true)          private val names = listOf("name 1", "name 2") +        private val slice = listOf(false, true)          @Parameters(name = "{0}") @JvmStatic fun data() = testData() @@ -291,6 +299,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC                      carrierNetworkChange.first(),                      roaming.first(),                      names.first(), +                    slice.first(),                  )              val tail = @@ -303,6 +312,7 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC                          carrierNetworkChange.map { baseCase.modifiedBy(carrierNetworkChange = it) },                          roaming.map { baseCase.modifiedBy(roaming = it) },                          names.map { baseCase.modifiedBy(name = it) }, +                        slice.map { baseCase.modifiedBy(slice = it) }                      )                      .flatten() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt index 2712b70a9745..d918fa8193bb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt @@ -549,6 +549,7 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() {              launch { conn.carrierName.collect {} }              launch { conn.isEmergencyOnly.collect {} }              launch { conn.dataConnectionState.collect {} } +            launch { conn.hasPrioritizedNetworkCapabilities.collect {} }          }          return job      } @@ -574,6 +575,7 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() {                      .isEqualTo(NetworkNameModel.IntentDerived(model.name))                  assertThat(conn.carrierName.value)                      .isEqualTo(NetworkNameModel.SubscriptionDerived("${model.name} ${model.subId}")) +                assertThat(conn.hasPrioritizedNetworkCapabilities.value).isEqualTo(model.slice)                  // TODO(b/261029387) check these once we start handling them                  assertThat(conn.isEmergencyOnly.value).isFalse() @@ -599,6 +601,7 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() {          assertThat(conn.isEmergencyOnly.value).isFalse()          assertThat(conn.isGsm.value).isFalse()          assertThat(conn.dataConnectionState.value).isEqualTo(DataConnectionState.Connected) +        assertThat(conn.hasPrioritizedNetworkCapabilities.value).isFalse()          job.cancel()      }  } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt index ede02d1fbc9c..1c21ebe05d84 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt @@ -16,6 +16,7 @@  package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod +import android.net.ConnectivityManager  import android.telephony.ServiceState  import android.telephony.SignalStrength  import android.telephony.TelephonyCallback @@ -80,6 +81,7 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() {          )      private val mobileFactory = mock<MobileConnectionRepositoryImpl.Factory>()      private val carrierMergedFactory = mock<CarrierMergedConnectionRepository.Factory>() +    private val connectivityManager = mock<ConnectivityManager>()      private val subscriptionModel =          MutableStateFlow( @@ -678,6 +680,7 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() {                  subscriptionModel,                  DEFAULT_NAME_MODEL,                  SEP, +                connectivityManager,                  telephonyManager,                  systemUiCarrierConfig = mock(),                  fakeBroadcastDispatcher, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt index 8ef82c9fe7bc..ba6426586ebd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt @@ -19,6 +19,8 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod  import android.content.BroadcastReceiver  import android.content.Context  import android.content.Intent +import android.net.ConnectivityManager +import android.net.ConnectivityManager.NetworkCallback  import android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WLAN  import android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN  import android.telephony.CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL @@ -85,7 +87,9 @@ import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityMod  import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel  import com.android.systemui.util.mockito.any  import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.mockito.mock  import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.mockito.withArgCaptor  import com.google.common.truth.Truth.assertThat  import kotlinx.coroutines.ExperimentalCoroutinesApi  import kotlinx.coroutines.flow.MutableStateFlow @@ -107,6 +111,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {      private lateinit var underTest: MobileConnectionRepositoryImpl      private lateinit var connectionsRepo: FakeMobileConnectionsRepository +    @Mock private lateinit var connectivityManager: ConnectivityManager      @Mock private lateinit var telephonyManager: TelephonyManager      @Mock private lateinit var logger: MobileInputLogger      @Mock private lateinit var tableLogger: TableLogBuffer @@ -144,6 +149,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {                  subscriptionModel,                  DEFAULT_NAME_MODEL,                  SEP, +                connectivityManager,                  telephonyManager,                  systemUiCarrierConfig,                  fakeBroadcastDispatcher, @@ -904,6 +910,45 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {              assertThat(latest).isFalse()          } +    @Test +    fun hasPrioritizedCaps_defaultFalse() { +        assertThat(underTest.hasPrioritizedNetworkCapabilities.value).isFalse() +    } + +    @Test +    fun hasPrioritizedCaps_trueWhenAvailable() = +        testScope.runTest { +            val latest by collectLastValue(underTest.hasPrioritizedNetworkCapabilities) + +            val callback: NetworkCallback = +                withArgCaptor<NetworkCallback> { +                    verify(connectivityManager).registerNetworkCallback(any(), capture()) +                } + +            callback.onAvailable(mock()) + +            assertThat(latest).isTrue() +        } + +    @Test +    fun hasPrioritizedCaps_becomesFalseWhenNetworkLost() = +        testScope.runTest { +            val latest by collectLastValue(underTest.hasPrioritizedNetworkCapabilities) + +            val callback: NetworkCallback = +                withArgCaptor<NetworkCallback> { +                    verify(connectivityManager).registerNetworkCallback(any(), capture()) +                } + +            callback.onAvailable(mock()) + +            assertThat(latest).isTrue() + +            callback.onLost(mock()) + +            assertThat(latest).isFalse() +        } +      private inline fun <reified T> getTelephonyCallbackForType(): T {          return MobileTelephonyHelpers.getTelephonyCallbackForType(telephonyManager)      } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt index 852ed2054fcd..889f60a08766 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt @@ -16,6 +16,7 @@  package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod +import android.net.ConnectivityManager  import android.telephony.ServiceState  import android.telephony.TelephonyCallback  import android.telephony.TelephonyCallback.CarrierNetworkListener @@ -96,6 +97,7 @@ class MobileConnectionTelephonySmokeTests : SysuiTestCase() {      private lateinit var underTest: MobileConnectionRepositoryImpl      private lateinit var connectionsRepo: FakeMobileConnectionsRepository +    @Mock private lateinit var connectivityManager: ConnectivityManager      @Mock private lateinit var telephonyManager: TelephonyManager      @Mock private lateinit var logger: MobileInputLogger      @Mock private lateinit var tableLogger: TableLogBuffer @@ -129,6 +131,7 @@ class MobileConnectionTelephonySmokeTests : SysuiTestCase() {                  subscriptionModel,                  DEFAULT_NAME,                  SEP, +                connectivityManager,                  telephonyManager,                  systemUiCarrierConfig,                  fakeBroadcastDispatcher, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt index 9148c7580296..18ba6c4f5d9f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt @@ -180,6 +180,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {              MobileConnectionRepositoryImpl.Factory(                  context,                  fakeBroadcastDispatcher, +                connectivityManager,                  telephonyManager = telephonyManager,                  bgDispatcher = dispatcher,                  logger = logger, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt index de2b6a850e03..5f4d7bf6f371 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt @@ -48,6 +48,8 @@ class FakeMobileIconInteractor(              NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G)          ) +    override val showSliceAttribution = MutableStateFlow(false) +      override val networkName = MutableStateFlow(NetworkNameModel.IntentDerived("demo mode"))      override val carrierName = MutableStateFlow("demo mode") diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt index 218fd33ac0fc..3936bedc1059 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt @@ -22,13 +22,15 @@ import android.testing.TestableLooper  import android.testing.TestableLooper.RunWithLooper  import android.testing.ViewUtils  import android.view.View +import android.widget.FrameLayout  import android.widget.ImageView  import androidx.test.filters.SmallTest -import com.android.systemui.res.R  import com.android.systemui.SysuiTestCase +import com.android.systemui.flags.FakeFeatureFlagsClassic +import com.android.systemui.flags.Flags  import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.res.R  import com.android.systemui.statusbar.StatusBarIconView -import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags  import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository  import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor  import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor @@ -58,8 +60,8 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() {      private lateinit var testableLooper: TestableLooper      private val testDispatcher = UnconfinedTestDispatcher()      private val testScope = TestScope(testDispatcher) +    private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) } -    @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags      @Mock private lateinit var tableLogBuffer: TableLogBuffer      @Mock private lateinit var viewLogger: MobileViewLogger      @Mock private lateinit var constants: ConnectivityConstants @@ -246,7 +248,8 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() {          testableLooper.processAllMessages()          val color = 0x12345678 -        view.onDarkChanged(arrayListOf(), 1.0f, color) +        val contrast = 0x12344321 +        view.onDarkChangedWithContrast(arrayListOf(), color, contrast)          testableLooper.processAllMessages()          assertThat(view.getIconView().imageTintList).isEqualTo(ColorStateList.valueOf(color)) @@ -267,7 +270,8 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() {          testableLooper.processAllMessages()          val color = 0x23456789 -        view.setStaticDrawableColor(color) +        val contrast = 0x12344321 +        view.setStaticDrawableColor(color, contrast)          testableLooper.processAllMessages()          assertThat(view.getIconView().imageTintList).isEqualTo(ColorStateList.valueOf(color)) @@ -275,6 +279,35 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() {          ViewUtils.detachView(view)      } +    @Test +    fun colorChange_layersUpdateWithContrast() { +        // Allow the slice, and set it to visible. This cause us to use special color logic +        flags.set(Flags.NEW_NETWORK_SLICE_UI, true) +        interactor.showSliceAttribution.value = true +        createViewModel() + +        val view = +            ModernStatusBarMobileView.constructAndBind( +                context, +                viewLogger, +                SLOT_NAME, +                viewModel, +            ) +        ViewUtils.attachView(view) +        testableLooper.processAllMessages() + +        val color = 0x23456789 +        val contrast = 0x12344321 +        view.setStaticDrawableColor(color, contrast) + +        testableLooper.processAllMessages() + +        assertThat(view.getNetTypeContainer().backgroundTintList).isEqualTo(color.colorState()) +        assertThat(view.getNetTypeView().imageTintList).isEqualTo(contrast.colorState()) + +        ViewUtils.detachView(view) +    } +      private fun View.getGroupView(): View {          return this.requireViewById(R.id.mobile_group)      } @@ -283,10 +316,20 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() {          return this.requireViewById(R.id.mobile_signal)      } +    private fun View.getNetTypeContainer(): FrameLayout { +        return this.requireViewById(R.id.mobile_type_container) +    } + +    private fun View.getNetTypeView(): ImageView { +        return this.requireViewById(R.id.mobile_type) +    } +      private fun View.getDotView(): View {          return this.requireViewById(R.id.status_bar_dot)      } +    private fun Int.colorState() = ColorStateList.valueOf(this) +      private fun createViewModel() {          viewModelCommon =              MobileIconViewModel( @@ -294,6 +337,7 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() {                  interactor,                  airplaneModeInteractor,                  constants, +                flags,                  testScope.backgroundScope,              )          viewModel = QsMobileIconViewModel(viewModelCommon) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt index 187832908e2b..1d5487f31e55 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt @@ -16,8 +16,11 @@  package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel +import androidx.test.ext.junit.runners.AndroidJUnit4  import androidx.test.filters.SmallTest  import com.android.systemui.SysuiTestCase +import com.android.systemui.flags.FakeFeatureFlagsClassic +import com.android.systemui.flags.Flags  import com.android.systemui.log.table.TableLogBuffer  import com.android.systemui.statusbar.connectivity.MobileIconCarrierIdOverridesFake  import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags @@ -47,12 +50,14 @@ import kotlinx.coroutines.test.UnconfinedTestDispatcher  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.MockitoAnnotations  @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")  @OptIn(ExperimentalCoroutinesApi::class)  @SmallTest +@RunWith(AndroidJUnit4::class)  class LocationBasedMobileIconViewModelTest : SysuiTestCase() {      private lateinit var commonImpl: MobileIconViewModelCommon      private lateinit var homeIcon: HomeMobileIconViewModel @@ -65,6 +70,7 @@ class LocationBasedMobileIconViewModelTest : SysuiTestCase() {      private lateinit var airplaneModeInteractor: AirplaneModeInteractor      private val connectivityRepository = FakeConnectivityRepository() +    private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }      @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags      @Mock private lateinit var constants: ConnectivityConstants @@ -133,6 +139,7 @@ class LocationBasedMobileIconViewModelTest : SysuiTestCase() {                  interactor,                  airplaneModeInteractor,                  constants, +                flags,                  testScope.backgroundScope,              ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt index 796d5ec3dd42..c831e62dd709 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt @@ -16,6 +16,7 @@  package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel +import androidx.test.ext.junit.runners.AndroidJUnit4  import androidx.test.filters.SmallTest  import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH  import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH_NONE @@ -26,7 +27,12 @@ import com.android.settingslib.mobile.TelephonyIcons.UNKNOWN  import com.android.systemui.SysuiTestCase  import com.android.systemui.common.shared.model.ContentDescription  import com.android.systemui.common.shared.model.Icon +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.FakeFeatureFlagsClassic +import com.android.systemui.flags.Flags +import com.android.systemui.flags.Flags.NEW_NETWORK_SLICE_UI  import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.res.R  import com.android.systemui.statusbar.connectivity.MobileIconCarrierIdOverridesFake  import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository  import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor @@ -54,12 +60,14 @@ import kotlinx.coroutines.test.runTest  import kotlinx.coroutines.yield  import org.junit.Before  import org.junit.Test +import org.junit.runner.RunWith  import org.mockito.Mock  import org.mockito.MockitoAnnotations  @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")  @OptIn(ExperimentalCoroutinesApi::class)  @SmallTest +@RunWith(AndroidJUnit4::class)  class MobileIconViewModelTest : SysuiTestCase() {      private var connectivityRepository = FakeConnectivityRepository() @@ -74,6 +82,7 @@ class MobileIconViewModelTest : SysuiTestCase() {      @Mock private lateinit var tableLogBuffer: TableLogBuffer      @Mock private lateinit var carrierConfigTracker: CarrierConfigTracker +    private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }      private val testDispatcher = UnconfinedTestDispatcher()      private val testScope = TestScope(testDispatcher) @@ -595,6 +604,46 @@ class MobileIconViewModelTest : SysuiTestCase() {              containerJob.cancel()          } +    @Test +    fun netTypeBackground_flagOff_isNull() = +        testScope.runTest { +            flags.set(NEW_NETWORK_SLICE_UI, false) +            createAndSetViewModel() + +            val latest by collectLastValue(underTest.networkTypeBackground) + +            repository.hasPrioritizedNetworkCapabilities.value = true + +            assertThat(latest).isNull() +        } + +    @Test +    fun netTypeBackground_flagOn_nullWhenNoPrioritizedCapabilities() = +        testScope.runTest { +            flags.set(NEW_NETWORK_SLICE_UI, true) +            createAndSetViewModel() + +            val latest by collectLastValue(underTest.networkTypeBackground) + +            repository.hasPrioritizedNetworkCapabilities.value = false + +            assertThat(latest).isNull() +        } + +    @Test +    fun netTypeBackground_flagOn_notNullWhenPrioritizedCapabilities() = +        testScope.runTest { +            flags.set(NEW_NETWORK_SLICE_UI, true) +            createAndSetViewModel() + +            val latest by collectLastValue(underTest.networkTypeBackground) + +            repository.hasPrioritizedNetworkCapabilities.value = true + +            assertThat(latest) +                .isEqualTo(Icon.Resource(R.drawable.mobile_network_type_background, null)) +        } +      private fun createAndSetViewModel() {          underTest =              MobileIconViewModel( @@ -602,6 +651,7 @@ class MobileIconViewModelTest : SysuiTestCase() {                  interactor,                  airplaneModeInteractor,                  constants, +                flags,                  testScope.backgroundScope,              )      } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt index eb6f2f81fde4..f3e334ed8a22 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt @@ -16,9 +16,12 @@  package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel +import androidx.test.ext.junit.runners.AndroidJUnit4  import androidx.test.filters.SmallTest  import com.android.settingslib.mobile.TelephonyIcons  import com.android.systemui.SysuiTestCase +import com.android.systemui.flags.FakeFeatureFlagsClassic +import com.android.systemui.flags.Flags  import com.android.systemui.statusbar.phone.StatusBarLocation  import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags  import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository @@ -41,15 +44,18 @@ import kotlinx.coroutines.test.UnconfinedTestDispatcher  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.MockitoAnnotations  @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")  @OptIn(ExperimentalCoroutinesApi::class)  @SmallTest +@RunWith(AndroidJUnit4::class)  class MobileIconsViewModelTest : SysuiTestCase() {      private lateinit var underTest: MobileIconsViewModel      private val interactor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock()) +    private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }      private lateinit var airplaneModeInteractor: AirplaneModeInteractor      @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags @@ -77,6 +83,7 @@ class MobileIconsViewModelTest : SysuiTestCase() {                  interactor,                  airplaneModeInteractor,                  constants, +                flags,                  testScope.backgroundScope,              ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt index e44ff8e21270..28d632d9fcea 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt @@ -41,6 +41,7 @@ class FakeConnectivityRepository : ConnectivityRepository {       * setting mobile connected && validated, since the default state is disconnected && not       * validated       */ +    @JvmOverloads      fun setMobileConnected(          default: Boolean = true,          validated: Boolean = true, @@ -53,6 +54,7 @@ class FakeConnectivityRepository : ConnectivityRepository {      }      /** Similar convenience method for ethernet */ +    @JvmOverloads      fun setEthernetConnected(          default: Boolean = true,          validated: Boolean = true, @@ -64,6 +66,7 @@ class FakeConnectivityRepository : ConnectivityRepository {              )      } +    @JvmOverloads      fun setWifiConnected(          default: Boolean = true,          validated: Boolean = true, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt index b4039d906810..028a58c6e1b0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt @@ -77,9 +77,10 @@ class ModernStatusBarViewTest : SysuiTestCase() {      fun onDarkChanged_bindingReceivesIconAndDecorTint() {          val view = createAndInitView() -        view.onDarkChanged(arrayListOf(), 1.0f, 0x12345678) +        view.onDarkChangedWithContrast(arrayListOf(), 0x12345678, 0x12344321)          assertThat(binding.iconTint).isEqualTo(0x12345678) +        assertThat(binding.contrastTint).isEqualTo(0x12344321)          assertThat(binding.decorTint).isEqualTo(0x12345678)      } @@ -87,9 +88,10 @@ class ModernStatusBarViewTest : SysuiTestCase() {      fun setStaticDrawableColor_bindingReceivesIconTint() {          val view = createAndInitView() -        view.setStaticDrawableColor(0x12345678) +        view.setStaticDrawableColor(0x12345678, 0x12344321)          assertThat(binding.iconTint).isEqualTo(0x12345678) +        assertThat(binding.contrastTint).isEqualTo(0x12344321)      }      @Test @@ -144,13 +146,15 @@ class ModernStatusBarViewTest : SysuiTestCase() {      inner class TestBinding : ModernStatusBarViewBinding {          var iconTint: Int? = null +        var contrastTint: Int? = null          var decorTint: Int? = null          var onVisibilityStateChangedCalled: Boolean = false          var shouldIconBeVisibleInternal: Boolean = true -        override fun onIconTintChanged(newTint: Int) { +        override fun onIconTintChanged(newTint: Int, contrastTint: Int) {              iconTint = newTint +            this.contrastTint = contrastTint          }          override fun onDecorTintChanged(newTint: Int) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt index c2f56654e00a..31263627213d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt @@ -201,11 +201,11 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() {          testScope.runTest {              val latest by collectLastValue(underTest.isWifiDefault) -            val wifiEntry = +            val mergedEntry =                  mock<MergedCarrierEntry>().apply {                      whenever(this.isDefaultNetwork).thenReturn(true)                  } -            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) +            whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)              getCallback().onWifiEntriesChanged()              assertThat(latest).isTrue() @@ -229,11 +229,11 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() {          testScope.runTest {              val latest by collectLastValue(underTest.isWifiDefault) -            val wifiEntry = +            val mergedEntry =                  mock<MergedCarrierEntry>().apply {                      whenever(this.isDefaultNetwork).thenReturn(false)                  } -            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) +            whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)              getCallback().onWifiEntriesChanged()              assertThat(latest).isFalse() @@ -526,13 +526,14 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() {          testScope.runTest {              val latest by collectLastValue(underTest.wifiNetwork) -            val wifiEntry = +            val mergedEntry =                  mock<MergedCarrierEntry>().apply {                      whenever(this.isPrimaryNetwork).thenReturn(true)                      whenever(this.level).thenReturn(3)                      whenever(this.subscriptionId).thenReturn(567) +                    whenever(this.isDefaultNetwork).thenReturn(true)                  } -            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) +            whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)              getCallback().onWifiEntriesChanged()              assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue() @@ -546,11 +547,12 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() {          testScope.runTest {              val latest by collectLastValue(underTest.wifiNetwork) -            val wifiEntry = +            val mergedEntry =                  mock<MergedCarrierEntry>().apply {                      whenever(this.isPrimaryNetwork).thenReturn(true) +                    whenever(this.isDefaultNetwork).thenReturn(true)                  } -            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) +            whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)              whenever(wifiManager.maxSignalLevel).thenReturn(5)              getCallback().onWifiEntriesChanged() @@ -566,12 +568,13 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() {          testScope.runTest {              val latest by collectLastValue(underTest.wifiNetwork) -            val wifiEntry = +            val mergedEntry =                  mock<MergedCarrierEntry>().apply {                      whenever(this.isPrimaryNetwork).thenReturn(true)                      whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID) +                    whenever(this.isDefaultNetwork).thenReturn(true)                  } -            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) +            whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)              getCallback().onWifiEntriesChanged() @@ -628,11 +631,12 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() {          testScope.runTest {              val latest by collectLastValue(underTest.wifiNetwork) -            val wifiEntry = +            val mergedEntry =                  mock<MergedCarrierEntry>().apply {                      whenever(this.isPrimaryNetwork).thenReturn(false)                  } -            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) +            whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry) +            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null)              getCallback().onWifiEntriesChanged()              assertThat(latest).isEqualTo(WifiNetworkModel.Inactive) @@ -717,12 +721,14 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() {          testScope.runTest {              val latest by collectLastValue(underTest.wifiNetwork) -            val wifiEntry = +            val mergedEntry =                  mock<MergedCarrierEntry>().apply {                      whenever(this.isPrimaryNetwork).thenReturn(true)                      whenever(this.level).thenReturn(3) +                    whenever(this.isDefaultNetwork).thenReturn(true)                  } -            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) +            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null) +            whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)              getCallback().onWifiEntriesChanged()              assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue() @@ -730,6 +736,7 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() {              // WHEN we lose our current network              whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null) +            whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(null)              getCallback().onWifiEntriesChanged()              // THEN we update to no network @@ -767,6 +774,56 @@ class WifiRepositoryViaTrackerLibTest : SysuiTestCase() {          }      @Test +    fun wifiNetwork_carrierMerged_default_usesCarrierMergedInfo() = +        testScope.runTest { +            val latest by collectLastValue(underTest.wifiNetwork) + +            val mergedEntry = +                mock<MergedCarrierEntry>().apply { +                    whenever(this.isPrimaryNetwork).thenReturn(true) +                    whenever(this.level).thenReturn(3) +                    whenever(this.isDefaultNetwork).thenReturn(true) +                } +            val wifiEntry = +                mock<WifiEntry>().apply { +                    whenever(this.isPrimaryNetwork).thenReturn(true) +                    whenever(this.level).thenReturn(1) +                    whenever(this.title).thenReturn(TITLE) +                } +            whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry) +            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + +            getCallback().onWifiEntriesChanged() + +            assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue() +        } + +    @Test +    fun wifiNetwork_carrierMerged_notDefault_usesConnectedInfo() = +        testScope.runTest { +            val latest by collectLastValue(underTest.wifiNetwork) + +            val mergedEntry = +                mock<MergedCarrierEntry>().apply { +                    whenever(this.isPrimaryNetwork).thenReturn(true) +                    whenever(this.level).thenReturn(3) +                    whenever(this.isDefaultNetwork).thenReturn(false) +                } +            val wifiEntry = +                mock<WifiEntry>().apply { +                    whenever(this.isPrimaryNetwork).thenReturn(true) +                    whenever(this.level).thenReturn(1) +                    whenever(this.title).thenReturn(TITLE) +                } +            whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry) +            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) + +            getCallback().onWifiEntriesChanged() + +            assertThat(latest is WifiNetworkModel.Active).isTrue() +        } + +    @Test      fun secondaryNetworks_activeEntriesEmpty_isEmpty() =          testScope.runTest {              featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt index a27f8990dec1..d75a45247cbf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt @@ -25,9 +25,9 @@ import android.view.View  import android.view.ViewGroup  import android.widget.ImageView  import androidx.test.filters.SmallTest -import com.android.systemui.res.R  import com.android.systemui.SysuiTestCase  import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.res.R  import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT  import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN  import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON @@ -229,7 +229,8 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() {          testableLooper.processAllMessages()          val color = 0x12345678 -        view.onDarkChanged(arrayListOf(), 1.0f, color) +        val contrast = 0x12344321 +        view.onDarkChangedWithContrast(arrayListOf(), color, contrast)          testableLooper.processAllMessages()          assertThat(view.getIconView().imageTintList).isEqualTo(ColorStateList.valueOf(color)) @@ -244,7 +245,8 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() {          testableLooper.processAllMessages()          val color = 0x23456789 -        view.setStaticDrawableColor(color) +        val contrast = 0x12344321 +        view.setStaticDrawableColor(color, contrast)          testableLooper.processAllMessages()          assertThat(view.getIconView().imageTintList).isEqualTo(ColorStateList.valueOf(color)) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt index 1250228e2d37..d33806e131d5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt @@ -16,6 +16,7 @@  package com.android.systemui.statusbar.policy +import com.android.systemui.flags.FakeFeatureFlags  import android.testing.AndroidTestingRunner  import android.testing.TestableLooper  import android.testing.ViewUtils @@ -77,11 +78,13 @@ class KeyguardQsUserSwitchControllerTest : SysuiTestCase() {      private lateinit var view: FrameLayout      private lateinit var testableLooper: TestableLooper      private lateinit var keyguardQsUserSwitchController: KeyguardQsUserSwitchController +    private lateinit var featureFlags: FakeFeatureFlags      @Before      fun setUp() {          MockitoAnnotations.initMocks(this)          testableLooper = TestableLooper.get(this) +        featureFlags = FakeFeatureFlags()          view = LayoutInflater.from(context)                  .inflate(R.layout.keyguard_qs_user_switch, null) as FrameLayout @@ -98,6 +101,7 @@ class KeyguardQsUserSwitchControllerTest : SysuiTestCase() {                  dozeParameters,                  screenOffAnimationController,                  userSwitchDialogController, +                featureFlags,                  uiEventLogger)          ViewUtils.attachView(view) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java index f7e0120d3843..6ef812be0350 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java @@ -15,6 +15,8 @@   */  package com.android.systemui; +import static com.android.systemui.Flags.FLAG_EXAMPLE_FLAG; +  import static org.mockito.Mockito.mock;  import static org.mockito.Mockito.spy;  import static org.mockito.Mockito.when; @@ -25,6 +27,7 @@ import android.os.Handler;  import android.os.Looper;  import android.os.MessageQueue;  import android.os.ParcelFileDescriptor; +import android.platform.test.flag.junit.SetFlagsRule;  import android.testing.DexmakerShareClassLoaderRule;  import android.testing.LeakCheck;  import android.testing.TestWithLooperRule; @@ -68,6 +71,9 @@ public abstract class SysuiTestCase {              new AndroidXAnimatorIsolationRule();      @Rule +    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + +    @Rule      public SysuiTestableContext mContext = new SysuiTestableContext(              InstrumentationRegistry.getContext(), getLeakCheck());      @Rule @@ -88,6 +94,10 @@ public abstract class SysuiTestCase {          if (isRobolectricTest()) {              mContext = mContext.createDefaultDisplayContext();          } +        // Set the value of a single gantry flag inside the com.android.systemui package to +        // ensure all flags in that package are faked (and thus require a value to be set). +        mSetFlagsRule.disableFlags(FLAG_EXAMPLE_FLAG); +          mDependency = SysuiTestDependencyKt.installSysuiTestDependency(mContext);          mFakeBroadcastDispatcher = new FakeBroadcastDispatcher(                  mContext, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt index 10b284a03a59..5dcc7423ecc6 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt @@ -47,7 +47,7 @@ class FakeConfigurationRepository @Inject constructor() : ConfigurationRepositor      }      override fun getDimensionPixelSize(id: Int): Int { -        throw IllegalStateException("Don't use for tests") +        return 0      }  } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt index 1a8c5830e453..30132f7747b7 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt @@ -1,5 +1,6 @@  package com.android.systemui.communal.data.repository +import com.android.systemui.communal.data.model.CommunalWidgetMetadata  import com.android.systemui.communal.shared.CommunalAppWidgetInfo  import kotlinx.coroutines.flow.Flow  import kotlinx.coroutines.flow.MutableStateFlow @@ -8,6 +9,7 @@ import kotlinx.coroutines.flow.MutableStateFlow  class FakeCommunalWidgetRepository : CommunalWidgetRepository {      private val _stopwatchAppWidgetInfo = MutableStateFlow<CommunalAppWidgetInfo?>(null)      override val stopwatchAppWidgetInfo: Flow<CommunalAppWidgetInfo?> = _stopwatchAppWidgetInfo +    override var communalWidgetAllowlist: List<CommunalWidgetMetadata> = emptyList()      fun setStopwatchAppWidgetInfo(appWidgetInfo: CommunalAppWidgetInfo) {          _stopwatchAppWidgetInfo.value = appWidgetInfo diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt index e91e9559fa1e..852611230623 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt @@ -34,7 +34,7 @@ class FakeBiometricSettingsRepository : BiometricSettingsRepository {          get() = _isFingerprintAuthCurrentlyAllowed      private val _isFaceAuthEnrolledAndEnabled = MutableStateFlow(false) -    override val isFaceAuthEnrolledAndEnabled: Flow<Boolean> +    override val isFaceAuthEnrolledAndEnabled: StateFlow<Boolean>          get() = _isFaceAuthEnrolledAndEnabled      private val _isFaceAuthCurrentlyAllowed = MutableStateFlow(false) diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/core/FakeLogBuffer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/log/core/FakeLogBuffer.kt index 272d686e974b..272d686e974b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/log/core/FakeLogBuffer.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/log/core/FakeLogBuffer.kt diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeSensorManager.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeSensorManager.java index 197873f15d0d..288dcfe2825d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeSensorManager.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeSensorManager.java @@ -186,7 +186,7 @@ public class FakeSensorManager extends SensorManager {      }      @Override -    protected boolean initDataInjectionImpl(boolean enable) { +    protected boolean initDataInjectionImpl(boolean enable, @DataInjectionMode int mode) {          return false;      } diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto index 21d09792f1c7..b403a7fe8f12 100644 --- a/proto/src/system_messages.proto +++ b/proto/src/system_messages.proto @@ -404,5 +404,9 @@ message SystemMessage {      // Notify the user that audio was lowered based on Calculated Sound Dose (CSD)      NOTE_CSD_LOWER_AUDIO = 1007; + +    // Notify the user about external display events related to screenshot. +    // Package: com.android.systemui +    NOTE_GLOBAL_SCREENSHOT_EXTERNAL_DISPLAY = 1008;    }  } diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig index 10ac2ebc9b2f..f09cb19d0c1d 100644 --- a/services/accessibility/accessibility.aconfig +++ b/services/accessibility/accessibility.aconfig @@ -48,3 +48,17 @@ flag {      description: "Stops using the deprecated PackageListObserver."      bug: "304561459"  } + +flag { +    name: "scan_packages_without_lock" +    namespace: "accessibility" +    description: "Scans packages for accessibility service/activity info without holding the A11yMS lock" +    bug: "295969873" +} + +flag { +    name: "reduce_touch_exploration_sensitivity" +    namespace: "accessibility" +    description: "Reduces touch exploration sensitivity by only sending a hover event when the ifnger has moved the amount of pixels defined by the system's touch slop." +    bug: "303677860" +} diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index aa6d800510e1..8c1d444006bd 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -290,14 +290,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub      private final Set<ComponentName> mTempComponentNameSet = new HashSet<>(); -    private final List<AccessibilityServiceInfo> mTempAccessibilityServiceInfoList = -            new ArrayList<>(); -      private final IntArray mTempIntArray = new IntArray(0);      private final RemoteCallbackList<IAccessibilityManagerClient> mGlobalClients =              new RemoteCallbackList<>(); +    private PackageMonitor mPackageMonitor; +      @VisibleForTesting      final SparseArray<AccessibilityUserState> mUserStates = new SparseArray<>(); @@ -531,6 +530,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub          disableAccessibilityMenuToMigrateIfNeeded();      } +    /** +     * Returns if the current thread is holding {@link #mLock}. Used for testing +     * deadlock bug fixes. +     * +     * <p><strong>Warning:</strong> this should not be used for production logic +     * because by the time you receive an answer it may no longer be valid. +     * </p> +     */ +    @VisibleForTesting +    boolean unsafeIsLockHeld() { +        return Thread.holdsLock(mLock); +    } +      @Override      public int getCurrentUserIdLocked() {          return mCurrentUserId; @@ -690,6 +702,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub          }      } +    private void onSomePackagesChangedLocked( +            @Nullable List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos, +            @Nullable List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos) { +        final AccessibilityUserState userState = getCurrentUserStateLocked(); +        // Reload the installed services since some services may have different attributes +        // or resolve info (does not support equals), etc. Remove them then to force reload. +        userState.mInstalledServices.clear(); +        if (readConfigurationForUserStateLocked(userState, +                    parsedAccessibilityServiceInfos, parsedAccessibilityShortcutInfos)) { +            onUserStateChangedLocked(userState); +        } +    } +      private void onPackageRemovedLocked(String packageName) {          final AccessibilityUserState userState = getCurrentUserState();          final Predicate<ComponentName> filter = @@ -721,8 +746,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub          }      } +    @VisibleForTesting +    PackageMonitor getPackageMonitor() { +        return mPackageMonitor; +    } +      private void registerBroadcastReceivers() { -        PackageMonitor monitor = new PackageMonitor() { +        mPackageMonitor = new PackageMonitor() {              @Override              public void onSomePackagesChanged() {                  if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_PACKAGE_BROADCAST_RECEIVER)) { @@ -730,13 +760,25 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub                              FLAGS_PACKAGE_BROADCAST_RECEIVER);                  } +                final int userId = getChangingUserId(); +                List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = null; +                List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = null; +                if (Flags.scanPackagesWithoutLock()) { +                    parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId); +                    parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId); +                }                  synchronized (mLock) {                      // Only the profile parent can install accessibility services.                      // Therefore we ignore packages from linked profiles. -                    if (getChangingUserId() != mCurrentUserId) { +                    if (userId != mCurrentUserId) {                          return;                      } -                    onSomePackagesChangedLocked(); +                    if (Flags.scanPackagesWithoutLock()) { +                        onSomePackagesChangedLocked(parsedAccessibilityServiceInfos, +                                parsedAccessibilityShortcutInfos); +                    } else { +                        onSomePackagesChangedLocked(); +                    }                  }              } @@ -751,8 +793,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub                              FLAGS_PACKAGE_BROADCAST_RECEIVER,                              "packageName=" + packageName + ";uid=" + uid);                  } +                final int userId = getChangingUserId(); +                List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = null; +                List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = null; +                if (Flags.scanPackagesWithoutLock()) { +                    parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId); +                    parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId); +                }                  synchronized (mLock) { -                    final int userId = getChangingUserId();                      if (userId != mCurrentUserId) {                          return;                      } @@ -765,8 +813,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub                      // Reloads the installed services info to make sure the rebound service could                      // get a new one.                      userState.mInstalledServices.clear(); -                    final boolean configurationChanged = -                            readConfigurationForUserStateLocked(userState); +                    final boolean configurationChanged; +                    if (Flags.scanPackagesWithoutLock()) { +                        configurationChanged = readConfigurationForUserStateLocked(userState, +                                parsedAccessibilityServiceInfos, parsedAccessibilityShortcutInfos); +                    } else { +                        configurationChanged = readConfigurationForUserStateLocked(userState); +                    }                      if (reboundAService || configurationChanged) {                          onUserStateChangedLocked(userState);                      } @@ -839,7 +892,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub          };          // package changes -        monitor.register(mContext, null,  UserHandle.ALL, true); +        mPackageMonitor.register(mContext, null,  UserHandle.ALL, true);          if (!Flags.deprecatePackageListObserver()) {              final PackageManagerInternal pm = LocalServices.getService( @@ -1831,8 +1884,15 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub          mA11yWindowManager.onTouchInteractionEnd();      } -    private void switchUser(int userId) { +    @VisibleForTesting +    void switchUser(int userId) {          mMagnificationController.updateUserIdIfNeeded(userId); +        List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = null; +        List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = null; +        if (Flags.scanPackagesWithoutLock()) { +            parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId); +            parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId); +        }          synchronized (mLock) {              if (mCurrentUserId == userId && mInitialized) {                  return; @@ -1857,7 +1917,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub              mCurrentUserId = userId;              AccessibilityUserState userState = getCurrentUserStateLocked(); -            readConfigurationForUserStateLocked(userState); +            if (Flags.scanPackagesWithoutLock()) { +                readConfigurationForUserStateLocked(userState, +                        parsedAccessibilityServiceInfos, parsedAccessibilityShortcutInfos); +            } else { +                readConfigurationForUserStateLocked(userState); +            }              mSecurityPolicy.onSwitchUserLocked(mCurrentUserId, userState.mEnabledServices);              // Even if reading did not yield change, we have to update              // the state since the context in which the current user @@ -2105,8 +2170,17 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub          }      } -    private boolean readInstalledAccessibilityServiceLocked(AccessibilityUserState userState) { -        mTempAccessibilityServiceInfoList.clear(); +    /** +     * Finds packages that provide AccessibilityService interfaces, and parses +     * their metadata XML to build up {@link AccessibilityServiceInfo} objects. +     * +     * <p> +     * <strong>Note:</strong> XML parsing is a resource-heavy operation that may +     * stall, so this method should not be called while holding a lock. +     * </p> +     */ +    private List<AccessibilityServiceInfo> parseAccessibilityServiceInfos(int userId) { +        List<AccessibilityServiceInfo> result = new ArrayList<>();          int flags = PackageManager.GET_SERVICES                  | PackageManager.GET_META_DATA @@ -2114,12 +2188,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub                  | PackageManager.MATCH_DIRECT_BOOT_AWARE                  | PackageManager.MATCH_DIRECT_BOOT_UNAWARE; -        if (userState.getBindInstantServiceAllowedLocked()) { -            flags |= PackageManager.MATCH_INSTANT; +        synchronized (mLock) { +            if (getUserStateLocked(userId).getBindInstantServiceAllowedLocked()) { +                flags |= PackageManager.MATCH_INSTANT; +            }          }          List<ResolveInfo> installedServices = mPackageManager.queryIntentServicesAsUser( -                new Intent(AccessibilityService.SERVICE_INTERFACE), flags, mCurrentUserId); +                new Intent(AccessibilityService.SERVICE_INTERFACE), flags, userId);          for (int i = 0, count = installedServices.size(); i < count; i++) {              ResolveInfo resolveInfo = installedServices.get(i); @@ -2132,40 +2208,60 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub              AccessibilityServiceInfo accessibilityServiceInfo;              try {                  accessibilityServiceInfo = new AccessibilityServiceInfo(resolveInfo, mContext); -                if (!accessibilityServiceInfo.isWithinParcelableSize()) { -                    Slog.e(LOG_TAG, "Skipping service " -                            + accessibilityServiceInfo.getResolveInfo().getComponentInfo() -                            + " because service info size is larger than safe parcelable limits."); -                    continue; -                } -                if (userState.mCrashedServices.contains(serviceInfo.getComponentName())) { -                    // Restore the crashed attribute. -                    accessibilityServiceInfo.crashed = true; -                } -                mTempAccessibilityServiceInfoList.add(accessibilityServiceInfo);              } catch (XmlPullParserException | IOException xppe) {                  Slog.e(LOG_TAG, "Error while initializing AccessibilityServiceInfo", xppe); +                continue; +            } +            if (!accessibilityServiceInfo.isWithinParcelableSize()) { +                Slog.e(LOG_TAG, "Skipping service " +                        + accessibilityServiceInfo.getResolveInfo().getComponentInfo() +                        + " because service info size is larger than safe parcelable limits."); +                continue;              } +            result.add(accessibilityServiceInfo);          } +        return result; +    } -        if (!mTempAccessibilityServiceInfoList.equals(userState.mInstalledServices)) { +    private boolean readInstalledAccessibilityServiceLocked(AccessibilityUserState userState, +            @Nullable List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos) { +        for (int i = 0, count = parsedAccessibilityServiceInfos.size(); i < count; i++) { +            AccessibilityServiceInfo accessibilityServiceInfo = +                    parsedAccessibilityServiceInfos.get(i); +            if (userState.mCrashedServices.contains(accessibilityServiceInfo.getComponentName())) { +                // Restore the crashed attribute. +                accessibilityServiceInfo.crashed = true; +            } +        } + +        if (!parsedAccessibilityServiceInfos.equals(userState.mInstalledServices)) {              userState.mInstalledServices.clear(); -            userState.mInstalledServices.addAll(mTempAccessibilityServiceInfoList); -            mTempAccessibilityServiceInfoList.clear(); +            userState.mInstalledServices.addAll(parsedAccessibilityServiceInfos);              return true;          } - -        mTempAccessibilityServiceInfoList.clear();          return false;      } -    private boolean readInstalledAccessibilityShortcutLocked(AccessibilityUserState userState) { -        final List<AccessibilityShortcutInfo> shortcutInfos = AccessibilityManager -                .getInstance(mContext).getInstalledAccessibilityShortcutListAsUser( -                        mContext, mCurrentUserId); -        if (!shortcutInfos.equals(userState.mInstalledShortcuts)) { +    /** +     * Returns the {@link AccessibilityShortcutInfo}s of the installed +     * accessibility shortcut targets for the given user. +     * +     * <p> +     * <strong>Note:</strong> XML parsing is a resource-heavy operation that may +     * stall, so this method should not be called while holding a lock. +     * </p> +     */ +    private List<AccessibilityShortcutInfo> parseAccessibilityShortcutInfos(int userId) { +        // TODO: b/297279151 - This should be implemented here, not by AccessibilityManager. +        return AccessibilityManager.getInstance(mContext) +                .getInstalledAccessibilityShortcutListAsUser(mContext, userId); +    } + +    private boolean readInstalledAccessibilityShortcutLocked(AccessibilityUserState userState, +            List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos) { +        if (!parsedAccessibilityShortcutInfos.equals(userState.mInstalledShortcuts)) {              userState.mInstalledShortcuts.clear(); -            userState.mInstalledShortcuts.addAll(shortcutInfos); +            userState.mInstalledShortcuts.addAll(parsedAccessibilityShortcutInfos);              return true;          }          return false; @@ -2890,9 +2986,23 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub          userState.setFilterKeyEventsEnabledLocked(false);      } +    // ErrorProne doesn't understand that this method is only called while locked, +    // returning an error for accessing mCurrentUserId. +    @SuppressWarnings("GuardedBy")      private boolean readConfigurationForUserStateLocked(AccessibilityUserState userState) { -        boolean somethingChanged = readInstalledAccessibilityServiceLocked(userState); -        somethingChanged |= readInstalledAccessibilityShortcutLocked(userState); +        return readConfigurationForUserStateLocked(userState, +                parseAccessibilityServiceInfos(mCurrentUserId), +                parseAccessibilityShortcutInfos(mCurrentUserId)); +    } + +    private boolean readConfigurationForUserStateLocked( +            AccessibilityUserState userState, +            List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos, +            List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos) { +        boolean somethingChanged = readInstalledAccessibilityServiceLocked( +                userState, parsedAccessibilityServiceInfos); +        somethingChanged |= readInstalledAccessibilityShortcutLocked( +                userState, parsedAccessibilityShortcutInfos);          somethingChanged |= readEnabledAccessibilityServicesLocked(userState);          somethingChanged |= readTouchExplorationGrantedAccessibilityServicesLocked(userState);          somethingChanged |= readTouchExplorationEnabledSettingLocked(userState); diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java index c4184854e690..fc8d4f89e6a7 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java @@ -882,10 +882,22 @@ public class TouchExplorer extends BaseEventStreamTransformation          final int pointerIndex = event.findPointerIndex(pointerId);          switch (event.getPointerCount()) {              case 1: -            // Touch exploration. +                // Touch exploration.                  sendTouchExplorationGestureStartAndHoverEnterIfNeeded(policyFlags); -                mDispatcher.sendMotionEvent( -                        event, ACTION_HOVER_MOVE, rawEvent, pointerIdBits, policyFlags); +                if (Flags.reduceTouchExplorationSensitivity() +                        && mState.getLastInjectedHoverEvent() != null) { +                    final MotionEvent lastEvent = mState.getLastInjectedHoverEvent(); +                    final float deltaX = lastEvent.getX() - rawEvent.getX(); +                    final float deltaY = lastEvent.getY() - rawEvent.getY(); +                    final double moveDelta = Math.hypot(deltaX, deltaY); +                    if (moveDelta > mTouchSlop) { +                        mDispatcher.sendMotionEvent( +                                event, ACTION_HOVER_MOVE, rawEvent, pointerIdBits, policyFlags); +                    } +                } else { +                    mDispatcher.sendMotionEvent( +                            event, ACTION_HOVER_MOVE, rawEvent, pointerIdBits, policyFlags); +                }                  break;              case 2:                  if (mGestureDetector.isMultiFingerGesturesEnabled() diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index 72242d265e79..e4f1d3acce6d 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -363,6 +363,7 @@ public final class AutofillManagerService                  case AutofillFeatureFlags.DEVICE_CONFIG_AUTOFILL_PCC_FEATURE_PROVIDER_HINTS:                  case AutofillFeatureFlags.DEVICE_CONFIG_PREFER_PROVIDER_OVER_PCC:                  case AutofillFeatureFlags.DEVICE_CONFIG_PCC_USE_FALLBACK: +                case Flags.FLAG_AUTOFILL_CREDMAN_INTEGRATION:                      setDeviceConfigProperties();                      break;                  case AutofillFeatureFlags.DEVICE_CONFIG_AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES: 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 f5562d27f4e8..4298c079a63e 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -632,6 +632,10 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub      public void setDevicePolicy(@VirtualDeviceParams.DynamicPolicyType int policyType,              @VirtualDeviceParams.DevicePolicy int devicePolicy) {          super.setDevicePolicy_enforcePermission(); +        if (!Flags.dynamicPolicy()) { +            return; +        } +          switch (policyType) {              case POLICY_TYPE_RECENTS:                  synchronized (mVirtualDeviceLock) { diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java index 1a8dd3a7316e..65975e44ee2a 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java @@ -28,6 +28,7 @@ import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_OK;  import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_SECURITY_EXCEPTION;  import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_TRUE;  import static android.view.contentcapture.ContentCaptureSession.STATE_DISABLED; +import static android.view.contentprotection.flags.Flags.parseGroupsConfigEnabled;  import static com.android.internal.util.FrameworkStatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__ACCEPT_DATA_SHARE_REQUEST;  import static com.android.internal.util.FrameworkStatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__DATA_SHARE_ERROR_CLIENT_PIPE_FAIL; @@ -115,6 +116,7 @@ import java.io.InputStream;  import java.io.OutputStream;  import java.io.PrintWriter;  import java.util.ArrayList; +import java.util.Arrays;  import java.util.Collections;  import java.util.HashSet;  import java.util.List; @@ -142,6 +144,9 @@ public class ContentCaptureManagerService extends      private static final int MAX_CONCURRENT_FILE_SHARING_REQUESTS = 10;      private static final int DATA_SHARE_BYTE_BUFFER_LENGTH = 1_024; +    private static final String CONTENT_PROTECTION_GROUP_CONFIG_SEPARATOR_GROUP = ";"; +    private static final String CONTENT_PROTECTION_GROUP_CONFIG_SEPARATOR_VALUE = ","; +      // Needed to pass checkstyle_hook as names are too long for one line.      private static final int EVENT__DATA_SHARE_ERROR_CONCURRENT_REQUEST =              CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__DATA_SHARE_ERROR_CONCURRENT_REQUEST; @@ -957,14 +962,40 @@ public class ContentCaptureManagerService extends          return mContentCaptureManagerServiceStub;      } -    /** @hide */ +    /** +     * Parses a simple config in format "group;group" where each "group" is itself in the format of +     * "string1,string2", eg: +     * +     * <p>"a" -> [["a"]] +     * +     * <p>"a,b" -> [["a", "b"]] +     * +     * <p>"a,b;c;d,e" -> [["a", "b"], ["c"], ["d", "e"]] +     * +     * @hide +     */      @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)      @NonNull      protected List<List<String>> parseContentProtectionGroupsConfig(@Nullable String config) {          if (verbose) {              Slog.v(TAG, "parseContentProtectionGroupsConfig: " + config);          } -        return Collections.emptyList(); +        if (!parseGroupsConfigEnabled()) { +            return Collections.emptyList(); +        } +        if (config == null) { +            return Collections.emptyList(); +        } +        return Arrays.stream(config.split(CONTENT_PROTECTION_GROUP_CONFIG_SEPARATOR_GROUP)) +                .map(this::parseContentProtectionGroupConfigValues) +                .filter(group -> !group.isEmpty()) +                .toList(); +    } + +    private List<String> parseContentProtectionGroupConfigValues(@NonNull String group) { +        return Arrays.stream(group.split(CONTENT_PROTECTION_GROUP_CONFIG_SEPARATOR_VALUE)) +                .filter(value -> !value.isEmpty()) +                .toList();      }      final class ContentCaptureManagerServiceStub extends IContentCaptureManager.Stub { @@ -1406,6 +1437,10 @@ public class ContentCaptureManagerService extends                  if (!mDevCfgEnableContentProtectionReceiver) {                      return false;                  } +                if (mDevCfgContentProtectionRequiredGroups.isEmpty() +                        && mDevCfgContentProtectionOptionalGroups.isEmpty()) { +                    return false; +                }              }              return mContentProtectionConsentManager.isConsentGranted(userId)                      && mContentProtectionBlocklistManager.isAllowed(packageName); diff --git a/services/core/Android.bp b/services/core/Android.bp index 6dd32bc2c9bb..1d02e4c88b74 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -182,7 +182,6 @@ java_library_static {          "android.hidl.manager-V1.2-java",          "cbor-java",          "display_flags_lib", -        "dropbox_flags_lib",          "icu4j_calendar_astronomer",          "android.security.aaid_aidl-java",          "netd-client", @@ -192,7 +191,7 @@ java_library_static {          "ImmutabilityAnnotation",          "securebox",          "apache-commons-math", -        "power_optimization_flags_lib", +        "backstage_power_flags_lib",          "notification_flags_lib",          "camera_platform_flags_core_java_lib",          "biometrics_flags_lib", diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index 8df54569cccd..638abdba36ec 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -1387,11 +1387,12 @@ public abstract class PackageManagerInternal {              @UserIdInt int userId);      /** -     * Tells PackageManager when a component (except BroadcastReceivers) of the package is used +     * Tells PackageManager when a component of the package is used       * and the package should get out of stopped state and be enabled.       */      public abstract void notifyComponentUsed(@NonNull String packageName, -            @UserIdInt int userId, @NonNull String recentCallingPackage, @NonNull String debugInfo); +            @UserIdInt int userId, @Nullable String recentCallingPackage, +            @NonNull String debugInfo);      /** @deprecated For legacy shell command only. */      @Deprecated diff --git a/services/core/java/com/android/server/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java index f82a6aabfefb..55069b779a37 100644 --- a/services/core/java/com/android/server/DropBoxManagerService.java +++ b/services/core/java/com/android/server/DropBoxManagerService.java @@ -16,14 +16,10 @@  package com.android.server; -import android.Manifest;  import android.annotation.Nullable;  import android.app.ActivityManager;  import android.app.AppOpsManager;  import android.app.BroadcastOptions; -import android.app.compat.CompatChanges; -import android.compat.annotation.ChangeId; -import android.compat.annotation.EnabledAfter;  import android.content.BroadcastReceiver;  import android.content.ContentResolver;  import android.content.Context; @@ -34,7 +30,6 @@ import android.content.res.Resources;  import android.database.ContentObserver;  import android.net.Uri;  import android.os.Binder; -import android.os.Build;  import android.os.Bundle;  import android.os.BundleMerger;  import android.os.Debug; @@ -71,7 +66,6 @@ import com.android.internal.util.DumpUtils;  import com.android.internal.util.FrameworkStatsLog;  import com.android.internal.util.ObjectUtils;  import com.android.server.DropBoxManagerInternal.EntrySource; -import com.android.server.feature.flags.Flags;  import libcore.io.IoUtils; @@ -95,13 +89,6 @@ import java.util.zip.GZIPOutputStream;   * Clients use {@link DropBoxManager} to access this service.   */  public final class DropBoxManagerService extends SystemService { -    /** -     * For Android U and earlier versions, apps can continue to use the READ_LOGS permission, -     * but for all subsequent versions, the READ_DROPBOX_DATA permission must be used. -     */ -    @ChangeId -    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) -    private static final long ENFORCE_READ_DROPBOX_DATA = 296060945L;      private static final String TAG = "DropBoxManagerService";      private static final int DEFAULT_AGE_SECONDS = 3 * 86400;      private static final int DEFAULT_MAX_FILES = 1000; @@ -122,6 +109,7 @@ public final class DropBoxManagerService extends SystemService {      // Tags that we should drop by default.      private static final List<String> DISABLED_BY_DEFAULT_TAGS =              List.of("data_app_wtf", "system_app_wtf", "system_server_wtf"); +      // TODO: This implementation currently uses one file per entry, which is      // inefficient for smallish entries -- consider using a single queue file      // per tag (or even globally) instead. @@ -303,21 +291,8 @@ public final class DropBoxManagerService extends SystemService {              if (!DropBoxManagerService.this.mBooted) {                  intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);              } -            if (Flags.enableReadDropboxPermission()) { -                BroadcastOptions unbundledOptions = (options == null) -                        ? BroadcastOptions.makeBasic() : BroadcastOptions.fromBundle(options); - -                unbundledOptions.setRequireCompatChange(ENFORCE_READ_DROPBOX_DATA, true); -                getContext().sendBroadcastAsUser(intent, UserHandle.ALL, -                        Manifest.permission.READ_DROPBOX_DATA, unbundledOptions.toBundle()); - -                unbundledOptions.setRequireCompatChange(ENFORCE_READ_DROPBOX_DATA, false); -                getContext().sendBroadcastAsUser(intent, UserHandle.ALL, -                        Manifest.permission.READ_LOGS, unbundledOptions.toBundle()); -            } else { -                getContext().sendBroadcastAsUser(intent, UserHandle.ALL, -                        android.Manifest.permission.READ_LOGS, options); -            } +            getContext().sendBroadcastAsUser(intent, UserHandle.ALL, +                    android.Manifest.permission.READ_LOGS, options);          }          private Intent createIntent(String tag, long time) { @@ -597,16 +572,9 @@ public final class DropBoxManagerService extends SystemService {              return true;          } - -        String permission = Manifest.permission.READ_LOGS; -        if (Flags.enableReadDropboxPermission() -                && CompatChanges.isChangeEnabled(ENFORCE_READ_DROPBOX_DATA, callingUid)) { -            permission = Manifest.permission.READ_DROPBOX_DATA; -        } -          // Callers always need this permission -        getContext().enforceCallingOrSelfPermission(permission, TAG); - +        getContext().enforceCallingOrSelfPermission( +                android.Manifest.permission.READ_LOGS, TAG);          // Callers also need the ability to read usage statistics          switch (getContext().getSystemService(AppOpsManager.class).noteOp( diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 962f38f10b5d..beea063221fb 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -214,9 +214,12 @@ class StorageManagerService extends IStorageManager.Stub      // external storage service.      public static final int FAILED_MOUNT_RESET_TIMEOUT_SECONDS = 10; -     /** Extended timeout for the system server watchdog. */ +    /** Extended timeout for the system server watchdog. */      private static final int SLOW_OPERATION_WATCHDOG_TIMEOUT_MS = 60 * 1000; +    /** Extended timeout for the system server watchdog for vold#partition operation. */ +    private static final int PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS = 3 * 60 * 1000; +      @GuardedBy("mLock")      private final Set<Integer> mFuseMountedUser = new ArraySet<>(); @@ -2338,6 +2341,8 @@ class StorageManagerService extends IStorageManager.Stub          try {              // TODO(b/135341433): Remove cautious logging when FUSE is stable              Slog.i(TAG, "Mounting volume " + vol); +            Watchdog.getInstance().setOneOffTimeoutForMonitors( +                    SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#mount might be slow");              mVold.mount(vol.id, vol.mountFlags, vol.mountUserId, new IVoldMountCallback.Stub() {                  @Override                  public boolean onVolumeChecking(FileDescriptor fd, String path, @@ -2463,10 +2468,12 @@ class StorageManagerService extends IStorageManager.Stub      @android.annotation.EnforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS)      @Override      public void partitionPublic(String diskId) { -          super.partitionPublic_enforcePermission();          final CountDownLatch latch = findOrCreateDiskScanLatch(diskId); + +        Watchdog.getInstance().setOneOffTimeoutForMonitors( +                PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS, "#partition might be very slow");          try {              mVold.partition(diskId, IVold.PARTITION_TYPE_PUBLIC, -1);              waitForLatch(latch, "partitionPublic", 3 * DateUtils.MINUTE_IN_MILLIS); @@ -2483,6 +2490,9 @@ class StorageManagerService extends IStorageManager.Stub          enforceAdminUser();          final CountDownLatch latch = findOrCreateDiskScanLatch(diskId); + +        Watchdog.getInstance().setOneOffTimeoutForMonitors( +                PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS, "#partition might be very slow");          try {              mVold.partition(diskId, IVold.PARTITION_TYPE_PRIVATE, -1);              waitForLatch(latch, "partitionPrivate", 3 * DateUtils.MINUTE_IN_MILLIS); @@ -2499,6 +2509,9 @@ class StorageManagerService extends IStorageManager.Stub          enforceAdminUser();          final CountDownLatch latch = findOrCreateDiskScanLatch(diskId); + +        Watchdog.getInstance().setOneOffTimeoutForMonitors( +                PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS, "#partition might be very slow");          try {              mVold.partition(diskId, IVold.PARTITION_TYPE_MIXED, ratio);              waitForLatch(latch, "partitionMixed", 3 * DateUtils.MINUTE_IN_MILLIS); diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 5fb889a23fc5..1650a96a4012 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -5309,7 +5309,7 @@ public class AccountManagerService              if (Log.isLoggable(TAG, Log.VERBOSE)) {                  Log.v(TAG, "performing bindService to " + authenticatorInfo.componentName);              } -            long flags = Context.BIND_FILTER_OUT_QUARANTINED_COMPONENTS | Context.BIND_AUTO_CREATE; +            long flags = Context.BIND_AUTO_CREATE;              if (mAuthenticatorCache.getBindInstantServiceAllowed(mAccounts.userId)) {                  flags |= Context.BIND_ALLOW_INSTANT;              } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 0956c6ded013..5f1a7e7e8123 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -3678,8 +3678,8 @@ public final class ActiveServices {                  || (flags & Context.BIND_EXTERNAL_SERVICE_LONG) != 0;          final boolean allowInstant = (flags & Context.BIND_ALLOW_INSTANT) != 0;          final boolean inSharedIsolatedProcess = (flags & Context.BIND_SHARED_ISOLATED_PROCESS) != 0; -        final boolean filterOutQuarantined = -                (flags & Context.BIND_FILTER_OUT_QUARANTINED_COMPONENTS) != 0; +        final boolean matchQuarantined = +                (flags & Context.BIND_MATCH_QUARANTINED_COMPONENTS) != 0;          ProcessRecord attributedApp = null;          if (sdkSandboxClientAppUid > 0) { @@ -3689,7 +3689,7 @@ public final class ActiveServices {                  isSdkSandboxService, sdkSandboxClientAppUid, sdkSandboxClientAppPackage,                  resolvedType, callingPackage, callingPid, callingUid, userId, true, callerFg,                  isBindExternal, allowInstant, null /* fgsDelegateOptions */, -                inSharedIsolatedProcess, filterOutQuarantined); +                inSharedIsolatedProcess, matchQuarantined);          if (res == null) {              return 0;          } @@ -4202,7 +4202,7 @@ public final class ActiveServices {                  sdkSandboxClientAppUid, sdkSandboxClientAppPackage, resolvedType, callingPackage,                  callingPid, callingUid, userId, createIfNeeded, callingFromFg, isBindExternal,                  allowInstant, fgsDelegateOptions, inSharedIsolatedProcess, -                false /* filterOutQuarantined */); +                false /* matchQuarantined */);      }      private ServiceLookupResult retrieveServiceLocked(Intent service, @@ -4211,7 +4211,7 @@ public final class ActiveServices {              String callingPackage, int callingPid, int callingUid, int userId,              boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal,              boolean allowInstant, ForegroundServiceDelegationOptions fgsDelegateOptions, -            boolean inSharedIsolatedProcess, boolean filterOutQuarantined) { +            boolean inSharedIsolatedProcess, boolean matchQuarantined) {          if (isSdkSandboxService && instanceName == null) {              throw new IllegalArgumentException("No instanceName provided for sdk sandbox process");          } @@ -4333,8 +4333,8 @@ public final class ActiveServices {                  if (allowInstant) {                      flags |= PackageManager.MATCH_INSTANT;                  } -                if (filterOutQuarantined) { -                    flags |= PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS; +                if (matchQuarantined) { +                    flags |= PackageManager.MATCH_QUARANTINED_COMPONENTS;                  }                  // TODO: come back and remove this assumption to triage all services                  ResolveInfo rInfo = mAm.getPackageManagerInternal().resolveService(service, diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index b43b986064fe..31817f1c427d 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -58,7 +58,6 @@ import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_INSTRUMENTAT  import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_PERSISTENT;  import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_SYSTEM;  import static android.content.pm.ApplicationInfo.HIDDEN_API_ENFORCEMENT_DEFAULT; -import static android.content.pm.PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS;  import static android.content.pm.PackageManager.GET_SHARED_LIBRARY_FILES;  import static android.content.pm.PackageManager.MATCH_ALL;  import static android.content.pm.PackageManager.MATCH_ANY_USER; @@ -14295,8 +14294,7 @@ public class ActivityManagerService extends IActivityManager.Stub      private List<ResolveInfo> collectReceiverComponents(Intent intent, String resolvedType,              int callingUid, int[] users, int[] broadcastAllowList) {          // TODO: come back and remove this assumption to triage all broadcasts -        long pmFlags = STOCK_PM_FLAGS | MATCH_DEBUG_TRIAGED_MISSING -                | FILTER_OUT_QUARANTINED_COMPONENTS; +        long pmFlags = STOCK_PM_FLAGS | MATCH_DEBUG_TRIAGED_MISSING;          List<ResolveInfo> receivers = null;          HashSet<ComponentName> singleUserReceivers = null; diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 2249607863d5..0ab81a5e4eac 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -91,6 +91,7 @@ import android.telephony.ModemActivityInfo;  import android.telephony.NetworkRegistrationInfo;  import android.telephony.SignalStrength;  import android.telephony.TelephonyManager; +import android.util.AtomicFile;  import android.util.IndentingPrintWriter;  import android.util.Slog;  import android.util.StatsEvent; @@ -99,8 +100,10 @@ import com.android.internal.R;  import com.android.internal.annotations.GuardedBy;  import com.android.internal.app.IBatteryStats;  import com.android.internal.os.BinderCallsStats; +import com.android.internal.os.Clock;  import com.android.internal.os.CpuScalingPolicies;  import com.android.internal.os.CpuScalingPolicyReader; +import com.android.internal.os.MonotonicClock;  import com.android.internal.os.PowerProfile;  import com.android.internal.os.RailStats;  import com.android.internal.os.RpmStats; @@ -114,16 +117,21 @@ import com.android.server.Watchdog;  import com.android.server.net.BaseNetworkObserver;  import com.android.server.pm.UserManagerInternal;  import com.android.server.power.optimization.Flags; +import com.android.server.power.stats.AggregatedPowerStatsConfig;  import com.android.server.power.stats.BatteryExternalStatsWorker;  import com.android.server.power.stats.BatteryStatsImpl;  import com.android.server.power.stats.BatteryUsageStatsProvider; -import com.android.server.power.stats.BatteryUsageStatsStore; +import com.android.server.power.stats.PowerStatsAggregator; +import com.android.server.power.stats.PowerStatsScheduler; +import com.android.server.power.stats.PowerStatsStore;  import com.android.server.power.stats.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;  import com.android.server.power.stats.wakeups.CpuWakeupStats;  import java.io.File;  import java.io.FileDescriptor; +import java.io.FileOutputStream;  import java.io.IOException; +import java.io.InputStream;  import java.io.PrintWriter;  import java.nio.ByteBuffer;  import java.nio.CharBuffer; @@ -136,6 +144,7 @@ import java.util.HashMap;  import java.util.List;  import java.util.Map;  import java.util.Objects; +import java.util.Properties;  import java.util.concurrent.CountDownLatch;  import java.util.concurrent.ExecutionException;  import java.util.concurrent.Future; @@ -153,20 +162,24 @@ public final class BatteryStatsService extends IBatteryStats.Stub      static final String TAG = "BatteryStatsService";      static final String TRACE_TRACK_WAKEUP_REASON = "wakeup_reason";      static final boolean DBG = false; -    private static final boolean BATTERY_USAGE_STORE_ENABLED = true;      private static IBatteryStats sService;      private final PowerProfile mPowerProfile;      private final CpuScalingPolicies mCpuScalingPolicies; +    private final MonotonicClock mMonotonicClock;      private final BatteryStatsImpl.BatteryStatsConfig mBatteryStatsConfig;      final BatteryStatsImpl mStats;      final CpuWakeupStats mCpuWakeupStats; -    private final BatteryUsageStatsStore mBatteryUsageStatsStore; +    private final PowerStatsStore mPowerStatsStore; +    private final PowerStatsAggregator mPowerStatsAggregator; +    private final PowerStatsScheduler mPowerStatsScheduler;      private final BatteryStatsImpl.UserInfoProvider mUserManagerUserInfoProvider;      private final Context mContext;      private final BatteryExternalStatsWorker mWorker;      private final BatteryUsageStatsProvider mBatteryUsageStatsProvider; +    private final AtomicFile mConfigFile; +      private volatile boolean mMonitorEnabled = true;      private native void getRailEnergyPowerStats(RailStats railStats); @@ -376,6 +389,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub          mHandlerThread.start();          mHandler = new Handler(mHandlerThread.getLooper()); +        mMonotonicClock = new MonotonicClock(new File(systemDir, "monotonic_clock.xml"));          mPowerProfile = new PowerProfile(context);          mCpuScalingPolicies = new CpuScalingPolicyReader().read(); @@ -391,23 +405,43 @@ public final class BatteryStatsService extends IBatteryStats.Stub                          .setResetOnUnplugAfterSignificantCharge(resetOnUnplugAfterSignificantCharge)                          .setPowerStatsThrottlePeriodCpu(powerStatsThrottlePeriodCpu)                          .build(); -        mStats = new BatteryStatsImpl(mBatteryStatsConfig, systemDir, handler, this, -                this, mUserManagerUserInfoProvider, mPowerProfile, mCpuScalingPolicies); +        mStats = new BatteryStatsImpl(mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock, +                systemDir, handler, this, this, mUserManagerUserInfoProvider, mPowerProfile, +                mCpuScalingPolicies);          mWorker = new BatteryExternalStatsWorker(context, mStats);          mStats.setExternalStatsSyncLocked(mWorker);          mStats.setRadioScanningTimeoutLocked(mContext.getResources().getInteger(                  com.android.internal.R.integer.config_radioScanningTimeout) * 1000L);          mStats.startTrackingSystemServerCpuTime(); -        if (BATTERY_USAGE_STORE_ENABLED) { -            mBatteryUsageStatsStore = -                    new BatteryUsageStatsStore(context, mStats, systemDir, mHandler); -        } else { -            mBatteryUsageStatsStore = null; -        } +        AggregatedPowerStatsConfig aggregatedPowerStatsConfig = getAggregatedPowerStatsConfig(); +        mPowerStatsStore = new PowerStatsStore(systemDir, mHandler, aggregatedPowerStatsConfig);          mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mStats, -                mBatteryUsageStatsStore); +                mPowerStatsStore); +        mPowerStatsAggregator = new PowerStatsAggregator(aggregatedPowerStatsConfig, +                mStats.getHistory()); +        final long aggregatedPowerStatsSpanDuration = context.getResources().getInteger( +                com.android.internal.R.integer.config_aggregatedPowerStatsSpanDuration); +        final long powerStatsAggregationPeriod = context.getResources().getInteger( +                com.android.internal.R.integer.config_powerStatsAggregationPeriod); +        mPowerStatsScheduler = new PowerStatsScheduler(context, mPowerStatsAggregator, +                aggregatedPowerStatsSpanDuration, powerStatsAggregationPeriod, mPowerStatsStore, +                Clock.SYSTEM_CLOCK, mMonotonicClock, mHandler, mStats, mBatteryUsageStatsProvider);          mCpuWakeupStats = new CpuWakeupStats(context, R.xml.irq_device_map, mHandler); +        mConfigFile = new AtomicFile(new File(systemDir, "battery_usage_stats_config")); +    } + +    private AggregatedPowerStatsConfig getAggregatedPowerStatsConfig() { +        AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig(); +        config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_CPU) +                .trackDeviceStates( +                        AggregatedPowerStatsConfig.STATE_POWER, +                        AggregatedPowerStatsConfig.STATE_SCREEN) +                .trackUidStates( +                        AggregatedPowerStatsConfig.STATE_POWER, +                        AggregatedPowerStatsConfig.STATE_SCREEN, +                        AggregatedPowerStatsConfig.STATE_PROCESS_STATE); +        return config;      }      /** @@ -466,9 +500,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub       */      public void onSystemReady() {          mStats.onSystemReady(); -        if (BATTERY_USAGE_STORE_ENABLED) { -            mBatteryUsageStatsStore.onSystemReady(); -        } +        mPowerStatsScheduler.start(Flags.streamlinedBatteryStats());      }      private final class LocalService extends BatteryStatsInternal { @@ -639,6 +671,9 @@ public final class BatteryStatsService extends IBatteryStats.Stub          // Shutdown the thread we made.          mWorker.shutdown(); + +        // To insure continuity, write the monotonic timeshift after writing the last history event +        mMonotonicClock.write();      }      public static IBatteryStats getService() { @@ -892,12 +927,8 @@ public final class BatteryStatsService extends IBatteryStats.Stub                      bus = getBatteryUsageStats(List.of(queryPowerProfile)).get(0);                      break;                  case FrameworkStatsLog.BATTERY_USAGE_STATS_BEFORE_RESET: -                    if (!BATTERY_USAGE_STORE_ENABLED) { -                        return StatsManager.PULL_SKIP; -                    } - -                    final long sessionStart = mBatteryUsageStatsStore -                            .getLastBatteryUsageStatsBeforeResetAtomPullTimestamp(); +                    final long sessionStart = +                            getLastBatteryUsageStatsBeforeResetAtomPullTimestamp();                      final long sessionEnd;                      synchronized (mStats) {                          sessionEnd = mStats.getStartClockTime(); @@ -910,8 +941,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub                                      .aggregateSnapshots(sessionStart, sessionEnd)                                      .build();                      bus = getBatteryUsageStats(List.of(queryBeforeReset)).get(0); -                    mBatteryUsageStatsStore -                            .setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(sessionEnd); +                    setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(sessionEnd);                      break;                  default:                      throw new UnsupportedOperationException("Unknown tagId=" + atomTag); @@ -2641,7 +2671,15 @@ public final class BatteryStatsService extends IBatteryStats.Stub      }      private void dumpAggregatedStats(PrintWriter pw) { -        mStats.dumpAggregatedStats(pw, /* startTime */ 0, /* endTime */0); +        mPowerStatsScheduler.aggregateAndDumpPowerStats(pw); +    } + +    private void dumpPowerStatsStore(PrintWriter pw) { +        mPowerStatsStore.dump(new IndentingPrintWriter(pw, "  ")); +    } + +    private void dumpPowerStatsStoreTableOfContents(PrintWriter pw) { +        mPowerStatsStore.dumpTableOfContents(new IndentingPrintWriter(pw, "  "));      }      private void dumpMeasuredEnergyStats(PrintWriter pw) { @@ -2789,7 +2827,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub                      synchronized (mStats) {                          mStats.resetAllStatsAndHistoryLocked(                                  BatteryStatsImpl.RESET_REASON_ADB_COMMAND); -                        mBatteryUsageStatsStore.removeAllSnapshots(); +                        mPowerStatsStore.reset();                          pw.println("Battery stats and history reset.");                          noOutput = true;                      } @@ -2891,6 +2929,12 @@ public final class BatteryStatsService extends IBatteryStats.Stub                  } else if ("--aggregated".equals(arg)) {                      dumpAggregatedStats(pw);                      return; +                } else if ("--store".equals(arg)) { +                    dumpPowerStatsStore(pw); +                    return; +                } else if ("--store-toc".equals(arg)) { +                    dumpPowerStatsStoreTableOfContents(pw); +                    return;                  } else if ("-a".equals(arg)) {                      flags |= BatteryStats.DUMP_VERBOSE;                  } else if (arg.length() > 0 && arg.charAt(0) == '-'){ @@ -2951,7 +2995,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub                                  in.unmarshall(raw, 0, raw.length);                                  in.setDataPosition(0);                                  BatteryStatsImpl checkinStats = new BatteryStatsImpl( -                                        mBatteryStatsConfig, +                                        mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock,                                          null, mStats.mHandler, null, null,                                          mUserManagerUserInfoProvider, mPowerProfile,                                          mCpuScalingPolicies); @@ -2993,7 +3037,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub                                  in.unmarshall(raw, 0, raw.length);                                  in.setDataPosition(0);                                  BatteryStatsImpl checkinStats = new BatteryStatsImpl( -                                        mBatteryStatsConfig, +                                        mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock,                                          null, mStats.mHandler, null, null,                                          mUserManagerUserInfoProvider, mPowerProfile,                                          mCpuScalingPolicies); @@ -3360,6 +3404,52 @@ public final class BatteryStatsService extends IBatteryStats.Stub          }      } +    private static final String BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY = +            "BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP"; + +    /** +     * Saves the supplied timestamp of the BATTERY_USAGE_STATS_BEFORE_RESET statsd atom pull +     * in persistent file. +     */ +    public void setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(long timestamp) { +        synchronized (mConfigFile) { +            Properties props = new Properties(); +            try (InputStream in = mConfigFile.openRead()) { +                props.load(in); +            } catch (IOException e) { +                Slog.e(TAG, "Cannot load config file " + mConfigFile, e); +            } +            props.put(BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY, +                    String.valueOf(timestamp)); +            FileOutputStream out = null; +            try { +                out = mConfigFile.startWrite(); +                props.store(out, "Statsd atom pull timestamps"); +                mConfigFile.finishWrite(out); +            } catch (IOException e) { +                mConfigFile.failWrite(out); +                Slog.e(TAG, "Cannot save config file " + mConfigFile, e); +            } +        } +    } + +    /** +     * Retrieves the previously saved timestamp of the last BATTERY_USAGE_STATS_BEFORE_RESET +     * statsd atom pull. +     */ +    public long getLastBatteryUsageStatsBeforeResetAtomPullTimestamp() { +        synchronized (mConfigFile) { +            Properties props = new Properties(); +            try (InputStream in = mConfigFile.openRead()) { +                props.load(in); +            } catch (IOException e) { +                Slog.e(TAG, "Cannot load config file " + mConfigFile, e); +            } +            return Long.parseLong( +                    props.getProperty(BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY, "0")); +        } +    } +      /**       * Sets battery AC charger to enabled/disabled, and freezes the battery state.       */ diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java index 127c5b389d79..3c56752d08e3 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java @@ -1440,10 +1440,9 @@ public class BroadcastQueueImpl extends BroadcastQueue {                      r.curComponent.getPackageName(), r.userId, Event.APP_COMPONENT_USED);          } -        // Broadcast is being executed, its package can't be stopped.          try { -            mService.mPackageManagerInt.setPackageStoppedState( -                    r.curComponent.getPackageName(), false, r.userId); +            mService.mPackageManagerInt.notifyComponentUsed( +                    r.curComponent.getPackageName(), r.userId, r.callerPackage, r.toString());          } catch (IllegalArgumentException e) {              Slog.w(TAG, "Failed trying to unstop package "                      + r.curComponent.getPackageName() + ": " + e); diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index d19eae5b0709..b48169788180 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -1982,8 +1982,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue {          mService.notifyPackageUse(receiverPackageName,                  PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER); -        mService.mPackageManagerInt.setPackageStoppedState( -                receiverPackageName, false, r.userId); +        mService.mPackageManagerInt.notifyComponentUsed( +                receiverPackageName, r.userId, r.callerPackage, r.toString());      }      private void reportUsageStatsBroadcastDispatched(@NonNull ProcessRecord app, diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index 3771c05a294e..f02b8c737f90 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -511,8 +511,8 @@ class ProcessRecord implements WindowProcessListener {          pw.print(prefix); pw.print("pid="); pw.println(mPid);          pw.print(prefix); pw.print("lastActivityTime=");          TimeUtils.formatDuration(mLastActivityTime, nowUptime, pw); -        pw.print(prefix); pw.print("startUptimeTime="); -        TimeUtils.formatDuration(mStartElapsedTime, nowUptime, pw); +        pw.print(prefix); pw.print("startUpTime="); +        TimeUtils.formatDuration(mStartUptime, nowUptime, pw);          pw.print(prefix); pw.print("startElapsedTime=");          TimeUtils.formatDuration(mStartElapsedTime, nowElapsedTime, pw);          pw.println(); diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java index 1ba1f55af71b..2d231b3cf42e 100644 --- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java @@ -152,6 +152,7 @@ public class SettingsToPropertiesMapper {          "preload_safety",          "responsible_apis",          "rust", +        "safety_center",          "system_performance",          "test_suites",          "text", @@ -159,13 +160,23 @@ public class SettingsToPropertiesMapper {          "tv_system_ui",          "vibrator",          "virtual_devices", +        "wear_calling_messaging", +        "wear_connectivity", +        "wear_esim_carriers",          "wear_frameworks", +        "wear_health_services", +        "wear_media", +        "wear_offload", +        "wear_security",          "wear_system_health",          "wear_systems",          "window_surfaces", -        "windowing_frontend" +        "windowing_frontend",      }; +    public static final String NAMESPACE_REBOOT_STAGING = "staged"; +    public static final String NAMESPACE_REBOOT_STAGING_DELIMITER = "*"; +      private final String[] mGlobalSettings;      private final String[] mDeviceConfigScopes; @@ -261,6 +272,22 @@ public class SettingsToPropertiesMapper {                          }                      });          } + +        // add sys prop sync callback for staged flag values +        DeviceConfig.addOnPropertiesChangedListener( +            NAMESPACE_REBOOT_STAGING, +            AsyncTask.THREAD_POOL_EXECUTOR, +            (DeviceConfig.Properties properties) -> { +              String scope = properties.getNamespace(); +              for (String key : properties.getKeyset()) { +                String aconfigPropertyName = makeAconfigFlagStagedPropertyName(key); +                if (aconfigPropertyName == null) { +                    log("unable to construct system property for " + scope + "/" + key); +                    return; +                } +                setProperty(aconfigPropertyName, properties.getString(key, null)); +              } +            });      }      public static SettingsToPropertiesMapper start(ContentResolver contentResolver) { @@ -332,6 +359,35 @@ public class SettingsToPropertiesMapper {      }      /** +     * system property name constructing rule for staged aconfig flags, the flag name +     * is in the form of [namespace]*[actual flag name], we should push the following +     * to system properties +     * "next_boot.[actual sys prop name]". +     * If the name contains invalid characters or substrings for system property name, +     * will return null. +     * @param flagName +     * @return +     */ +    @VisibleForTesting +    static String makeAconfigFlagStagedPropertyName(String flagName) { +        int idx = flagName.indexOf(NAMESPACE_REBOOT_STAGING_DELIMITER); +        if (idx == -1 || idx == flagName.length() - 1 || idx == 0) { +            log("invalid staged flag: " + flagName); +            return null; +        } + +        String propertyName = "next_boot." + makeAconfigFlagPropertyName( +                flagName.substring(0, idx), flagName.substring(idx+1)); + +        if (!propertyName.matches(SYSTEM_PROPERTY_VALID_CHARACTERS_REGEX) +                || propertyName.contains(SYSTEM_PROPERTY_INVALID_SUBSTRING)) { +            return null; +        } + +        return propertyName; +    } + +    /**       * system property name constructing rule for aconfig flags:       * "persist.device_config.aconfig_flags.[category_name].[flag_name]".       * If the name contains invalid characters or substrings for system property name, diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig index 26d99d843c7e..bb9ea285385b 100644 --- a/services/core/java/com/android/server/am/flags.aconfig +++ b/services/core/java/com/android/server/am/flags.aconfig @@ -2,7 +2,7 @@ package: "com.android.server.am"  flag {      name: "oomadjuster_correctness_rewrite" -    namespace: "android_platform_power_optimization" +    namespace: "backstage_power"      description: "Utilize new OomAdjuster implementation"      bug: "298055811"      is_fixed_read_only: true diff --git a/services/core/java/com/android/server/audio/MusicFxHelper.java b/services/core/java/com/android/server/audio/MusicFxHelper.java index 6c0fef5f628d..5f4e4c3bc4e0 100644 --- a/services/core/java/com/android/server/audio/MusicFxHelper.java +++ b/services/core/java/com/android/server/audio/MusicFxHelper.java @@ -157,7 +157,8 @@ public class MusicFxHelper {              Log.w(TAG, " inside handle MSG_EFFECT_CLIENT_GONE");              // Once the uid is no longer running, close all remain audio session(s) for this UID              if (mClientUidSessionMap.get(Integer.valueOf(uid)) != null) { -                final List<Integer> sessions = mClientUidSessionMap.get(Integer.valueOf(uid)); +                final List<Integer> sessions = +                        new ArrayList(mClientUidSessionMap.get(Integer.valueOf(uid)));                  Log.i(TAG, "UID " + uid + " gone, closing " + sessions.size() + " sessions");                  for (Integer session : sessions) {                      Intent intent = new Intent( diff --git a/services/core/java/com/android/server/biometrics/biometrics.aconfig b/services/core/java/com/android/server/biometrics/biometrics.aconfig index b537e0eae548..b12d831ffe24 100644 --- a/services/core/java/com/android/server/biometrics/biometrics.aconfig +++ b/services/core/java/com/android/server/biometrics/biometrics.aconfig @@ -6,3 +6,10 @@ flag {    description: "This flag controls tunscany virtual HAL feature"    bug: "294254230"  } + +flag { +    name: "de_hidl" +    namespace: "biometrics_framework" +    description: "feature flag for biometrics de-hidl" +    bug: "287332354" +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java index 78c95ad4576b..a47135fd032f 100644 --- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java @@ -35,6 +35,7 @@ import android.util.EventLog;  import android.util.Slog;  import com.android.server.biometrics.BiometricsProto; +import com.android.server.biometrics.Flags;  import com.android.server.biometrics.Utils;  import com.android.server.biometrics.log.BiometricContext;  import com.android.server.biometrics.log.BiometricLogger; @@ -116,7 +117,24 @@ public abstract class AuthenticationClient<T, O extends AuthenticateOptions>      @LockoutTracker.LockoutMode      public int handleFailedAttempt(int userId) { -        return LockoutTracker.LOCKOUT_NONE; +        if (Flags.deHidl()) { +            if (mLockoutTracker != null) { +                mLockoutTracker.addFailedAttemptForUser(getTargetUserId()); +            } +            @LockoutTracker.LockoutMode final int lockoutMode = +                    getLockoutTracker().getLockoutModeForUser(userId); +            final PerformanceTracker performanceTracker = +                    PerformanceTracker.getInstanceForSensorId(getSensorId()); +            if (lockoutMode == LockoutTracker.LOCKOUT_PERMANENT) { +                performanceTracker.incrementPermanentLockoutForUser(userId); +            } else if (lockoutMode == LockoutTracker.LOCKOUT_TIMED) { +                performanceTracker.incrementTimedLockoutForUser(userId); +            } + +            return lockoutMode; +        } else { +            return LockoutTracker.LOCKOUT_NONE; +        }      }      protected long getStartTimeMs() { diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java index 8a54ae5a6fea..037ea38a2d17 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java +++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java @@ -586,4 +586,13 @@ public class BiometricScheduler {              }          }, 10000);      } + +    /** +     * Handle stop user client when user switching occurs. +     */ +    public void onUserStopped() {} + +    public Handler getHandler() { +        return mHandler; +    }  } diff --git a/services/core/java/com/android/server/biometrics/sensors/LockoutCache.java b/services/core/java/com/android/server/biometrics/sensors/LockoutCache.java index 95c49032c029..35e9bcbbf085 100644 --- a/services/core/java/com/android/server/biometrics/sensors/LockoutCache.java +++ b/services/core/java/com/android/server/biometrics/sensors/LockoutCache.java @@ -32,6 +32,7 @@ public class LockoutCache implements LockoutTracker {          mUserLockoutStates = new SparseIntArray();      } +    @Override      public void setLockoutModeForUser(int userId, @LockoutMode int mode) {          Slog.d(TAG, "Lockout for user: " + userId +  " is " + mode);          synchronized (this) { diff --git a/services/core/java/com/android/server/biometrics/sensors/LockoutTracker.java b/services/core/java/com/android/server/biometrics/sensors/LockoutTracker.java index 4a59c9df9bef..8271380010e2 100644 --- a/services/core/java/com/android/server/biometrics/sensors/LockoutTracker.java +++ b/services/core/java/com/android/server/biometrics/sensors/LockoutTracker.java @@ -35,4 +35,7 @@ public interface LockoutTracker {      @interface LockoutMode {}      @LockoutMode int getLockoutModeForUser(int userId); +    void setLockoutModeForUser(int userId, @LockoutMode int mode); +    default void resetFailedAttemptsForUser(boolean clearAttemptCounter, int userId) {} +    default void addFailedAttemptForUser(int userId) {}  } diff --git a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java index 694dfd28d0cc..80754702415a 100644 --- a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java +++ b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java @@ -165,6 +165,7 @@ public class UserAwareBiometricScheduler extends BiometricScheduler {          }      } +    @Override      public void onUserStopped() {          if (mStopUserClient == null) {              Slog.e(getTag(), "Unexpected onUserStopped"); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java index cc0022703745..e5d4a635876d 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/LockoutHalImpl.java @@ -31,6 +31,11 @@ public class LockoutHalImpl implements LockoutTracker {          return mCurrentUserLockoutMode;      } +    @Override +    public void setLockoutModeForUser(int userId, @LockoutMode int mode) { +        setCurrentUserLockoutMode(mode); +    } +      public void setCurrentUserLockoutMode(@LockoutMode int lockoutMode) {          mCurrentUserLockoutMode = lockoutMode;      } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlConversionUtils.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlConversionUtils.java index 573c20fe041b..d149f5215a7a 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlConversionUtils.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlConversionUtils.java @@ -38,7 +38,7 @@ import android.util.Slog;  /**   * Utilities for converting from hardware to framework-defined AIDL models.   */ -final class AidlConversionUtils { +public final class AidlConversionUtils {      private static final String TAG = "AidlConversionUtils"; diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java new file mode 100644 index 000000000000..57f5b34c197a --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java @@ -0,0 +1,319 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.face.aidl; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.hardware.biometrics.face.AuthenticationFrame; +import android.hardware.biometrics.face.EnrollmentFrame; +import android.hardware.biometrics.face.Error; +import android.hardware.biometrics.face.ISessionCallback; +import android.hardware.face.Face; +import android.hardware.keymaster.HardwareAuthToken; +import android.util.Slog; + +import com.android.server.biometrics.HardwareAuthTokenUtils; +import com.android.server.biometrics.Utils; +import com.android.server.biometrics.sensors.AcquisitionClient; +import com.android.server.biometrics.sensors.AuthSessionCoordinator; +import com.android.server.biometrics.sensors.AuthenticationConsumer; +import com.android.server.biometrics.sensors.BaseClientMonitor; +import com.android.server.biometrics.sensors.BiometricScheduler; +import com.android.server.biometrics.sensors.EnumerateConsumer; +import com.android.server.biometrics.sensors.ErrorConsumer; +import com.android.server.biometrics.sensors.LockoutConsumer; +import com.android.server.biometrics.sensors.LockoutResetDispatcher; +import com.android.server.biometrics.sensors.LockoutTracker; +import com.android.server.biometrics.sensors.RemovalConsumer; +import com.android.server.biometrics.sensors.face.FaceUtils; + +import java.util.ArrayList; +import java.util.function.Consumer; + +/** + * Response handler for the {@link ISessionCallback} HAL AIDL interface. + */ +public class AidlResponseHandler extends ISessionCallback.Stub { +    /** +     * Interface to send results to the AidlResponseHandler's owner. +     */ +    public interface HardwareUnavailableCallback { +        /** +         * Invoked when the HAL sends ERROR_HW_UNAVAILABLE. +         */ +        void onHardwareUnavailable(); +    } + +    private static final String TAG = "AidlResponseHandler"; + +    @NonNull +    private final Context mContext; +    @NonNull +    private final BiometricScheduler mScheduler; +    private final int mSensorId; +    private final int mUserId; +    @NonNull +    private final LockoutTracker mLockoutCache; +    @NonNull +    private final LockoutResetDispatcher mLockoutResetDispatcher; + +    @NonNull +    private final AuthSessionCoordinator mAuthSessionCoordinator; +    @NonNull +    private final HardwareUnavailableCallback mHardwareUnavailableCallback; + +    public AidlResponseHandler(@NonNull Context context, +            @NonNull BiometricScheduler scheduler, int sensorId, int userId, +            @NonNull LockoutTracker lockoutTracker, +            @NonNull LockoutResetDispatcher lockoutResetDispatcher, +            @NonNull AuthSessionCoordinator authSessionCoordinator, +            @NonNull HardwareUnavailableCallback hardwareUnavailableCallback) { +        mContext = context; +        mScheduler = scheduler; +        mSensorId = sensorId; +        mUserId = userId; +        mLockoutCache = lockoutTracker; +        mLockoutResetDispatcher = lockoutResetDispatcher; +        mAuthSessionCoordinator = authSessionCoordinator; +        mHardwareUnavailableCallback = hardwareUnavailableCallback; +    } + +    @Override +    public int getInterfaceVersion() { +        return this.VERSION; +    } + +    @Override +    public String getInterfaceHash() { +        return this.HASH; +    } + +    @Override +    public void onChallengeGenerated(long challenge) { +        handleResponse(FaceGenerateChallengeClient.class, (c) -> c.onChallengeGenerated(mSensorId, +                mUserId, challenge), null); +    } + +    @Override +    public void onChallengeRevoked(long challenge) { +        handleResponse(FaceRevokeChallengeClient.class, (c) -> c.onChallengeRevoked(mSensorId, +                mUserId, challenge), null); +    } + +    @Override +    public void onAuthenticationFrame(AuthenticationFrame frame) { +        handleResponse(FaceAuthenticationClient.class, (c) -> { +            if (frame == null) { +                Slog.e(TAG, "Received null enrollment frame for face authentication client."); +                return; +            } +            c.onAuthenticationFrame(AidlConversionUtils.toFrameworkAuthenticationFrame(frame)); +        }, null); +    } + +    @Override +    public void onEnrollmentFrame(EnrollmentFrame frame) { +        handleResponse(FaceEnrollClient.class, (c) -> { +            if (frame == null) { +                Slog.e(TAG, "Received null enrollment frame for face enroll client."); +                return; +            } +            c.onEnrollmentFrame(AidlConversionUtils.toFrameworkEnrollmentFrame(frame)); +        }, null); +    } + +    @Override +    public void onError(byte error, int vendorCode) { +        onError(AidlConversionUtils.toFrameworkError(error), vendorCode); +    } + +    /** +     * Handle error messages from the HAL. +     */ +    public void onError(int error, int vendorCode) { +        handleResponse(ErrorConsumer.class, (c) -> { +            c.onError(error, vendorCode); +            if (error == Error.HW_UNAVAILABLE) { +                mHardwareUnavailableCallback.onHardwareUnavailable(); +            } +        }, null); +    } + +    @Override +    public void onEnrollmentProgress(int enrollmentId, int remaining) { +        BaseClientMonitor client = mScheduler.getCurrentClient(); +        final int currentUserId; +        if (client == null) { +            return; +        } else { +            currentUserId = client.getTargetUserId(); +        } +        final CharSequence name = FaceUtils.getInstance(mSensorId) +                .getUniqueName(mContext, currentUserId); +        final Face face = new Face(name, enrollmentId, mSensorId); + +        handleResponse(FaceEnrollClient.class, (c) -> c.onEnrollResult(face, remaining), null); +    } + +    @Override +    public void onAuthenticationSucceeded(int enrollmentId, HardwareAuthToken hat) { +        final Face face = new Face("" /* name */, enrollmentId, mSensorId); +        final byte[] byteArray = HardwareAuthTokenUtils.toByteArray(hat); +        final ArrayList<Byte> byteList = new ArrayList<>(); +        for (byte b : byteArray) { +            byteList.add(b); +        } +        handleResponse(AuthenticationConsumer.class, (c) -> c.onAuthenticated(face, +                true /* authenticated */, byteList), null); +    } + +    @Override +    public void onAuthenticationFailed() { +        final Face face = new Face("" /* name */, 0 /* faceId */, mSensorId); +        handleResponse(AuthenticationConsumer.class, (c) -> c.onAuthenticated(face, +                false /* authenticated */, null /* hat */), null); +    } + +    @Override +    public void onLockoutTimed(long durationMillis) { +        handleResponse(LockoutConsumer.class, (c) -> c.onLockoutTimed(durationMillis), null); +    } + +    @Override +    public void onLockoutPermanent() { +        handleResponse(LockoutConsumer.class, LockoutConsumer::onLockoutPermanent, null); +    } + +    @Override +    public void onLockoutCleared() { +        handleResponse(FaceResetLockoutClient.class, FaceResetLockoutClient::onLockoutCleared, +                (c) -> FaceResetLockoutClient.resetLocalLockoutStateToNone(mSensorId, mUserId, +                mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator, +                Utils.getCurrentStrength(mSensorId), -1 /* requestId */)); +    } + +    @Override +    public void onInteractionDetected() { +        handleResponse(FaceDetectClient.class, FaceDetectClient::onInteractionDetected, null); +    } + +    @Override +    public void onEnrollmentsEnumerated(int[] enrollmentIds) { +        if (enrollmentIds.length > 0) { +            for (int i = 0; i < enrollmentIds.length; ++i) { +                final Face face = new Face("" /* name */, enrollmentIds[i], mSensorId); +                final int finalI = i; +                handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(face, +                        enrollmentIds.length - finalI - 1), null); +            } +        } else { +            handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult( +                    null /* identifier */, 0 /* remaining */), null); +        } +    } + +    @Override +    public void onFeaturesRetrieved(byte[] features) { +        handleResponse(FaceGetFeatureClient.class, (c) -> c.onFeatureGet(true /* success */, +                features), null); +    } + +    @Override +    public void onFeatureSet(byte feature) { +        handleResponse(FaceSetFeatureClient.class, (c) -> c.onFeatureSet(true /* success */), null); +    } + +    @Override +    public void onEnrollmentsRemoved(int[] enrollmentIds) { +        if (enrollmentIds.length > 0) { +            for (int i = 0; i < enrollmentIds.length; i++) { +                final Face face = new Face("" /* name */, enrollmentIds[i], mSensorId); +                final int finalI = i; +                handleResponse(RemovalConsumer.class, +                        (c) -> c.onRemoved(face, enrollmentIds.length - finalI - 1), +                        null); +            } +        } else { +            handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(null /* identifier */, +                    0 /* remaining */), null); +        } +    } + +    @Override +    public void onAuthenticatorIdRetrieved(long authenticatorId) { +        handleResponse(FaceGetAuthenticatorIdClient.class, (c) -> c.onAuthenticatorIdRetrieved( +                authenticatorId), null); +    } + +    @Override +    public void onAuthenticatorIdInvalidated(long newAuthenticatorId) { +        handleResponse(FaceInvalidationClient.class, (c) -> c.onAuthenticatorIdInvalidated( +                newAuthenticatorId), null); +    } + +    /** +     * Handles acquired messages sent by the HAL (specifically for HIDL HAL). +     */ +    public void onAcquired(int acquiredInfo, int vendorCode) { +        handleResponse(AcquisitionClient.class, (c) -> c.onAcquired(acquiredInfo, vendorCode), +                null); +    } + +    /** +     * Handles lockout changed messages sent by the HAL (specifically for HIDL HAL). +     */ +    public void onLockoutChanged(long duration) { +        mScheduler.getHandler().post(() -> { +            @LockoutTracker.LockoutMode final int lockoutMode; +            if (duration == 0) { +                lockoutMode = LockoutTracker.LOCKOUT_NONE; +            } else if (duration == -1 || duration == Long.MAX_VALUE) { +                lockoutMode = LockoutTracker.LOCKOUT_PERMANENT; +            } else { +                lockoutMode = LockoutTracker.LOCKOUT_TIMED; +            } + +            mLockoutCache.setLockoutModeForUser(mUserId, lockoutMode); + +            if (duration == 0) { +                mLockoutResetDispatcher.notifyLockoutResetCallbacks(mSensorId); +            } +        }); +    } + +    private <T> void handleResponse(@NonNull Class<T> className, +            @NonNull Consumer<T> actionIfClassMatchesClient, +            @Nullable Consumer<BaseClientMonitor> alternateAction) { +        mScheduler.getHandler().post(() -> { +            final BaseClientMonitor client = mScheduler.getCurrentClient(); +            if (className.isInstance(client)) { +                actionIfClassMatchesClient.accept((T) client); +            } else { +                Slog.d(TAG, "Current client is not an instance of " + className.getName()); +                if (alternateAction != null) { +                    alternateAction.accept(client); +                } +            } +        }); +    } + +    @Override +    public void onSessionClosed() { +        mScheduler.getHandler().post(mScheduler::onUserStopped); +    } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java index 29eee6b5bb06..858bb864d4db 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlSession.java @@ -16,10 +16,14 @@  package com.android.server.biometrics.sensors.face.aidl; -import static com.android.server.biometrics.sensors.face.aidl.Sensor.HalSessionCallback; -  import android.annotation.NonNull; +import android.content.Context;  import android.hardware.biometrics.face.ISession; +import android.hardware.biometrics.face.V1_0.IBiometricsFace; + +import com.android.server.biometrics.sensors.face.hidl.AidlToHidlAdapter; + +import java.util.function.Supplier;  /**   * A holder for an AIDL {@link ISession} with additional metadata about the current user @@ -31,14 +35,22 @@ public class AidlSession {      @NonNull      private final ISession mSession;      private final int mUserId; -    @NonNull private final HalSessionCallback mHalSessionCallback; +    @NonNull private final AidlResponseHandler mAidlResponseHandler;      public AidlSession(int halInterfaceVersion, @NonNull ISession session, int userId, -            HalSessionCallback halSessionCallback) { +            AidlResponseHandler aidlResponseHandler) {          mHalInterfaceVersion = halInterfaceVersion;          mSession = session;          mUserId = userId; -        mHalSessionCallback = halSessionCallback; +        mAidlResponseHandler = aidlResponseHandler; +    } + +    public AidlSession(Context context, Supplier<IBiometricsFace> session, int userId, +            AidlResponseHandler aidlResponseHandler) { +        mSession = new AidlToHidlAdapter(context, session, userId, aidlResponseHandler); +        mHalInterfaceVersion = 0; +        mUserId = userId; +        mAidlResponseHandler = aidlResponseHandler;      }      /** The underlying {@link ISession}. */ @@ -52,8 +64,8 @@ public class AidlSession {      }      /** The HAL callback, which should only be used in tests {@See BiometricTestSessionImpl}. */ -    HalSessionCallback getHalSessionCallback() { -        return mHalSessionCallback; +    AidlResponseHandler getHalSessionCallback() { +        return mAidlResponseHandler;      }      /** diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java index 35fc43ae5f74..470dc4b7172c 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java @@ -46,8 +46,8 @@ import com.android.server.biometrics.sensors.AuthenticationClient;  import com.android.server.biometrics.sensors.ClientMonitorCallback;  import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;  import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback; -import com.android.server.biometrics.sensors.LockoutCache;  import com.android.server.biometrics.sensors.LockoutConsumer; +import com.android.server.biometrics.sensors.LockoutTracker;  import com.android.server.biometrics.sensors.PerformanceTracker;  import com.android.server.biometrics.sensors.face.UsageStats; @@ -57,7 +57,8 @@ import java.util.function.Supplier;  /**   * Face-specific authentication client for the {@link IFace} AIDL HAL interface.   */ -class FaceAuthenticationClient extends AuthenticationClient<AidlSession, FaceAuthenticateOptions> +public class FaceAuthenticationClient +        extends AuthenticationClient<AidlSession, FaceAuthenticateOptions>          implements LockoutConsumer {      private static final String TAG = "FaceAuthenticationClient"; @@ -74,11 +75,11 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession, FaceAut      @Nullable      private ICancellationSignal mCancellationSignal;      @Nullable -    private SensorPrivacyManager mSensorPrivacyManager; +    private final SensorPrivacyManager mSensorPrivacyManager;      @FaceManager.FaceAcquired      private int mLastAcquire = FaceManager.FACE_ACQUIRED_UNKNOWN; -    FaceAuthenticationClient(@NonNull Context context, +    public FaceAuthenticationClient(@NonNull Context context,              @NonNull Supplier<AidlSession> lazyDaemon,              @NonNull IBinder token, long requestId,              @NonNull ClientMonitorCallbackConverter listener, long operationId, @@ -86,7 +87,7 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession, FaceAut              boolean requireConfirmation,              @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,              boolean isStrongBiometric, @NonNull UsageStats usageStats, -            @NonNull LockoutCache lockoutCache, boolean allowBackgroundAuthentication, +            @NonNull LockoutTracker lockoutCache, boolean allowBackgroundAuthentication,              @Authenticators.Types int sensorStrength) {          this(context, lazyDaemon, token, requestId, listener, operationId,                  restricted, options, cookie, requireConfirmation, logger, biometricContext, @@ -103,12 +104,12 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession, FaceAut              boolean requireConfirmation,              @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,              boolean isStrongBiometric, @NonNull UsageStats usageStats, -            @NonNull LockoutCache lockoutCache, boolean allowBackgroundAuthentication, +            @NonNull LockoutTracker lockoutTracker, boolean allowBackgroundAuthentication,              SensorPrivacyManager sensorPrivacyManager,              @Authenticators.Types int biometricStrength) {          super(context, lazyDaemon, token, listener, operationId, restricted,                  options, cookie, requireConfirmation, logger, biometricContext, -                isStrongBiometric, null /* taskStackListener */, null /* lockoutCache */, +                isStrongBiometric, null /* taskStackListener */, lockoutTracker,                  allowBackgroundAuthentication, false /* shouldVibrate */,                  biometricStrength);          setRequestId(requestId); @@ -263,8 +264,13 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession, FaceAut          mLastAcquire = acquireInfo;          final boolean shouldSend = shouldSendAcquiredMessage(acquireInfo, vendorCode);          onAcquiredInternal(acquireInfo, vendorCode, shouldSend); -        PerformanceTracker pt = PerformanceTracker.getInstanceForSensorId(getSensorId()); -        pt.incrementAcquireForUser(getTargetUserId(), isCryptoOperation()); + +        //Check if it is AIDL (lockoutTracker = null) or if it there is no lockout for HIDL +        if (getLockoutTracker() == null || getLockoutTracker().getLockoutModeForUser( +                getTargetUserId()) == LockoutTracker.LOCKOUT_NONE) { +            PerformanceTracker pt = PerformanceTracker.getInstanceForSensorId(getSensorId()); +            pt.incrementAcquireForUser(getTargetUserId(), isCryptoOperation()); +        }      }      /** diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java index f55cf0549382..dbed1f7a8f9d 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java @@ -85,7 +85,7 @@ public class FaceEnrollClient extends EnrollClient<AidlSession> {                  }              }; -    FaceEnrollClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, +    public FaceEnrollClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,              @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,              @NonNull byte[] hardwareAuthToken, @NonNull String opPackageName, long requestId,              @NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec, diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java index 165c3a241043..e404bd2be31e 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClient.java @@ -36,7 +36,7 @@ import java.util.function.Supplier;  public class FaceGenerateChallengeClient extends GenerateChallengeClient<AidlSession> {      private static final String TAG = "FaceGenerateChallengeClient"; -    FaceGenerateChallengeClient(@NonNull Context context, +    public FaceGenerateChallengeClient(@NonNull Context context,              @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token,              @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner,              int sensorId, @NonNull BiometricLogger logger, diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java index ef3b345402bf..c15049b48bb2 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClient.java @@ -21,6 +21,7 @@ import android.annotation.Nullable;  import android.content.Context;  import android.hardware.biometrics.BiometricFaceConstants;  import android.hardware.biometrics.face.IFace; +import android.hardware.biometrics.face.ISession;  import android.os.IBinder;  import android.os.RemoteException;  import android.provider.Settings; @@ -33,6 +34,7 @@ import com.android.server.biometrics.sensors.ClientMonitorCallback;  import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;  import com.android.server.biometrics.sensors.ErrorConsumer;  import com.android.server.biometrics.sensors.HalClientMonitor; +import com.android.server.biometrics.sensors.face.hidl.AidlToHidlAdapter;  import java.util.HashMap;  import java.util.Map; @@ -46,14 +48,16 @@ public class FaceGetFeatureClient extends HalClientMonitor<AidlSession> implemen      private static final String TAG = "FaceGetFeatureClient";      private final int mUserId; +    private final int mFeature; -    FaceGetFeatureClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, +    public FaceGetFeatureClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,              @NonNull IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId,              @NonNull String owner, int sensorId, @NonNull BiometricLogger logger, -            @NonNull BiometricContext biometricContext) { +            @NonNull BiometricContext biometricContext, int feature) {          super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,                  logger, biometricContext);          mUserId = userId; +        mFeature = feature;      }      @Override @@ -70,7 +74,11 @@ public class FaceGetFeatureClient extends HalClientMonitor<AidlSession> implemen      @Override      protected void startHalOperation() {          try { -            getFreshDaemon().getSession().getFeatures(); +            ISession session = getFreshDaemon().getSession(); +            if (session instanceof AidlToHidlAdapter) { +                ((AidlToHidlAdapter) session).setFeature(mFeature); +            } +            session.getFeatures();          } catch (RemoteException e) {              Slog.e(TAG, "Unable to getFeature", e);              mCallback.onClientFinished(this, false /* success */); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java index f09d192966f1..e75c6aba1489 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java @@ -38,9 +38,9 @@ import java.util.function.Supplier;  /**   * Face-specific internal cleanup client for the {@link IFace} AIDL HAL interface.   */ -class FaceInternalCleanupClient extends InternalCleanupClient<Face, AidlSession> { +public class FaceInternalCleanupClient extends InternalCleanupClient<Face, AidlSession> { -    FaceInternalCleanupClient(@NonNull Context context, +    public FaceInternalCleanupClient(@NonNull Context context,              @NonNull Supplier<AidlSession> lazyDaemon, int userId, @NonNull String owner,              int sensorId, @NonNull BiometricLogger logger,              @NonNull BiometricContext biometricContext, diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java index cc3118cc3433..dd9c6d50ae9e 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java @@ -493,7 +493,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {                      createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,                              mAuthenticationStatsCollector),                      mBiometricContext, isStrongBiometric, -                    mUsageStats, mFaceSensors.get(sensorId).getLockoutCache(), +                    mUsageStats, null /* lockoutTracker */,                      allowBackgroundAuthentication, Utils.getCurrentStrength(sensorId));              scheduleForSensor(sensorId, client, new ClientMonitorCallback() {                  @Override @@ -619,7 +619,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {              final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext,                      mFaceSensors.get(sensorId).getLazySession(), token, callback, userId,                      mContext.getOpPackageName(), sensorId, BiometricLogger.ofUnknown(mContext), -                    mBiometricContext); +                    mBiometricContext, feature);              scheduleForSensor(sensorId, client);          });      } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java index 0512017394af..079388822def 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClient.java @@ -36,12 +36,12 @@ import java.util.function.Supplier;  /**   * Face-specific removal client for the {@link IFace} AIDL HAL interface.   */ -class FaceRemovalClient extends RemovalClient<Face, AidlSession> { +public class FaceRemovalClient extends RemovalClient<Face, AidlSession> {      private static final String TAG = "FaceRemovalClient";      final int[] mBiometricIds; -    FaceRemovalClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, +    public FaceRemovalClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,              @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,              int[] biometricIds, int userId, @NonNull String owner,              @NonNull BiometricUtils<Face> utils, int sensorId, diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java index 1a12fcdf5010..77b5592c5064 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClient.java @@ -32,7 +32,6 @@ import com.android.server.biometrics.sensors.AuthSessionCoordinator;  import com.android.server.biometrics.sensors.ClientMonitorCallback;  import com.android.server.biometrics.sensors.ErrorConsumer;  import com.android.server.biometrics.sensors.HalClientMonitor; -import com.android.server.biometrics.sensors.LockoutCache;  import com.android.server.biometrics.sensors.LockoutResetDispatcher;  import com.android.server.biometrics.sensors.LockoutTracker; @@ -48,14 +47,14 @@ public class FaceResetLockoutClient extends HalClientMonitor<AidlSession> implem      private static final String TAG = "FaceResetLockoutClient";      private final HardwareAuthToken mHardwareAuthToken; -    private final LockoutCache mLockoutCache; +    private final LockoutTracker mLockoutCache;      private final LockoutResetDispatcher mLockoutResetDispatcher;      private final int mBiometricStrength; -    FaceResetLockoutClient(@NonNull Context context, +    public FaceResetLockoutClient(@NonNull Context context,              @NonNull Supplier<AidlSession> lazyDaemon, int userId, String owner, int sensorId,              @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, -            @NonNull byte[] hardwareAuthToken, @NonNull LockoutCache lockoutTracker, +            @NonNull byte[] hardwareAuthToken, @NonNull LockoutTracker lockoutTracker,              @NonNull LockoutResetDispatcher lockoutResetDispatcher,              @Authenticators.Types int biometricStrength) {          super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner, @@ -107,7 +106,7 @@ public class FaceResetLockoutClient extends HalClientMonitor<AidlSession> implem       * be used instead.       */      static void resetLocalLockoutStateToNone(int sensorId, int userId, -            @NonNull LockoutCache lockoutTracker, +            @NonNull LockoutTracker lockoutTracker,              @NonNull LockoutResetDispatcher lockoutResetDispatcher,              @NonNull AuthSessionCoordinator authSessionCoordinator,              @Authenticators.Types int biometricStrength, long requestId) { diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java index 8838345de4d6..0d6143a7d0f0 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClient.java @@ -38,7 +38,7 @@ public class FaceRevokeChallengeClient extends RevokeChallengeClient<AidlSession      private final long mChallenge; -    FaceRevokeChallengeClient(@NonNull Context context, +    public FaceRevokeChallengeClient(@NonNull Context context,              @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token,              int userId, @NonNull String owner, int sensorId,              @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java index 6c143872ff8c..f6da8726564f 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClient.java @@ -46,7 +46,7 @@ public class FaceSetFeatureClient extends HalClientMonitor<AidlSession> implemen      private final boolean mEnabled;      private final HardwareAuthToken mHardwareAuthToken; -    FaceSetFeatureClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, +    public FaceSetFeatureClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,              @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,              @NonNull String owner, int sensorId,              @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java index 2ad41c2a7a02..54e66eb4cca4 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java @@ -23,16 +23,10 @@ import android.content.pm.UserInfo;  import android.hardware.biometrics.BiometricsProtoEnums;  import android.hardware.biometrics.ITestSession;  import android.hardware.biometrics.ITestSessionCallback; -import android.hardware.biometrics.face.AuthenticationFrame; -import android.hardware.biometrics.face.EnrollmentFrame; -import android.hardware.biometrics.face.Error;  import android.hardware.biometrics.face.IFace;  import android.hardware.biometrics.face.ISession; -import android.hardware.biometrics.face.ISessionCallback; -import android.hardware.face.Face;  import android.hardware.face.FaceManager;  import android.hardware.face.FaceSensorPropertiesInternal; -import android.hardware.keymaster.HardwareAuthToken;  import android.os.Binder;  import android.os.Handler;  import android.os.IBinder; @@ -44,29 +38,22 @@ import android.util.proto.ProtoOutputStream;  import com.android.internal.annotations.VisibleForTesting;  import com.android.internal.util.FrameworkStatsLog; -import com.android.server.biometrics.HardwareAuthTokenUtils;  import com.android.server.biometrics.SensorServiceStateProto;  import com.android.server.biometrics.SensorStateProto;  import com.android.server.biometrics.UserStateProto;  import com.android.server.biometrics.Utils;  import com.android.server.biometrics.log.BiometricContext;  import com.android.server.biometrics.log.BiometricLogger; -import com.android.server.biometrics.sensors.AuthSessionCoordinator; -import com.android.server.biometrics.sensors.AuthenticationConsumer;  import com.android.server.biometrics.sensors.BaseClientMonitor;  import com.android.server.biometrics.sensors.BiometricScheduler; -import com.android.server.biometrics.sensors.EnumerateConsumer;  import com.android.server.biometrics.sensors.ErrorConsumer;  import com.android.server.biometrics.sensors.LockoutCache; -import com.android.server.biometrics.sensors.LockoutConsumer;  import com.android.server.biometrics.sensors.LockoutResetDispatcher; -import com.android.server.biometrics.sensors.RemovalConsumer;  import com.android.server.biometrics.sensors.StartUserClient;  import com.android.server.biometrics.sensors.StopUserClient;  import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;  import com.android.server.biometrics.sensors.face.FaceUtils; -import java.util.ArrayList;  import java.util.HashMap;  import java.util.Map;  import java.util.function.Supplier; @@ -91,397 +78,6 @@ public class Sensor {      @NonNull private final Supplier<AidlSession> mLazySession;      @Nullable AidlSession mCurrentSession; -    @VisibleForTesting -    public static class HalSessionCallback extends ISessionCallback.Stub { -        /** -         * Interface to sends results to the HalSessionCallback's owner. -         */ -        public interface Callback { -            /** -             * Invoked when the HAL sends ERROR_HW_UNAVAILABLE. -             */ -            void onHardwareUnavailable(); -        } - -        @NonNull -        private final Context mContext; -        @NonNull -        private final Handler mHandler; -        @NonNull -        private final String mTag; -        @NonNull -        private final UserAwareBiometricScheduler mScheduler; -        private final int mSensorId; -        private final int mUserId; -        @NonNull -        private final LockoutCache mLockoutCache; -        @NonNull -        private final LockoutResetDispatcher mLockoutResetDispatcher; - -        @NonNull -        private AuthSessionCoordinator mAuthSessionCoordinator; -        @NonNull -        private final Callback mCallback; - -        HalSessionCallback(@NonNull Context context, @NonNull Handler handler, @NonNull String tag, -                @NonNull UserAwareBiometricScheduler scheduler, int sensorId, int userId, -                @NonNull LockoutCache lockoutTracker, -                @NonNull LockoutResetDispatcher lockoutResetDispatcher, -                @NonNull AuthSessionCoordinator authSessionCoordinator, -                @NonNull Callback callback) { -            mContext = context; -            mHandler = handler; -            mTag = tag; -            mScheduler = scheduler; -            mSensorId = sensorId; -            mUserId = userId; -            mLockoutCache = lockoutTracker; -            mLockoutResetDispatcher = lockoutResetDispatcher; -            mAuthSessionCoordinator = authSessionCoordinator; -            mCallback = callback; -        } - -        @Override -        public int getInterfaceVersion() { -            return this.VERSION; -        } - -        @Override -        public String getInterfaceHash() { -            return this.HASH; -        } - -        @Override -        public void onChallengeGenerated(long challenge) { -            mHandler.post(() -> { -                final BaseClientMonitor client = mScheduler.getCurrentClient(); -                if (!(client instanceof FaceGenerateChallengeClient)) { -                    Slog.e(mTag, "onChallengeGenerated for wrong client: " -                            + Utils.getClientName(client)); -                    return; -                } - -                final FaceGenerateChallengeClient generateChallengeClient = -                        (FaceGenerateChallengeClient) client; -                generateChallengeClient.onChallengeGenerated(mSensorId, mUserId, challenge); -            }); -        } - -        @Override -        public void onChallengeRevoked(long challenge) { -            mHandler.post(() -> { -                final BaseClientMonitor client = mScheduler.getCurrentClient(); -                if (!(client instanceof FaceRevokeChallengeClient)) { -                    Slog.e(mTag, "onChallengeRevoked for wrong client: " -                            + Utils.getClientName(client)); -                    return; -                } - -                final FaceRevokeChallengeClient revokeChallengeClient = -                        (FaceRevokeChallengeClient) client; -                revokeChallengeClient.onChallengeRevoked(mSensorId, mUserId, challenge); -            }); -        } - -        @Override -        public void onAuthenticationFrame(AuthenticationFrame frame) { -            mHandler.post(() -> { -                final BaseClientMonitor client = mScheduler.getCurrentClient(); -                if (!(client instanceof FaceAuthenticationClient)) { -                    Slog.e(mTag, "onAuthenticationFrame for incompatible client: " -                            + Utils.getClientName(client)); -                    return; - -                } -                if (frame == null) { -                    Slog.e(mTag, "Received null authentication frame for client: " -                            + Utils.getClientName(client)); -                    return; -                } -                ((FaceAuthenticationClient) client).onAuthenticationFrame( -                        AidlConversionUtils.toFrameworkAuthenticationFrame(frame)); -            }); -        } - -        @Override -        public void onEnrollmentFrame(EnrollmentFrame frame) { -            mHandler.post(() -> { -                final BaseClientMonitor client = mScheduler.getCurrentClient(); -                if (!(client instanceof FaceEnrollClient)) { -                    Slog.e(mTag, "onEnrollmentFrame for incompatible client: " -                            + Utils.getClientName(client)); -                    return; -                } -                if (frame == null) { -                    Slog.e(mTag, "Received null enrollment frame for client: " -                            + Utils.getClientName(client)); -                    return; -                } -                ((FaceEnrollClient) client).onEnrollmentFrame( -                        AidlConversionUtils.toFrameworkEnrollmentFrame(frame)); -            }); -        } - -        @Override -        public void onError(byte error, int vendorCode) { -            mHandler.post(() -> { -                final BaseClientMonitor client = mScheduler.getCurrentClient(); -                Slog.d(mTag, "onError" -                        + ", client: " + Utils.getClientName(client) -                        + ", error: " + error -                        + ", vendorCode: " + vendorCode); -                if (!(client instanceof ErrorConsumer)) { -                    Slog.e(mTag, "onError for non-error consumer: " -                            + Utils.getClientName(client)); -                    return; -                } - -                final ErrorConsumer errorConsumer = (ErrorConsumer) client; -                errorConsumer.onError(AidlConversionUtils.toFrameworkError(error), vendorCode); - -                if (error == Error.HW_UNAVAILABLE) { -                    mCallback.onHardwareUnavailable(); -                } -            }); -        } - -        @Override -        public void onEnrollmentProgress(int enrollmentId, int remaining) { -            mHandler.post(() -> { -                final BaseClientMonitor client = mScheduler.getCurrentClient(); -                if (!(client instanceof FaceEnrollClient)) { -                    Slog.e(mTag, "onEnrollmentProgress for non-enroll client: " -                            + Utils.getClientName(client)); -                    return; -                } - -                final int currentUserId = client.getTargetUserId(); -                final CharSequence name = FaceUtils.getInstance(mSensorId) -                        .getUniqueName(mContext, currentUserId); -                final Face face = new Face(name, enrollmentId, mSensorId); - -                final FaceEnrollClient enrollClient = (FaceEnrollClient) client; -                enrollClient.onEnrollResult(face, remaining); -            }); -        } - -        @Override -        public void onAuthenticationSucceeded(int enrollmentId, HardwareAuthToken hat) { -            mHandler.post(() -> { -                final BaseClientMonitor client = mScheduler.getCurrentClient(); -                if (!(client instanceof AuthenticationConsumer)) { -                    Slog.e(mTag, "onAuthenticationSucceeded for non-authentication consumer: " -                            + Utils.getClientName(client)); -                    return; -                } - -                final AuthenticationConsumer authenticationConsumer = -                        (AuthenticationConsumer) client; -                final Face face = new Face("" /* name */, enrollmentId, mSensorId); -                final byte[] byteArray = HardwareAuthTokenUtils.toByteArray(hat); -                final ArrayList<Byte> byteList = new ArrayList<>(); -                for (byte b : byteArray) { -                    byteList.add(b); -                } -                authenticationConsumer.onAuthenticated(face, true /* authenticated */, byteList); -            }); -        } - -        @Override -        public void onAuthenticationFailed() { -            mHandler.post(() -> { -                final BaseClientMonitor client = mScheduler.getCurrentClient(); -                if (!(client instanceof AuthenticationConsumer)) { -                    Slog.e(mTag, "onAuthenticationFailed for non-authentication consumer: " -                            + Utils.getClientName(client)); -                    return; -                } - -                final AuthenticationConsumer authenticationConsumer = -                        (AuthenticationConsumer) client; -                final Face face = new Face("" /* name */, 0 /* faceId */, mSensorId); -                authenticationConsumer.onAuthenticated(face, false /* authenticated */, -                        null /* hat */); -            }); -        } - -        @Override -        public void onLockoutTimed(long durationMillis) { -            mHandler.post(() -> { -                final BaseClientMonitor client = mScheduler.getCurrentClient(); -                if (!(client instanceof LockoutConsumer)) { -                    Slog.e(mTag, "onLockoutTimed for non-lockout consumer: " -                            + Utils.getClientName(client)); -                    return; -                } - -                final LockoutConsumer lockoutConsumer = (LockoutConsumer) client; -                lockoutConsumer.onLockoutTimed(durationMillis); -            }); -        } - -        @Override -        public void onLockoutPermanent() { -            mHandler.post(() -> { -                final BaseClientMonitor client = mScheduler.getCurrentClient(); -                if (!(client instanceof LockoutConsumer)) { -                    Slog.e(mTag, "onLockoutPermanent for non-lockout consumer: " -                            + Utils.getClientName(client)); -                    return; -                } - -                final LockoutConsumer lockoutConsumer = (LockoutConsumer) client; -                lockoutConsumer.onLockoutPermanent(); -            }); -        } - -        @Override -        public void onLockoutCleared() { -            mHandler.post(() -> { -                final BaseClientMonitor client = mScheduler.getCurrentClient(); -                if (!(client instanceof FaceResetLockoutClient)) { -                    Slog.d(mTag, "onLockoutCleared outside of resetLockout by HAL"); -                    // Given that onLockoutCleared() can happen at any time, and is not necessarily -                    // coming from a specific client, set this to -1 to indicate it wasn't for a -                    // specific request. -                    FaceResetLockoutClient.resetLocalLockoutStateToNone(mSensorId, mUserId, -                            mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator, -                            Utils.getCurrentStrength(mSensorId), -1 /* requestId */); -                } else { -                    Slog.d(mTag, "onLockoutCleared after resetLockout"); -                    final FaceResetLockoutClient resetLockoutClient = -                            (FaceResetLockoutClient) client; -                    resetLockoutClient.onLockoutCleared(); -                } -            }); -        } - -        @Override -        public void onInteractionDetected() { -            mHandler.post(() -> { -                final BaseClientMonitor client = mScheduler.getCurrentClient(); -                if (!(client instanceof FaceDetectClient)) { -                    Slog.e(mTag, "onInteractionDetected for wrong client: " -                            + Utils.getClientName(client)); -                    return; -                } - -                final FaceDetectClient detectClient = (FaceDetectClient) client; -                detectClient.onInteractionDetected(); -            }); -        } - -        @Override -        public void onEnrollmentsEnumerated(int[] enrollmentIds) { -            mHandler.post(() -> { -                final BaseClientMonitor client = mScheduler.getCurrentClient(); -                if (!(client instanceof EnumerateConsumer)) { -                    Slog.e(mTag, "onEnrollmentsEnumerated for non-enumerate consumer: " -                            + Utils.getClientName(client)); -                    return; -                } - -                final EnumerateConsumer enumerateConsumer = -                        (EnumerateConsumer) client; -                if (enrollmentIds.length > 0) { -                    for (int i = 0; i < enrollmentIds.length; ++i) { -                        final Face face = new Face("" /* name */, enrollmentIds[i], mSensorId); -                        enumerateConsumer.onEnumerationResult(face, enrollmentIds.length - i - 1); -                    } -                } else { -                    enumerateConsumer.onEnumerationResult(null /* identifier */, 0 /* remaining */); -                } -            }); -        } - -        @Override -        public void onFeaturesRetrieved(byte[] features) { -            mHandler.post(() -> { -                final BaseClientMonitor client = mScheduler.getCurrentClient(); -                if (!(client instanceof FaceGetFeatureClient)) { -                    Slog.e(mTag, "onFeaturesRetrieved for non-get feature consumer: " -                            + Utils.getClientName(client)); -                    return; -                } -                final FaceGetFeatureClient faceGetFeatureClient = (FaceGetFeatureClient) client; -                faceGetFeatureClient.onFeatureGet(true /* success */, features); -            }); - -        } - -        @Override -        public void onFeatureSet(byte feature) { -            mHandler.post(() -> { -                final BaseClientMonitor client = mScheduler.getCurrentClient(); -                if (!(client instanceof FaceSetFeatureClient)) { -                    Slog.e(mTag, "onFeatureSet for non-set consumer: " -                            + Utils.getClientName(client)); -                    return; -                } - -                final FaceSetFeatureClient faceSetFeatureClient = (FaceSetFeatureClient) client; -                faceSetFeatureClient.onFeatureSet(true /* success */); -            }); -        } - -        @Override -        public void onEnrollmentsRemoved(int[] enrollmentIds) { -            mHandler.post(() -> { -                final BaseClientMonitor client = mScheduler.getCurrentClient(); -                if (!(client instanceof RemovalConsumer)) { -                    Slog.e(mTag, "onRemoved for non-removal consumer: " -                            + Utils.getClientName(client)); -                    return; -                } - -                final RemovalConsumer removalConsumer = (RemovalConsumer) client; -                if (enrollmentIds.length > 0) { -                    for (int i = 0; i < enrollmentIds.length; i++) { -                        final Face face = new Face("" /* name */, enrollmentIds[i], mSensorId); -                        removalConsumer.onRemoved(face, enrollmentIds.length - i - 1); -                    } -                } else { -                    removalConsumer.onRemoved(null /* identifier */, 0 /* remaining */); -                } -            }); -        } - -        @Override -        public void onAuthenticatorIdRetrieved(long authenticatorId) { -            mHandler.post(() -> { -                final BaseClientMonitor client = mScheduler.getCurrentClient(); -                if (!(client instanceof FaceGetAuthenticatorIdClient)) { -                    Slog.e(mTag, "onAuthenticatorIdRetrieved for wrong consumer: " -                            + Utils.getClientName(client)); -                    return; -                } - -                final FaceGetAuthenticatorIdClient getAuthenticatorIdClient = -                        (FaceGetAuthenticatorIdClient) client; -                getAuthenticatorIdClient.onAuthenticatorIdRetrieved(authenticatorId); -            }); -        } - -        @Override -        public void onAuthenticatorIdInvalidated(long newAuthenticatorId) { -            mHandler.post(() -> { -                final BaseClientMonitor client = mScheduler.getCurrentClient(); -                if (!(client instanceof FaceInvalidationClient)) { -                    Slog.e(mTag, "onAuthenticatorIdInvalidated for wrong consumer: " -                            + Utils.getClientName(client)); -                    return; -                } - -                final FaceInvalidationClient invalidationClient = (FaceInvalidationClient) client; -                invalidationClient.onAuthenticatorIdInvalidated(newAuthenticatorId); -            }); -        } - -        @Override -        public void onSessionClosed() { -            mHandler.post(mScheduler::onUserStopped); -        } -    }      Sensor(@NonNull String tag, @NonNull FaceProvider provider, @NonNull Context context,              @NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties, @@ -511,9 +107,9 @@ public class Sensor {                      public StartUserClient<?, ?> getStartUserClient(int newUserId) {                          final int sensorId = mSensorProperties.sensorId; -                        final HalSessionCallback resultController = new HalSessionCallback(mContext, -                                mHandler, mTag, mScheduler, sensorId, newUserId, mLockoutCache, -                                lockoutResetDispatcher, +                        final AidlResponseHandler resultController = new AidlResponseHandler( +                                mContext, mScheduler, sensorId, newUserId, +                                mLockoutCache, lockoutResetDispatcher,                                  biometricContext.getAuthSessionCoordinator(), () -> {                              Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");                              mCurrentSession = null; diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapter.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapter.java new file mode 100644 index 000000000000..eecf44b92918 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapter.java @@ -0,0 +1,347 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.face.hidl; + +import android.annotation.DurationMillisLong; +import android.annotation.NonNull; +import android.content.Context; +import android.hardware.biometrics.BiometricFaceConstants; +import android.hardware.biometrics.common.ICancellationSignal; +import android.hardware.biometrics.common.OperationContext; +import android.hardware.biometrics.face.EnrollmentStageConfig; +import android.hardware.biometrics.face.ISession; +import android.hardware.biometrics.face.V1_0.IBiometricsFace; +import android.hardware.biometrics.face.V1_0.OptionalBool; +import android.hardware.biometrics.face.V1_0.Status; +import android.hardware.common.NativeHandle; +import android.hardware.face.Face; +import android.hardware.face.FaceManager; +import android.hardware.keymaster.HardwareAuthToken; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.biometrics.HardwareAuthTokenUtils; +import com.android.server.biometrics.sensors.face.aidl.AidlConversionUtils; +import com.android.server.biometrics.sensors.face.aidl.AidlResponseHandler; + +import java.time.Clock; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; + +/** + * Adapter to convert AIDL-specific interface {@link ISession} methods to HIDL implementation. + */ +public class AidlToHidlAdapter implements ISession { + +    private final String TAG = "AidlToHidlAdapter"; +    private static final int CHALLENGE_TIMEOUT_SEC = 600; +    @DurationMillisLong +    private static final int GENERATE_CHALLENGE_REUSE_INTERVAL_MILLIS = 60 * 1000; +    @DurationMillisLong +    private static final int GENERATE_CHALLENGE_COUNTER_TTL_MILLIS = CHALLENGE_TIMEOUT_SEC * 1000; +    private static final int INVALID_VALUE = -1; +    private final Clock mClock; +    private final List<Long> mGeneratedChallengeCount = new ArrayList<>(); +    @VisibleForTesting static final int ENROLL_TIMEOUT_SEC = 75; +    private long mGenerateChallengeCreatedAt = INVALID_VALUE; +    private long mGenerateChallengeResult = INVALID_VALUE; +    @NonNull private Supplier<IBiometricsFace> mSession; +    private final int mUserId; +    private HidlToAidlCallbackConverter mHidlToAidlCallbackConverter; +    private final Context mContext; +    private int mFeature = INVALID_VALUE; + +    public AidlToHidlAdapter(Context context, Supplier<IBiometricsFace> session, +            int userId, AidlResponseHandler aidlResponseHandler) { +        this(context, session, userId, aidlResponseHandler, Clock.systemUTC()); +    } + +    AidlToHidlAdapter(Context context, Supplier<IBiometricsFace> session, int userId, +            AidlResponseHandler aidlResponseHandler, Clock clock) { +        mSession = session; +        mUserId = userId; +        mContext = context; +        mClock = clock; +        setCallback(aidlResponseHandler); +    } + +    private void setCallback(AidlResponseHandler aidlResponseHandler) { +        mHidlToAidlCallbackConverter = new HidlToAidlCallbackConverter(aidlResponseHandler); +        try { +            mSession.get().setCallback(mHidlToAidlCallbackConverter); +        } catch (RemoteException e) { +            Slog.d(TAG, "Failed to set callback"); +        } +    } + +    @Override +    public IBinder asBinder() { +        return null; +    } + +    private boolean isGeneratedChallengeCacheValid() { +        return mGenerateChallengeCreatedAt != INVALID_VALUE +                && mGenerateChallengeResult != INVALID_VALUE +                && mClock.millis() - mGenerateChallengeCreatedAt +                < GENERATE_CHALLENGE_REUSE_INTERVAL_MILLIS; +    } + +    private void incrementChallengeCount() { +        mGeneratedChallengeCount.add(0, mClock.millis()); +    } + +    private int decrementChallengeCount() { +        final long now = mClock.millis(); +        // ignore values that are old in case generate/revoke calls are not matched +        // this doesn't ensure revoke if calls are mismatched but it keeps the list from growing +        mGeneratedChallengeCount.removeIf(x -> now - x > GENERATE_CHALLENGE_COUNTER_TTL_MILLIS); +        if (!mGeneratedChallengeCount.isEmpty()) { +            mGeneratedChallengeCount.remove(0); +        } +        return mGeneratedChallengeCount.size(); +    } + +    @Override +    public void generateChallenge() throws RemoteException { +        incrementChallengeCount(); +        if (isGeneratedChallengeCacheValid()) { +            Slog.d(TAG, "Current challenge is cached and will be reused"); +            mHidlToAidlCallbackConverter.onChallengeGenerated(mGenerateChallengeResult); +            return; +        } +        mGenerateChallengeCreatedAt = mClock.millis(); +        mGenerateChallengeResult = mSession.get().generateChallenge(CHALLENGE_TIMEOUT_SEC).value; +        mHidlToAidlCallbackConverter.onChallengeGenerated(mGenerateChallengeResult); +    } + +    @Override +    public void revokeChallenge(long challenge) throws RemoteException { +        final boolean shouldRevoke = decrementChallengeCount() == 0; +        if (!shouldRevoke) { +            Slog.w(TAG, "scheduleRevokeChallenge skipped - challenge still in use: " +                    + mGeneratedChallengeCount); +            mHidlToAidlCallbackConverter.onError(0 /* deviceId */, mUserId, +                    BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS, 0 /* vendorCode */); +            return; +        } +        mGenerateChallengeCreatedAt = INVALID_VALUE; +        mGenerateChallengeResult = INVALID_VALUE; +        mSession.get().revokeChallenge(); +        mHidlToAidlCallbackConverter.onChallengeRevoked(0L); +    } + +    @Override +    public EnrollmentStageConfig[] getEnrollmentConfig(byte enrollmentType) throws RemoteException { +        //unsupported in HIDL +        return null; +    } + +    @Override +    public ICancellationSignal enroll(HardwareAuthToken hat, byte type, byte[] features, +            NativeHandle previewSurface) throws RemoteException { +        final ArrayList<Byte> token = new ArrayList<>(); +        final byte[] hardwareAuthTokenArray = HardwareAuthTokenUtils.toByteArray(hat); +        for (byte b : hardwareAuthTokenArray) { +            token.add(b); +        } +        final ArrayList<Integer> disabledFeatures = new ArrayList<>(); +        for (byte b: features) { +            disabledFeatures.add(AidlConversionUtils.convertAidlToFrameworkFeature(b)); +        } +        mSession.get().enroll(token, ENROLL_TIMEOUT_SEC, disabledFeatures); +        return new Cancellation(); +    } + +    @Override +    public ICancellationSignal authenticate(long operationId) throws RemoteException { +        mSession.get().authenticate(operationId); +        return new Cancellation(); +    } + +    @Override +    public ICancellationSignal detectInteraction() throws RemoteException { +        mSession.get().authenticate(0); +        return new Cancellation(); +    } + +    @Override +    public void enumerateEnrollments() throws RemoteException { +        mSession.get().enumerate(); +    } + +    @Override +    public void removeEnrollments(int[] enrollmentIds) throws RemoteException { +        mSession.get().remove(enrollmentIds[0]); +    } + +    /** +     * Needs to be called before getFeatures is invoked. +     */ +    public void setFeature(int feature) { +        mFeature = feature; +    } + +    @Override +    public void getFeatures() throws RemoteException { +        final int faceId = getFaceId(); +        if (faceId == INVALID_VALUE || mFeature == INVALID_VALUE) { +            return; +        } + +        final OptionalBool result = mSession.get() +                .getFeature(mFeature, faceId); + +        if (result.status == Status.OK && result.value) { +            mHidlToAidlCallbackConverter.onFeatureGet(new byte[]{AidlConversionUtils +                    .convertFrameworkToAidlFeature(mFeature)}); +        } else if (result.status == Status.OK) { +            mHidlToAidlCallbackConverter.onFeatureGet(new byte[]{}); +        } else { +            mHidlToAidlCallbackConverter.onError(0 /* deviceId */, mUserId, +                    BiometricFaceConstants.FACE_ERROR_UNKNOWN, 0 /* vendorCode */); +        } + +        mFeature = INVALID_VALUE; +    } + +    @Override +    public void setFeature(HardwareAuthToken hat, byte feature, boolean enabled) +            throws RemoteException { +        final int faceId = getFaceId(); +        if (faceId == INVALID_VALUE) { +            return; +        } +        ArrayList<Byte> hardwareAuthTokenList = new ArrayList<>(); +        for (byte b: HardwareAuthTokenUtils.toByteArray(hat)) { +            hardwareAuthTokenList.add(b); +        } +        final int result = mSession.get().setFeature( +                AidlConversionUtils.convertAidlToFrameworkFeature(feature), +                enabled, hardwareAuthTokenList, faceId); +        if (result == Status.OK) { +            mHidlToAidlCallbackConverter.onFeatureSet(feature); +        } else { +            mHidlToAidlCallbackConverter.onError(0 /* deviceId */, mUserId, +                    BiometricFaceConstants.FACE_ERROR_UNKNOWN, 0 /* vendorCode */); +        } +    } + +    private int getFaceId() { +        FaceManager faceManager = mContext.getSystemService(FaceManager.class); +        List<Face> faces = faceManager.getEnrolledFaces(mUserId); +        if (faces.isEmpty()) { +            Slog.d(TAG, "No faces to get feature from."); +            mHidlToAidlCallbackConverter.onError(0 /* deviceId */, mUserId, +                    BiometricFaceConstants.FACE_ERROR_NOT_ENROLLED, 0 /* vendorCode */); +            return INVALID_VALUE; +        } + +        return faces.get(0).getBiometricId(); +    } + +    @Override +    public void getAuthenticatorId() throws RemoteException { +        long authenticatorId = mSession.get().getAuthenticatorId().value; +        mHidlToAidlCallbackConverter.onAuthenticatorIdRetrieved(authenticatorId); +    } + +    @Override +    public void invalidateAuthenticatorId() throws RemoteException { +        //unsupported in HIDL +    } + +    @Override +    public void resetLockout(HardwareAuthToken hat) throws RemoteException { +        ArrayList<Byte> hardwareAuthToken = new ArrayList<>(); +        for (byte b : HardwareAuthTokenUtils.toByteArray(hat)) { +            hardwareAuthToken.add(b); +        } +        mSession.get().resetLockout(hardwareAuthToken); +    } + +    @Override +    public void close() throws RemoteException { +        //Unsupported in HIDL +    } + +    @Override +    public ICancellationSignal authenticateWithContext(long operationId, OperationContext context) +            throws RemoteException { +        //Unsupported in HIDL +        return null; +    } + +    @Override +    public ICancellationSignal enrollWithContext(HardwareAuthToken hat, byte type, byte[] features, +            NativeHandle previewSurface, OperationContext context) throws RemoteException { +        //Unsupported in HIDL +        return null; +    } + +    @Override +    public ICancellationSignal detectInteractionWithContext(OperationContext context) +            throws RemoteException { +        //Unsupported in HIDL +        return null; +    } + +    @Override +    public void onContextChanged(OperationContext context) throws RemoteException { +        //Unsupported in HIDL +    } + +    @Override +    public int getInterfaceVersion() throws RemoteException { +        //Unsupported in HIDL +        return 0; +    } + +    @Override +    public String getInterfaceHash() throws RemoteException { +        //Unsupported in HIDL +        return null; +    } + +    /** +     * Cancellation in HIDL occurs for the entire session, instead of a specific client. +     */ +    private class Cancellation extends ICancellationSignal.Stub { + +        Cancellation() {} +        @Override +        public void cancel() throws RemoteException { +            try { +                mSession.get().cancel(); +            } catch (RemoteException e) { +                Slog.e(TAG, "Remote exception when requesting cancel", e); +            } +        } + +        @Override +        public int getInterfaceVersion() throws RemoteException { +            return 0; +        } + +        @Override +        public String getInterfaceHash() throws RemoteException { +            return null; +        } +    } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java index 1499317478aa..46ce0b62e6d5 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java @@ -54,6 +54,7 @@ import com.android.internal.annotations.VisibleForTesting;  import com.android.internal.util.FrameworkStatsLog;  import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver;  import com.android.server.biometrics.AuthenticationStatsCollector; +import com.android.server.biometrics.Flags;  import com.android.server.biometrics.SensorServiceStateProto;  import com.android.server.biometrics.SensorStateProto;  import com.android.server.biometrics.UserStateProto; @@ -61,6 +62,7 @@ import com.android.server.biometrics.Utils;  import com.android.server.biometrics.log.BiometricContext;  import com.android.server.biometrics.log.BiometricLogger;  import com.android.server.biometrics.sensors.AcquisitionClient; +import com.android.server.biometrics.sensors.AuthSessionCoordinator;  import com.android.server.biometrics.sensors.AuthenticationConsumer;  import com.android.server.biometrics.sensors.BaseClientMonitor;  import com.android.server.biometrics.sensors.BiometricScheduler; @@ -78,6 +80,8 @@ import com.android.server.biometrics.sensors.face.FaceUtils;  import com.android.server.biometrics.sensors.face.LockoutHalImpl;  import com.android.server.biometrics.sensors.face.ServiceProvider;  import com.android.server.biometrics.sensors.face.UsageStats; +import com.android.server.biometrics.sensors.face.aidl.AidlResponseHandler; +import com.android.server.biometrics.sensors.face.aidl.AidlSession;  import org.json.JSONArray;  import org.json.JSONException; @@ -131,7 +135,9 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {      private int mCurrentUserId = UserHandle.USER_NULL;      private final int mSensorId;      private final List<Long> mGeneratedChallengeCount = new ArrayList<>(); +    private final LockoutResetDispatcher mLockoutResetDispatcher;      private FaceGenerateChallengeClient mGeneratedChallengeCache = null; +    private AidlSession mSession;      private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() {          @Override @@ -361,6 +367,7 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {          mLockoutTracker = new LockoutHalImpl();          mHalResultController = new HalResultController(sensorProps.sensorId, context, mHandler,                  mScheduler, mLockoutTracker, lockoutResetDispatcher); +        mLockoutResetDispatcher = lockoutResetDispatcher;          mHalResultController.setCallback(() -> {              mDaemon = null;              mCurrentUserId = UserHandle.USER_NULL; @@ -420,6 +427,24 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {          });      } +    public int getCurrentUserId() { +        return mCurrentUserId; +    } + +    synchronized AidlSession getSession() { +        if (mDaemon != null && mSession != null) { +            return mSession; +        } else { +            return mSession = new AidlSession(mContext, this::getDaemon, mCurrentUserId, +                    new AidlResponseHandler(mContext, mScheduler, mSensorId, +                            mCurrentUserId, mLockoutTracker, mLockoutResetDispatcher, +                            new AuthSessionCoordinator(), () -> { +                        mDaemon = null; +                        mCurrentUserId = UserHandle.USER_NULL; +                    })); +        } +    } +      private synchronized IBiometricsFace getDaemon() {          if (mTestHalEnabled) {              final TestHal testHal = new TestHal(mContext, mSensorId); @@ -551,32 +576,63 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {      public void scheduleGenerateChallenge(int sensorId, int userId, @NonNull IBinder token,              @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {          mHandler.post(() -> { -            incrementChallengeCount(); +            scheduleUpdateActiveUserWithoutHandler(userId); -            if (isGeneratedChallengeCacheValid()) { -                Slog.d(TAG, "Current challenge is cached and will be reused"); -                mGeneratedChallengeCache.reuseResult(receiver); -                return; +            if (Flags.deHidl()) { +                scheduleGenerateChallengeAidl(userId, token, receiver, opPackageName); +            } else { +                scheduleGenerateChallengeHidl(userId, token, receiver, opPackageName);              } +        }); +    } -            scheduleUpdateActiveUserWithoutHandler(userId); +    private void scheduleGenerateChallengeAidl(int userId, @NonNull IBinder token, +            @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) { +        final com.android.server.biometrics.sensors.face.aidl.FaceGenerateChallengeClient client = +                new com.android.server.biometrics.sensors.face.aidl.FaceGenerateChallengeClient( +                        mContext, this::getSession, token, +                        new ClientMonitorCallbackConverter(receiver), userId, opPackageName, +                        mSensorId, createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, +                        BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), +                        mBiometricContext); +        mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { +            @Override +            public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { +                if (client != clientMonitor) { +                    Slog.e(TAG, +                            "scheduleGenerateChallenge onClientStarted, mismatched client." +                                    + " Expecting: " + client + ", received: " +                                    + clientMonitor); +                } +            } +        }); +    } -            final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext, -                    mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId, -                    opPackageName, mSensorId, -                    createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, -                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), -                    mBiometricContext, sSystemClock.millis()); -            mGeneratedChallengeCache = client; -            mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { -                @Override -                public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { -                    if (client != clientMonitor) { -                        Slog.e(TAG, "scheduleGenerateChallenge onClientStarted, mismatched client." -                                + " Expecting: " + client + ", received: " + clientMonitor); -                    } +    private void scheduleGenerateChallengeHidl(int userId, @NonNull IBinder token, +            @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) { +        incrementChallengeCount(); +        if (isGeneratedChallengeCacheValid()) { +            Slog.d(TAG, "Current challenge is cached and will be reused"); +            mGeneratedChallengeCache.reuseResult(receiver); +            return; +        } + +        final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext, +                mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId, +                opPackageName, mSensorId, createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, +                BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), +                mBiometricContext, sSystemClock.millis()); +        mGeneratedChallengeCache = client; +        mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { +            @Override +            public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { +                if (client != clientMonitor) { +                    Slog.e(TAG, +                            "scheduleGenerateChallenge onClientStarted, mismatched client." +                                    + " Expecting: " + client + ", received: " +                                    + clientMonitor);                  } -            }); +            }          });      } @@ -584,31 +640,62 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {      public void scheduleRevokeChallenge(int sensorId, int userId, @NonNull IBinder token,              @NonNull String opPackageName, long challenge) {          mHandler.post(() -> { -            final boolean shouldRevoke = decrementChallengeCount() == 0; -            if (!shouldRevoke) { -                Slog.w(TAG, "scheduleRevokeChallenge skipped - challenge still in use: " -                        + mGeneratedChallengeCount); -                return; +            if (Flags.deHidl()) { +                scheduleRevokeChallengeAidl(userId, token, opPackageName); +            } else { +                scheduleRevokeChallengeHidl(userId, token, opPackageName); +            } +        }); +    } + +    private void scheduleRevokeChallengeAidl(int userId, @NonNull IBinder token, +            @NonNull String opPackageName) { +        final com.android.server.biometrics.sensors.face.aidl.FaceRevokeChallengeClient +                client = +                new com.android.server.biometrics.sensors.face.aidl.FaceRevokeChallengeClient( +                        mContext, this::getSession, token, userId, opPackageName, mSensorId, +                        createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, +                                BiometricsProtoEnums.CLIENT_UNKNOWN, +                                mAuthenticationStatsCollector), mBiometricContext, 0L); +        mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { +            @Override +            public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, +                    boolean success) { +                if (client != clientMonitor) { +                    Slog.e(TAG, +                            "scheduleRevokeChallenge, mismatched client." + "Expecting: " +                                    + client + ", received: " + clientMonitor); +                }              } +        }); +    } -            Slog.d(TAG, "scheduleRevokeChallenge executing - no active clients"); -            mGeneratedChallengeCache = null; +    private void scheduleRevokeChallengeHidl(int userId, @NonNull IBinder token, +            @NonNull String opPackageName) { +        final boolean shouldRevoke = decrementChallengeCount() == 0; +        if (!shouldRevoke) { +            Slog.w(TAG, "scheduleRevokeChallenge skipped - challenge still in use: " +                    + mGeneratedChallengeCount); +            return; +        } -            final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext, -                    mLazyDaemon, token, userId, opPackageName, mSensorId, -                    createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, -                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), -                    mBiometricContext); -            mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { -                @Override -                public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, -                        boolean success) { -                    if (client != clientMonitor) { -                        Slog.e(TAG, "scheduleRevokeChallenge, mismatched client." -                                + "Expecting: " + client + ", received: " + clientMonitor); -                    } +        Slog.d(TAG, "scheduleRevokeChallenge executing - no active clients"); +        mGeneratedChallengeCache = null; +        final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext, +                mLazyDaemon, token, userId, opPackageName, mSensorId, +                createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, +                        BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), +                mBiometricContext); +        mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { +            @Override +            public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, +                    boolean success) { +                if (client != clientMonitor) { +                    Slog.e(TAG, +                            "scheduleRevokeChallenge, mismatched client." + "Expecting: " +                                    + client + ", received: " + clientMonitor);                  } -            }); +            }          });      } @@ -620,7 +707,62 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {          final long id = mRequestCounter.incrementAndGet();          mHandler.post(() -> {              scheduleUpdateActiveUserWithoutHandler(userId); +            if (Flags.deHidl()) { +                scheduleEnrollAidl(token, hardwareAuthToken, userId, receiver, +                        opPackageName, disabledFeatures, previewSurface, id); +            } else { +                scheduleEnrollHidl(token, hardwareAuthToken, userId, receiver, +                        opPackageName, disabledFeatures, previewSurface, id); +            } +        }); +        return id; +    } + +    private void scheduleEnrollAidl(@NonNull IBinder token, +            @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver, +            @NonNull String opPackageName, @NonNull int[] disabledFeatures, +            @Nullable Surface previewSurface, long id) { +        final com.android.server.biometrics.sensors.face.aidl.FaceEnrollClient client = +                new com.android.server.biometrics.sensors.face.aidl.FaceEnrollClient( +                        mContext, this::getSession, token, +                        new ClientMonitorCallbackConverter(receiver), userId, +                        hardwareAuthToken, opPackageName, id, +                        FaceUtils.getLegacyInstance(mSensorId), disabledFeatures, +                        ENROLL_TIMEOUT_SEC, previewSurface, mSensorId, +                        createLogger(BiometricsProtoEnums.ACTION_ENROLL, +                                BiometricsProtoEnums.CLIENT_UNKNOWN, +                                mAuthenticationStatsCollector), mBiometricContext, +                        mContext.getResources().getInteger( +                                com.android.internal.R.integer.config_faceMaxTemplatesPerUser), +                        false); + +        mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { +            @Override +            public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { +                mBiometricStateCallback.onClientStarted(clientMonitor); +            } + +            @Override +            public void onBiometricAction(int action) { +                mBiometricStateCallback.onBiometricAction(action); +            } +            @Override +            public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, +                    boolean success) { +                mBiometricStateCallback.onClientFinished(clientMonitor, success); +                if (success) { +                    // Update authenticatorIds +                    scheduleUpdateActiveUserWithoutHandler(client.getTargetUserId()); +                } +            } +        }); +    } + +    private void scheduleEnrollHidl(@NonNull IBinder token, +            @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver, +            @NonNull String opPackageName, @NonNull int[] disabledFeatures, +            @Nullable Surface previewSurface, long id) {              final FaceEnrollClient client = new FaceEnrollClient(mContext, mLazyDaemon, token,                      new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,                      opPackageName, id, FaceUtils.getLegacyInstance(mSensorId), disabledFeatures, @@ -628,7 +770,6 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {                      createLogger(BiometricsProtoEnums.ACTION_ENROLL,                              BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),                      mBiometricContext); -              mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {                  @Override                  public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { @@ -650,8 +791,6 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {                      }                  }              }); -        }); -        return id;      }      @Override @@ -683,18 +822,46 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {              scheduleUpdateActiveUserWithoutHandler(userId);              final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorId); -            final FaceAuthenticationClient client = new FaceAuthenticationClient(mContext, -                    mLazyDaemon, token, requestId, receiver, operationId, restricted, -                    options, cookie, false /* requireConfirmation */, -                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient, -                            mAuthenticationStatsCollector), -                    mBiometricContext, isStrongBiometric, mLockoutTracker, -                    mUsageStats, allowBackgroundAuthentication, -                    Utils.getCurrentStrength(mSensorId)); -            mScheduler.scheduleClientMonitor(client); +            if (Flags.deHidl()) { +                scheduleAuthenticateAidl(token, operationId, cookie, receiver, options, requestId, +                        restricted, statsClient, allowBackgroundAuthentication, isStrongBiometric); +            } else { +                scheduleAuthenticateHidl(token, operationId, cookie, receiver, options, requestId, +                        restricted, statsClient, allowBackgroundAuthentication, isStrongBiometric); +            }          });      } +    private void scheduleAuthenticateAidl(@NonNull IBinder token, long operationId, +            int cookie, @NonNull ClientMonitorCallbackConverter receiver, +            @NonNull FaceAuthenticateOptions options, long requestId, boolean restricted, +            int statsClient, boolean allowBackgroundAuthentication, boolean isStrongBiometric) { +        final com.android.server.biometrics.sensors.face.aidl.FaceAuthenticationClient +                client = +                new com.android.server.biometrics.sensors.face.aidl.FaceAuthenticationClient( +                        mContext, this::getSession, token, requestId, receiver, operationId, +                        restricted, options, cookie, false /* requireConfirmation */, +                        createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient, +                                mAuthenticationStatsCollector), mBiometricContext, +                        isStrongBiometric, mUsageStats, mLockoutTracker, +                        allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId)); +        mScheduler.scheduleClientMonitor(client); +    } + +    private void scheduleAuthenticateHidl(@NonNull IBinder token, long operationId, +            int cookie, @NonNull ClientMonitorCallbackConverter receiver, +            @NonNull FaceAuthenticateOptions options, long requestId, boolean restricted, +            int statsClient, boolean allowBackgroundAuthentication, boolean isStrongBiometric) { +        final FaceAuthenticationClient client = new FaceAuthenticationClient(mContext, +                mLazyDaemon, token, requestId, receiver, operationId, restricted, options, +                cookie, false /* requireConfirmation */, +                createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient, +                        mAuthenticationStatsCollector), mBiometricContext, +                isStrongBiometric, mLockoutTracker, mUsageStats, +                allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId)); +        mScheduler.scheduleClientMonitor(client); +    } +      @Override      public long scheduleAuthenticate(@NonNull IBinder token, long operationId,              int cookie, @NonNull ClientMonitorCallbackConverter receiver, @@ -719,13 +886,11 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {          mHandler.post(() -> {              scheduleUpdateActiveUserWithoutHandler(userId); -            final FaceRemovalClient client = new FaceRemovalClient(mContext, mLazyDaemon, token, -                    new ClientMonitorCallbackConverter(receiver), faceId, userId, opPackageName, -                    FaceUtils.getLegacyInstance(mSensorId), mSensorId, -                    createLogger(BiometricsProtoEnums.ACTION_REMOVE, -                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), -                    mBiometricContext, mAuthenticatorIds); -            mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); +            if (Flags.deHidl()) { +                scheduleRemoveAidl(token, userId, receiver, opPackageName, faceId); +            } else { +                scheduleRemoveHidl(token, userId, receiver, opPackageName, faceId); +            }          });      } @@ -736,17 +901,39 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {              scheduleUpdateActiveUserWithoutHandler(userId);              // For IBiometricsFace@1.0, remove(0) means remove all enrollments -            final FaceRemovalClient client = new FaceRemovalClient(mContext, mLazyDaemon, token, -                    new ClientMonitorCallbackConverter(receiver), 0 /* faceId */, userId, -                    opPackageName, -                    FaceUtils.getLegacyInstance(mSensorId), mSensorId, -                    createLogger(BiometricsProtoEnums.ACTION_REMOVE, -                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), -                    mBiometricContext, mAuthenticatorIds); -            mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); +            if (Flags.deHidl()) { +                scheduleRemoveAidl(token, userId, receiver, opPackageName, 0); +            } else { +                scheduleRemoveHidl(token, userId, receiver, opPackageName, 0); +            }          });      } +    private void scheduleRemoveAidl(@NonNull IBinder token, int userId, +            @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName, int faceId) { +        final com.android.server.biometrics.sensors.face.aidl.FaceRemovalClient client = +                new com.android.server.biometrics.sensors.face.aidl.FaceRemovalClient( +                        mContext, this::getSession, token, +                        new ClientMonitorCallbackConverter(receiver), new int[]{faceId}, userId, +                        opPackageName, FaceUtils.getLegacyInstance(mSensorId), mSensorId, +                        createLogger(BiometricsProtoEnums.ACTION_REMOVE, +                                BiometricsProtoEnums.CLIENT_UNKNOWN, +                                mAuthenticationStatsCollector), mBiometricContext, +                        mAuthenticatorIds); +        mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); +    } + +    private void scheduleRemoveHidl(@NonNull IBinder token, int userId, +            @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName, int faceId) { +        final FaceRemovalClient client = new FaceRemovalClient(mContext, mLazyDaemon, token, +                new ClientMonitorCallbackConverter(receiver), faceId, userId, +                opPackageName, FaceUtils.getLegacyInstance(mSensorId), mSensorId, +                createLogger(BiometricsProtoEnums.ACTION_REMOVE, +                        BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), +                mBiometricContext, mAuthenticatorIds); +        mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); +    } +      @Override      public void scheduleResetLockout(int sensorId, int userId, @NonNull byte[] hardwareAuthToken) {          mHandler.post(() -> { @@ -756,89 +943,180 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {              }              scheduleUpdateActiveUserWithoutHandler(userId); - -            final FaceResetLockoutClient client = new FaceResetLockoutClient(mContext, -                    mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId, -                    createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, -                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), -                    mBiometricContext, hardwareAuthToken); -            mScheduler.scheduleClientMonitor(client); +            if (Flags.deHidl()) { +                scheduleResetLockoutAidl(userId, hardwareAuthToken); +            } else { +                scheduleResetLockoutHidl(userId, hardwareAuthToken); +            }          });      } +    private void scheduleResetLockoutAidl(int userId, +            @NonNull byte[] hardwareAuthToken) { +        final com.android.server.biometrics.sensors.face.aidl.FaceResetLockoutClient client = +                new com.android.server.biometrics.sensors.face.aidl.FaceResetLockoutClient( +                        mContext, this::getSession, userId, mContext.getOpPackageName(), +                        mSensorId, +                        createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, +                                BiometricsProtoEnums.CLIENT_UNKNOWN, +                                mAuthenticationStatsCollector), +                        mBiometricContext, hardwareAuthToken, mLockoutTracker, +                        mLockoutResetDispatcher, mSensorProperties.sensorStrength); +        mScheduler.scheduleClientMonitor(client); +    } + +    private void scheduleResetLockoutHidl(int userId, +            @NonNull byte[] hardwareAuthToken) { +        final FaceResetLockoutClient client = new FaceResetLockoutClient(mContext, +                mLazyDaemon, +                userId, mContext.getOpPackageName(), mSensorId, +                createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, +                        BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), +                mBiometricContext, hardwareAuthToken); +        mScheduler.scheduleClientMonitor(client); +    } +      @Override      public void scheduleSetFeature(int sensorId, @NonNull IBinder token, int userId, int feature,              boolean enabled, @NonNull byte[] hardwareAuthToken,              @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {          mHandler.post(() -> { -            final List<Face> faces = getEnrolledFaces(sensorId, userId); -            if (faces.isEmpty()) { -                Slog.w(TAG, "Ignoring setFeature, no templates enrolled for user: " + userId); -                return; +            scheduleUpdateActiveUserWithoutHandler(userId); +            if (Flags.deHidl()) { +                scheduleSetFeatureAidl(sensorId, token, userId, feature, enabled, hardwareAuthToken, +                        receiver, opPackageName); +            } else { +                scheduleSetFeatureHidl(sensorId, token, userId, feature, enabled, hardwareAuthToken, +                        receiver, opPackageName);              } +        }); +    } -            scheduleUpdateActiveUserWithoutHandler(userId); +    private void scheduleSetFeatureHidl(int sensorId, @NonNull IBinder token, int userId, +            int feature, boolean enabled, @NonNull byte[] hardwareAuthToken, +            @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) { +        final List<Face> faces = getEnrolledFaces(sensorId, userId); +        if (faces.isEmpty()) { +            Slog.w(TAG, "Ignoring setFeature, no templates enrolled for user: " + userId); +            return; +        } +        final int faceId = faces.get(0).getBiometricId(); +        final FaceSetFeatureClient client = new FaceSetFeatureClient(mContext, mLazyDaemon, +                token, new ClientMonitorCallbackConverter(receiver), userId, opPackageName, +                mSensorId, BiometricLogger.ofUnknown(mContext), mBiometricContext, feature, +                enabled, hardwareAuthToken, faceId); +        mScheduler.scheduleClientMonitor(client); +    } -            final int faceId = faces.get(0).getBiometricId(); -            final FaceSetFeatureClient client = new FaceSetFeatureClient(mContext, -                    mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId, -                    opPackageName, mSensorId, BiometricLogger.ofUnknown(mContext), -                    mBiometricContext, -                    feature, enabled, hardwareAuthToken, faceId); -            mScheduler.scheduleClientMonitor(client); -        }); +    private void scheduleSetFeatureAidl(int sensorId, @NonNull IBinder token, int userId, +            int feature, boolean enabled, @NonNull byte[] hardwareAuthToken, +            @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) { +        final com.android.server.biometrics.sensors.face.aidl.FaceSetFeatureClient client = +                new com.android.server.biometrics.sensors.face.aidl.FaceSetFeatureClient( +                        mContext, this::getSession, token, +                        new ClientMonitorCallbackConverter(receiver), userId, opPackageName, +                        mSensorId, BiometricLogger.ofUnknown(mContext), mBiometricContext, +                        feature, enabled, hardwareAuthToken); +        mScheduler.scheduleClientMonitor(client);      } +      @Override      public void scheduleGetFeature(int sensorId, @NonNull IBinder token, int userId, int feature,              @Nullable ClientMonitorCallbackConverter listener, @NonNull String opPackageName) {          mHandler.post(() -> { -            final List<Face> faces = getEnrolledFaces(sensorId, userId); -            if (faces.isEmpty()) { -                Slog.w(TAG, "Ignoring getFeature, no templates enrolled for user: " + userId); -                return; +            scheduleUpdateActiveUserWithoutHandler(userId); + +            if (Flags.deHidl()) { +                scheduleGetFeatureAidl(token, userId, feature, listener, +                        opPackageName); +            } else { +                scheduleGetFeatureHidl(sensorId, token, userId, feature, listener, +                        opPackageName);              } +        }); +    } -            scheduleUpdateActiveUserWithoutHandler(userId); +    private void scheduleGetFeatureHidl(int sensorId, @NonNull IBinder token, int userId, +            int feature, @Nullable ClientMonitorCallbackConverter listener, +            @NonNull String opPackageName) { +        final List<Face> faces = getEnrolledFaces(sensorId, userId); +        if (faces.isEmpty()) { +            Slog.w(TAG, "Ignoring getFeature, no templates enrolled for user: " + userId); +            return; +        } -            final int faceId = faces.get(0).getBiometricId(); -            final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext, mLazyDaemon, -                    token, listener, userId, opPackageName, mSensorId, -                    BiometricLogger.ofUnknown(mContext), mBiometricContext, -                    feature, faceId); -            mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { -                @Override -                public void onClientFinished( -                        @NonNull BaseClientMonitor clientMonitor, boolean success) { -                    if (success && feature == BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION) { -                        final int settingsValue = client.getValue() ? 1 : 0; -                        Slog.d(TAG, "Updating attention value for user: " + userId -                                + " to value: " + settingsValue); -                        Settings.Secure.putIntForUser(mContext.getContentResolver(), -                                Settings.Secure.FACE_UNLOCK_ATTENTION_REQUIRED, -                                settingsValue, userId); -                    } +        final int faceId = faces.get(0).getBiometricId(); +        final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext, mLazyDaemon, +                token, listener, userId, opPackageName, mSensorId, +                BiometricLogger.ofUnknown(mContext), mBiometricContext, feature, faceId); +        mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { +            @Override +            public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, +                    boolean success) { +                if (success +                        && feature == BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION) { +                    final int settingsValue = client.getValue() ? 1 : 0; +                    Slog.d(TAG, +                            "Updating attention value for user: " + userId + " to value: " +                                    + settingsValue); +                    Settings.Secure.putIntForUser(mContext.getContentResolver(), +                            Settings.Secure.FACE_UNLOCK_ATTENTION_REQUIRED, settingsValue, +                            userId);                  } -            }); +            }          });      } +    private void scheduleGetFeatureAidl(@NonNull IBinder token, int userId, +            int feature, @Nullable ClientMonitorCallbackConverter listener, +            @NonNull String opPackageName) { +        final com.android.server.biometrics.sensors.face.aidl.FaceGetFeatureClient client = +                new com.android.server.biometrics.sensors.face.aidl.FaceGetFeatureClient( +                        mContext, this::getSession, token, listener, userId, opPackageName, +                        mSensorId, BiometricLogger.ofUnknown(mContext), mBiometricContext, +                        feature); +        mScheduler.scheduleClientMonitor(client); +    } +      private void scheduleInternalCleanup(int userId,              @Nullable ClientMonitorCallback callback) {          mHandler.post(() -> {              scheduleUpdateActiveUserWithoutHandler(userId); - -            final FaceInternalCleanupClient client = new FaceInternalCleanupClient(mContext, -                    mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId, -                    createLogger(BiometricsProtoEnums.ACTION_ENUMERATE, -                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), -                    mBiometricContext, -                    FaceUtils.getLegacyInstance(mSensorId), mAuthenticatorIds); -            mScheduler.scheduleClientMonitor(client, new ClientMonitorCompositeCallback(callback, -                    mBiometricStateCallback)); +            if (Flags.deHidl()) { +                scheduleInternalCleanupAidl(userId, callback); +            } else { +                scheduleInternalCleanupHidl(userId, callback); +            }          });      } +    private void scheduleInternalCleanupHidl(int userId, +            @Nullable ClientMonitorCallback callback) { +        final FaceInternalCleanupClient client = new FaceInternalCleanupClient(mContext, +                mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId, +                createLogger(BiometricsProtoEnums.ACTION_ENUMERATE, +                        BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), +                mBiometricContext, FaceUtils.getLegacyInstance(mSensorId), +                mAuthenticatorIds); +        mScheduler.scheduleClientMonitor(client, +                new ClientMonitorCompositeCallback(callback, mBiometricStateCallback)); +    } + +    private void scheduleInternalCleanupAidl(int userId, +            @Nullable ClientMonitorCallback callback) { +        final com.android.server.biometrics.sensors.face.aidl.FaceInternalCleanupClient +                client = +                new com.android.server.biometrics.sensors.face.aidl.FaceInternalCleanupClient( +                        mContext, this::getSession, userId, mContext.getOpPackageName(), +                        mSensorId, createLogger(BiometricsProtoEnums.ACTION_ENUMERATE, +                        BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), +                        mBiometricContext, FaceUtils.getLegacyInstance(mSensorId), +                        mAuthenticatorIds); +        mScheduler.scheduleClientMonitor(client, +                new ClientMonitorCompositeCallback(callback, mBiometricStateCallback)); +    } +      @Override      public void scheduleInternalCleanup(int sensorId, int userId,              @Nullable ClientMonitorCallback callback) { @@ -970,6 +1248,10 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {              public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,                      boolean success) {                  if (success) { +                    if (mCurrentUserId != targetUserId) { +                        // Create new session with updated user ID +                        mSession = null; +                    }                      mCurrentUserId = targetUserId;                  } else {                      Slog.w(TAG, "Failed to change user, still: " + mCurrentUserId); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlCallbackConverter.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlCallbackConverter.java new file mode 100644 index 000000000000..36a9790d2d4b --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlCallbackConverter.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.face.hidl; + +import android.hardware.biometrics.face.V1_0.IBiometricsFaceClientCallback; + +import com.android.server.biometrics.HardwareAuthTokenUtils; +import com.android.server.biometrics.sensors.face.aidl.AidlResponseHandler; + +import java.util.ArrayList; + +/** + * Convert HIDL-specific callback interface {@link IBiometricsFaceClientCallback} to AIDL + * response handler. + */ +public class HidlToAidlCallbackConverter extends IBiometricsFaceClientCallback.Stub { + +    private final AidlResponseHandler mAidlResponseHandler; + +    public HidlToAidlCallbackConverter(AidlResponseHandler aidlResponseHandler) { +        mAidlResponseHandler = aidlResponseHandler; +    } + +    @Override +    public void onEnrollResult( +            long deviceId, int faceId, int userId, int remaining) { +        mAidlResponseHandler.onEnrollmentProgress(faceId, remaining); +    } + +    @Override +    public void onAuthenticated(long deviceId, int faceId, int userId, +            ArrayList<Byte> token) { +        final boolean authenticated = faceId != 0; +        byte[] hardwareAuthToken = new byte[token.size()]; + +        for (int i = 0; i < token.size(); i++) { +            hardwareAuthToken[i] = token.get(i); +        } + +        if (authenticated) { +            mAidlResponseHandler.onAuthenticationSucceeded(faceId, +                    HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken)); +        } else { +            mAidlResponseHandler.onAuthenticationFailed(); +        } +    } + +    @Override +    public void onAcquired(long deviceId, int userId, int acquiredInfo, +            int vendorCode) { +        mAidlResponseHandler.onAcquired(acquiredInfo, vendorCode); +    } + +    @Override +    public void onError(long deviceId, int userId, int error, int vendorCode) { +        mAidlResponseHandler.onError(error, vendorCode); +    } + +    @Override +    public void onRemoved(long deviceId, ArrayList<Integer> removed, int userId) { +        int[] enrollmentIds = new int[removed.size()]; +        for (int i = 0; i < removed.size(); i++) { +            enrollmentIds[i] = removed.get(i); +        } +        mAidlResponseHandler.onEnrollmentsRemoved(enrollmentIds); +    } + +    @Override +    public void onEnumerate(long deviceId, ArrayList<Integer> faceIds, int userId) { +        int[] enrollmentIds = new int[faceIds.size()]; +        for (int i = 0; i < faceIds.size(); i++) { +            enrollmentIds[i] = faceIds.get(i); +        } +        mAidlResponseHandler.onEnrollmentsEnumerated(enrollmentIds); +    } + +    @Override +    public void onLockoutChanged(long duration) { +        mAidlResponseHandler.onLockoutChanged(duration); +    } + +    void onChallengeGenerated(long challenge) { +        mAidlResponseHandler.onChallengeGenerated(challenge); +    } + +    void onChallengeRevoked(long challenge) { +        mAidlResponseHandler.onChallengeRevoked(challenge); +    } + +    void onFeatureGet(byte[] features) { +        mAidlResponseHandler.onFeaturesRetrieved(features); +    } + +    void onFeatureSet(byte feature) { +        mAidlResponseHandler.onFeatureSet(feature); +    } + +    void onAuthenticatorIdRetrieved(long authenticatorId) { +        mAidlResponseHandler.onAuthenticatorIdRetrieved(authenticatorId); +    } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java new file mode 100644 index 000000000000..4a019436cf6f --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java @@ -0,0 +1,274 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.fingerprint.aidl; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.hardware.biometrics.face.Error; +import android.hardware.biometrics.fingerprint.ISessionCallback; +import android.hardware.fingerprint.Fingerprint; +import android.hardware.keymaster.HardwareAuthToken; +import android.util.Slog; + +import com.android.server.biometrics.HardwareAuthTokenUtils; +import com.android.server.biometrics.Utils; +import com.android.server.biometrics.sensors.AcquisitionClient; +import com.android.server.biometrics.sensors.AuthSessionCoordinator; +import com.android.server.biometrics.sensors.AuthenticationConsumer; +import com.android.server.biometrics.sensors.BaseClientMonitor; +import com.android.server.biometrics.sensors.BiometricScheduler; +import com.android.server.biometrics.sensors.EnumerateConsumer; +import com.android.server.biometrics.sensors.ErrorConsumer; +import com.android.server.biometrics.sensors.LockoutCache; +import com.android.server.biometrics.sensors.LockoutConsumer; +import com.android.server.biometrics.sensors.LockoutResetDispatcher; +import com.android.server.biometrics.sensors.RemovalConsumer; +import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils; + +import java.util.ArrayList; +import java.util.function.Consumer; + + +/** + * Response handler for the {@link ISessionCallback} HAL AIDL interface. + */ +public class AidlResponseHandler extends ISessionCallback.Stub { + +    /** +     * Interface to send results to the AidlResponseHandler's owner. +     */ +    public interface HardwareUnavailableCallback { +        /** +         * Invoked when the HAL sends ERROR_HW_UNAVAILABLE. +         */ +        void onHardwareUnavailable(); +    } + +    private static final String TAG = "AidlResponseHandler"; + +    @NonNull +    private final Context mContext; +    @NonNull +    private final BiometricScheduler mScheduler; +    private final int mSensorId; +    private final int mUserId; +    @NonNull +    private final LockoutCache mLockoutCache; +    @NonNull +    private final LockoutResetDispatcher mLockoutResetDispatcher; +    @NonNull +    private final AuthSessionCoordinator mAuthSessionCoordinator; +    @NonNull +    private final HardwareUnavailableCallback mHardwareUnavailableCallback; + +    public AidlResponseHandler(@NonNull Context context, +            @NonNull BiometricScheduler scheduler, int sensorId, int userId, +            @NonNull LockoutCache lockoutTracker, +            @NonNull LockoutResetDispatcher lockoutResetDispatcher, +            @NonNull AuthSessionCoordinator authSessionCoordinator, +            @NonNull HardwareUnavailableCallback hardwareUnavailableCallback) { +        mContext = context; +        mScheduler = scheduler; +        mSensorId = sensorId; +        mUserId = userId; +        mLockoutCache = lockoutTracker; +        mLockoutResetDispatcher = lockoutResetDispatcher; +        mAuthSessionCoordinator = authSessionCoordinator; +        mHardwareUnavailableCallback = hardwareUnavailableCallback; +    } + +    @Override +    public int getInterfaceVersion() { +        return this.VERSION; +    } + +    @Override +    public String getInterfaceHash() { +        return this.HASH; +    } + +    @Override +    public void onChallengeGenerated(long challenge) { +        handleResponse(FingerprintGenerateChallengeClient.class, (c) -> c.onChallengeGenerated( +                mSensorId, mUserId, challenge), null); +    } + +    @Override +    public void onChallengeRevoked(long challenge) { +        handleResponse(FingerprintRevokeChallengeClient.class, (c) -> c.onChallengeRevoked( +                challenge), null); +    } + +    /** +     * Handles acquired messages sent by the HAL (specifically for HIDL HAL). +     */ +    public void onAcquired(int acquiredInfo, int vendorCode) { +        handleResponse(AcquisitionClient.class, (c) -> c.onAcquired(acquiredInfo, vendorCode), +                null); +    } + +    @Override +    public void onAcquired(byte info, int vendorCode) { +        handleResponse(AcquisitionClient.class, (c) -> c.onAcquired( +                AidlConversionUtils.toFrameworkAcquiredInfo(info), vendorCode), null); +    } + +    /** +     * Handle error messages from the HAL. +     */ +    public void onError(int error, int vendorCode) { +        handleResponse(ErrorConsumer.class, (c) -> { +            c.onError(error, vendorCode); +            if (error == Error.HW_UNAVAILABLE) { +                mHardwareUnavailableCallback.onHardwareUnavailable(); +            } +        }, null); +    } + +    @Override +    public void onError(byte error, int vendorCode) { +        onError(AidlConversionUtils.toFrameworkError(error), vendorCode); +    } + +    @Override +    public void onEnrollmentProgress(int enrollmentId, int remaining) { +        BaseClientMonitor client = mScheduler.getCurrentClient(); +        final int currentUserId; +        if (client == null) { +            return; +        } else { +            currentUserId = client.getTargetUserId(); +        } +        final CharSequence name = FingerprintUtils.getInstance(mSensorId) +                .getUniqueName(mContext, currentUserId); +        final Fingerprint fingerprint = new Fingerprint(name, currentUserId, +                enrollmentId, mSensorId); +        handleResponse(FingerprintEnrollClient.class, (c) -> c.onEnrollResult(fingerprint, +                remaining), null); +    } + +    @Override +    public void onAuthenticationSucceeded(int enrollmentId, HardwareAuthToken hat) { +        final Fingerprint fp = new Fingerprint("", enrollmentId, mSensorId); +        final byte[] byteArray = HardwareAuthTokenUtils.toByteArray(hat); +        final ArrayList<Byte> byteList = new ArrayList<>(); +        for (byte b : byteArray) { +            byteList.add(b); +        } +        handleResponse(AuthenticationConsumer.class, (c) -> c.onAuthenticated(fp, +                true /* authenticated */, byteList), (c) -> onInteractionDetected()); +    } + +    @Override +    public void onAuthenticationFailed() { +        final Fingerprint fp = new Fingerprint("", 0 /* enrollmentId */, mSensorId); +        handleResponse(AuthenticationConsumer.class, (c) -> c.onAuthenticated(fp, +                false /* authenticated */, null /* hardwareAuthToken */), +                (c) -> onInteractionDetected()); +    } + +    @Override +    public void onLockoutTimed(long durationMillis) { +        handleResponse(LockoutConsumer.class, (c) -> c.onLockoutTimed(durationMillis), +                null); +    } + +    @Override +    public void onLockoutPermanent() { +        handleResponse(LockoutConsumer.class, LockoutConsumer::onLockoutPermanent, null); +    } + +    @Override +    public void onLockoutCleared() { +        handleResponse(FingerprintResetLockoutClient.class, +                FingerprintResetLockoutClient::onLockoutCleared, +                (c) -> FingerprintResetLockoutClient.resetLocalLockoutStateToNone( +                        mSensorId, mUserId, mLockoutCache, mLockoutResetDispatcher, +                        mAuthSessionCoordinator, Utils.getCurrentStrength(mSensorId), +                        -1 /* requestId */)); +    } + +    @Override +    public void onInteractionDetected() { +        handleResponse(FingerprintDetectClient.class, +                FingerprintDetectClient::onInteractionDetected, null); +    } + +    @Override +    public void onEnrollmentsEnumerated(int[] enrollmentIds) { +        if (enrollmentIds.length > 0) { +            for (int i = 0; i < enrollmentIds.length; i++) { +                final Fingerprint fp = new Fingerprint("", enrollmentIds[i], mSensorId); +                int finalI = i; +                handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(fp, +                        enrollmentIds.length - finalI - 1), null); +            } +        } else { +            handleResponse(EnumerateConsumer.class, (c) -> c.onEnumerationResult(null, +                    0), null); +        } +    } + +    @Override +    public void onEnrollmentsRemoved(int[] enrollmentIds) { +        if (enrollmentIds.length > 0) { +            for (int i  = 0; i < enrollmentIds.length; i++) { +                final Fingerprint fp = new Fingerprint("", enrollmentIds[i], mSensorId); +                int finalI = i; +                handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(fp, +                        enrollmentIds.length - finalI - 1), null); +            } +        } else { +            handleResponse(RemovalConsumer.class, (c) -> c.onRemoved(null, 0), +                    null); +        } +    } + +    @Override +    public void onAuthenticatorIdRetrieved(long authenticatorId) { +        handleResponse(FingerprintGetAuthenticatorIdClient.class, +                (c) -> c.onAuthenticatorIdRetrieved(authenticatorId), null); +    } + +    @Override +    public void onAuthenticatorIdInvalidated(long newAuthenticatorId) { +        handleResponse(FingerprintInvalidationClient.class, (c) -> c.onAuthenticatorIdInvalidated( +                newAuthenticatorId), null); +    } + +    private <T> void handleResponse(@NonNull Class<T> className, +            @NonNull Consumer<T> action, +            @Nullable Consumer<BaseClientMonitor> alternateAction) { +        mScheduler.getHandler().post(() -> { +            final BaseClientMonitor client = mScheduler.getCurrentClient(); +            if (className.isInstance(client)) { +                action.accept((T) client); +            } else { +                Slog.e(TAG, "Client monitor is not an instance of " + className.getName()); +                if (alternateAction != null) { +                    alternateAction.accept(client); +                } +            } +        }); +    } + +    @Override +    public void onSessionClosed() { +        mScheduler.getHandler().post(mScheduler::onUserStopped); +    } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java index 55861bb4cc6f..299a310caee9 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlSession.java @@ -16,10 +16,13 @@  package com.android.server.biometrics.sensors.fingerprint.aidl; -import static com.android.server.biometrics.sensors.fingerprint.aidl.Sensor.HalSessionCallback; -  import android.annotation.NonNull;  import android.hardware.biometrics.fingerprint.ISession; +import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; + +import com.android.server.biometrics.sensors.fingerprint.hidl.AidlToHidlAdapter; + +import java.util.function.Supplier;  /**   * A holder for an AIDL {@link ISession} with additional metadata about the current user @@ -30,14 +33,22 @@ public class AidlSession {      private final int mHalInterfaceVersion;      @NonNull private final ISession mSession;      private final int mUserId; -    @NonNull private final HalSessionCallback mHalSessionCallback; +    @NonNull private final AidlResponseHandler mAidlResponseHandler;      public AidlSession(int halInterfaceVersion, @NonNull ISession session, int userId, -            HalSessionCallback halSessionCallback) { +            AidlResponseHandler aidlResponseHandler) {          mHalInterfaceVersion = halInterfaceVersion;          mSession = session;          mUserId = userId; -        mHalSessionCallback = halSessionCallback; +        mAidlResponseHandler = aidlResponseHandler; +    } + +    public AidlSession(@NonNull Supplier<IBiometricsFingerprint> session, +            int userId, AidlResponseHandler aidlResponseHandler) { +        mSession = new AidlToHidlAdapter(session, userId, aidlResponseHandler); +        mHalInterfaceVersion = 0; +        mUserId = userId; +        mAidlResponseHandler = aidlResponseHandler;      }      /** The underlying {@link ISession}. */ @@ -51,8 +62,8 @@ public class AidlSession {      }      /** The HAL callback, which should only be used in tests {@See BiometricTestSessionImpl}. */ -    HalSessionCallback getHalSessionCallback() { -        return mHalSessionCallback; +    AidlResponseHandler getHalSessionCallback() { +        return mAidlResponseHandler;      }      /** diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java index 54d1faa39be0..337c3c299e54 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java @@ -21,6 +21,7 @@ import android.annotation.Nullable;  import android.app.TaskStackListener;  import android.content.Context;  import android.hardware.biometrics.BiometricAuthenticator; +import android.hardware.biometrics.BiometricConstants;  import android.hardware.biometrics.BiometricFingerprintConstants;  import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired;  import android.hardware.biometrics.BiometricManager.Authenticators; @@ -51,8 +52,8 @@ import com.android.server.biometrics.sensors.BiometricNotificationUtils;  import com.android.server.biometrics.sensors.ClientMonitorCallback;  import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;  import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback; -import com.android.server.biometrics.sensors.LockoutCache;  import com.android.server.biometrics.sensors.LockoutConsumer; +import com.android.server.biometrics.sensors.LockoutTracker;  import com.android.server.biometrics.sensors.PerformanceTracker;  import com.android.server.biometrics.sensors.SensorOverlays;  import com.android.server.biometrics.sensors.fingerprint.PowerPressHandler; @@ -66,7 +67,7 @@ import java.util.function.Supplier;   * Fingerprint-specific authentication client supporting the {@link   * android.hardware.biometrics.fingerprint.IFingerprint} AIDL interface.   */ -class FingerprintAuthenticationClient +public class FingerprintAuthenticationClient          extends AuthenticationClient<AidlSession, FingerprintAuthenticateOptions>          implements Udfps, LockoutConsumer, PowerPressHandler {      private static final String TAG = "FingerprintAuthenticationClient"; @@ -93,7 +94,7 @@ class FingerprintAuthenticationClient      private Runnable mAuthSuccessRunnable;      private final Clock mClock; -    FingerprintAuthenticationClient( +    public FingerprintAuthenticationClient(              @NonNull Context context,              @NonNull Supplier<AidlSession> lazyDaemon,              @NonNull IBinder token, @@ -108,14 +109,14 @@ class FingerprintAuthenticationClient              @NonNull BiometricContext biometricContext,              boolean isStrongBiometric,              @Nullable TaskStackListener taskStackListener, -            @NonNull LockoutCache lockoutCache,              @Nullable IUdfpsOverlayController udfpsOverlayController,              @Nullable ISidefpsController sidefpsController,              boolean allowBackgroundAuthentication,              @NonNull FingerprintSensorPropertiesInternal sensorProps,              @NonNull Handler handler,              @Authenticators.Types int biometricStrength, -            @NonNull Clock clock) { +            @NonNull Clock clock, +            @Nullable LockoutTracker lockoutTracker) {          super(                  context,                  lazyDaemon, @@ -130,7 +131,7 @@ class FingerprintAuthenticationClient                  biometricContext,                  isStrongBiometric,                  taskStackListener, -                null /* lockoutCache */, +                lockoutTracker,                  allowBackgroundAuthentication,                  false /* shouldVibrate */,                  biometricStrength); @@ -211,6 +212,7 @@ class FingerprintAuthenticationClient              boolean authenticated,              ArrayList<Byte> token) {          super.onAuthenticated(identifier, authenticated, token); +        handleLockout(authenticated);          if (authenticated) {              mState = STATE_STOPPED;              mSensorOverlays.hide(getSensorId()); @@ -219,6 +221,32 @@ class FingerprintAuthenticationClient          }      } +    private void handleLockout(boolean authenticated) { +        if (getLockoutTracker() == null) { +            Slog.d(TAG, "Lockout is implemented by the HAL"); +            return; +        } +        if (authenticated) { +            getLockoutTracker().resetFailedAttemptsForUser(true /* clearAttemptCounter */, +                    getTargetUserId()); +        } else { +            @LockoutTracker.LockoutMode final int lockoutMode = +                    getLockoutTracker().getLockoutModeForUser(getTargetUserId()); +            if (lockoutMode != LockoutTracker.LOCKOUT_NONE) { +                Slog.w(TAG, "Fingerprint locked out, lockoutMode(" + lockoutMode + ")"); +                final int errorCode = lockoutMode == LockoutTracker.LOCKOUT_TIMED +                        ? BiometricConstants.BIOMETRIC_ERROR_LOCKOUT +                        : BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT; +                // Send the error, but do not invoke the FinishCallback yet. Since lockout is not +                // controlled by the HAL, the framework must stop the sensor before finishing the +                // client. +                mSensorOverlays.hide(getSensorId()); +                onErrorInternal(errorCode, 0 /* vendorCode */, false /* finish */); +                cancel(); +            } +        } +    } +      @Override      public void onAcquired(@FingerprintAcquired int acquiredInfo, int vendorCode) {          // For UDFPS, notify SysUI with acquiredInfo, so that the illumination can be turned off diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java index 4502e5d0c4b6..e2413ee1c016 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java @@ -43,7 +43,8 @@ import java.util.function.Supplier;   * Performs fingerprint detection without exposing any matching information (e.g. accept/reject   * have the same haptic, lockout counter is not increased).   */ -class FingerprintDetectClient extends AcquisitionClient<AidlSession> implements DetectionConsumer { +public class FingerprintDetectClient extends AcquisitionClient<AidlSession> +        implements DetectionConsumer {      private static final String TAG = "FingerprintDetectClient"; @@ -52,7 +53,8 @@ class FingerprintDetectClient extends AcquisitionClient<AidlSession> implements      @NonNull private final SensorOverlays mSensorOverlays;      @Nullable private ICancellationSignal mCancellationSignal; -    FingerprintDetectClient(@NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon, +    public FingerprintDetectClient(@NonNull Context context, +            @NonNull Supplier<AidlSession> lazyDaemon,              @NonNull IBinder token, long requestId,              @NonNull ClientMonitorCallbackConverter listener,              @NonNull FingerprintAuthenticateOptions options, diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java index 46ff6b4fab1a..06550d8b4fce 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java @@ -49,14 +49,13 @@ import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;  import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;  import com.android.server.biometrics.sensors.EnrollClient;  import com.android.server.biometrics.sensors.SensorOverlays; -import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;  import com.android.server.biometrics.sensors.fingerprint.PowerPressHandler;  import com.android.server.biometrics.sensors.fingerprint.Udfps;  import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;  import java.util.function.Supplier; -class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps, +public class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps,          PowerPressHandler {      private static final String TAG = "FingerprintEnrollClient"; @@ -72,12 +71,16 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps      private static boolean shouldVibrateFor(Context context,              FingerprintSensorPropertiesInternal sensorProps) { -        final AccessibilityManager am = context.getSystemService(AccessibilityManager.class); -        final boolean isAccessbilityEnabled = am.isTouchExplorationEnabled(); -        return !sensorProps.isAnyUdfpsType() || isAccessbilityEnabled; +        if (sensorProps != null) { +            final AccessibilityManager am = context.getSystemService(AccessibilityManager.class); +            final boolean isAccessbilityEnabled = am.isTouchExplorationEnabled(); +            return !sensorProps.isAnyUdfpsType() || isAccessbilityEnabled; +        } else { +            return true; +        }      } -    FingerprintEnrollClient(@NonNull Context context, +    public FingerprintEnrollClient(@NonNull Context context,              @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, long requestId,              @NonNull ClientMonitorCallbackConverter listener, int userId,              @NonNull byte[] hardwareAuthToken, @NonNull String owner, @@ -89,8 +92,8 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps              int maxTemplatesPerUser, @FingerprintManager.EnrollReason int enrollReason) {          // UDFPS haptics occur when an image is acquired (instead of when the result is known)          super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils, -                0 /* timeoutSec */, sensorId, shouldVibrateFor(context, sensorProps), logger, -                biometricContext); +                0 /* timeoutSec */, sensorId, shouldVibrateFor(context, sensorProps), +                logger, biometricContext);          setRequestId(requestId);          mSensorProps = sensorProps;          mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController); @@ -136,7 +139,7 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps                  acquiredInfo == BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD;          // For UDFPS, notify SysUI that the illumination can be turned off.          // See AcquiredInfo#GOOD and AcquiredInfo#RETRYING_CAPTURE -        if (mSensorProps.isAnyUdfpsType()) { +        if (mSensorProps != null && mSensorProps.isAnyUdfpsType()) {              if (acquiredGood && mShouldVibrate) {                  vibrateSuccess();              } @@ -162,8 +165,7 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps      @Override      protected boolean hasReachedEnrollmentLimit() { -        return FingerprintUtils.getInstance(getSensorId()) -                .getBiometricsForUser(getContext(), getTargetUserId()).size() +        return mBiometricUtils.getBiometricsForUser(getContext(), getTargetUserId()).size()                  >= mMaxTemplatesPerUser;      } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java index ddae8bedf7c1..ce693ff58e19 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClient.java @@ -33,10 +33,10 @@ import java.util.function.Supplier;  /**   * Fingerprint-specific generateChallenge client for the {@link IFingerprint} AIDL HAL interface.   */ -class FingerprintGenerateChallengeClient extends GenerateChallengeClient<AidlSession> { +public class FingerprintGenerateChallengeClient extends GenerateChallengeClient<AidlSession> {      private static final String TAG = "FingerprintGenerateChallengeClient"; -    FingerprintGenerateChallengeClient(@NonNull Context context, +    public FingerprintGenerateChallengeClient(@NonNull Context context,              @NonNull Supplier<AidlSession> lazyDaemon,              @NonNull IBinder token,              @NonNull ClientMonitorCallbackConverter listener, diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java index ff9127f516af..5edc2ca080ad 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java @@ -39,9 +39,10 @@ import java.util.function.Supplier;   * Fingerprint-specific internal cleanup client supporting the   * {@link android.hardware.biometrics.fingerprint.IFingerprint} AIDL interface.   */ -class FingerprintInternalCleanupClient extends InternalCleanupClient<Fingerprint, AidlSession> { +public class FingerprintInternalCleanupClient +        extends InternalCleanupClient<Fingerprint, AidlSession> { -    FingerprintInternalCleanupClient(@NonNull Context context, +    public FingerprintInternalCleanupClient(@NonNull Context context,              @NonNull Supplier<AidlSession> lazyDaemon,              int userId, @NonNull String owner, int sensorId,              @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java index e42b66472cbe..9985b06833c9 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java @@ -428,8 +428,8 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi              final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext,                      mFingerprintSensors.get(sensorId).getLazySession(), token, id,                      new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken, -                    opPackageName, FingerprintUtils.getInstance(sensorId), sensorId, -                    createLogger(BiometricsProtoEnums.ACTION_ENROLL, +                    opPackageName, FingerprintUtils.getInstance(sensorId), +                    sensorId, createLogger(BiometricsProtoEnums.ACTION_ENROLL,                              BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),                      mBiometricContext,                      mFingerprintSensors.get(sensorId).getSensorProperties(), @@ -496,12 +496,13 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi                      createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,                              mAuthenticationStatsCollector),                      mBiometricContext, isStrongBiometric, -                    mTaskStackListener, mFingerprintSensors.get(sensorId).getLockoutCache(), +                    mTaskStackListener,                      mUdfpsOverlayController, mSidefpsController,                      allowBackgroundAuthentication,                      mFingerprintSensors.get(sensorId).getSensorProperties(), mHandler,                      Utils.getCurrentStrength(sensorId), -                    SystemClock.elapsedRealtimeClock()); +                    SystemClock.elapsedRealtimeClock(), +                    null /* lockoutTracker */);              scheduleForSensor(sensorId, client, new ClientMonitorCallback() {                  @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java index d559bb1d72f1..4f08f6fbde54 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClient.java @@ -37,12 +37,12 @@ import java.util.function.Supplier;   * Fingerprint-specific removal client supporting the   * {@link android.hardware.biometrics.fingerprint.IFingerprint} interface.   */ -class FingerprintRemovalClient extends RemovalClient<Fingerprint, AidlSession> { +public class FingerprintRemovalClient extends RemovalClient<Fingerprint, AidlSession> {      private static final String TAG = "FingerprintRemovalClient";      private final int[] mBiometricIds; -    FingerprintRemovalClient(@NonNull Context context, +    public FingerprintRemovalClient(@NonNull Context context,              @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token,              @Nullable ClientMonitorCallbackConverter listener, int[] biometricIds, int userId,              @NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int sensorId, diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java index 7a620349075c..ec225a60d54b 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClient.java @@ -32,7 +32,6 @@ import com.android.server.biometrics.sensors.AuthSessionCoordinator;  import com.android.server.biometrics.sensors.ClientMonitorCallback;  import com.android.server.biometrics.sensors.ErrorConsumer;  import com.android.server.biometrics.sensors.HalClientMonitor; -import com.android.server.biometrics.sensors.LockoutCache;  import com.android.server.biometrics.sensors.LockoutResetDispatcher;  import com.android.server.biometrics.sensors.LockoutTracker; @@ -43,19 +42,20 @@ import java.util.function.Supplier;   * Updates the framework's lockout cache and notifies clients such as Keyguard when lockout is   * cleared.   */ -class FingerprintResetLockoutClient extends HalClientMonitor<AidlSession> implements ErrorConsumer { +public class FingerprintResetLockoutClient extends HalClientMonitor<AidlSession> +        implements ErrorConsumer {      private static final String TAG = "FingerprintResetLockoutClient";      private final HardwareAuthToken mHardwareAuthToken; -    private final LockoutCache mLockoutCache; +    private final LockoutTracker mLockoutCache;      private final LockoutResetDispatcher mLockoutResetDispatcher;      private final int mBiometricStrength; -    FingerprintResetLockoutClient(@NonNull Context context, +    public FingerprintResetLockoutClient(@NonNull Context context,              @NonNull Supplier<AidlSession> lazyDaemon, int userId, String owner, int sensorId,              @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext, -            @NonNull byte[] hardwareAuthToken, @NonNull LockoutCache lockoutTracker, +            @NonNull byte[] hardwareAuthToken, @NonNull LockoutTracker lockoutTracker,              @NonNull LockoutResetDispatcher lockoutResetDispatcher,              @Authenticators.Types int biometricStrength) {          super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner, @@ -107,10 +107,11 @@ class FingerprintResetLockoutClient extends HalClientMonitor<AidlSession> implem       * be used instead.       */      static void resetLocalLockoutStateToNone(int sensorId, int userId, -            @NonNull LockoutCache lockoutTracker, +            @NonNull LockoutTracker lockoutTracker,              @NonNull LockoutResetDispatcher lockoutResetDispatcher,              @NonNull AuthSessionCoordinator authSessionCoordinator,              @Authenticators.Types int biometricStrength, long requestId) { +        lockoutTracker.resetFailedAttemptsForUser(true /* clearAttemptCounter */, userId);          lockoutTracker.setLockoutModeForUser(userId, LockoutTracker.LOCKOUT_NONE);          lockoutResetDispatcher.notifyLockoutResetCallbacks(sensorId);          authSessionCoordinator.resetLockoutFor(userId, biometricStrength, requestId); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java index afa62e2e3173..23d8e1e63f63 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClient.java @@ -32,13 +32,13 @@ import java.util.function.Supplier;  /**   * Fingerprint-specific revokeChallenge client for the {@link IFingerprint} AIDL HAL interface.   */ -class FingerprintRevokeChallengeClient extends RevokeChallengeClient<AidlSession> { +public class FingerprintRevokeChallengeClient extends RevokeChallengeClient<AidlSession> { -    private static final String TAG = "FingerpirntRevokeChallengeClient"; +    private static final String TAG = "FingerprintRevokeChallengeClient";      private final long mChallenge; -    FingerprintRevokeChallengeClient(@NonNull Context context, +    public FingerprintRevokeChallengeClient(@NonNull Context context,              @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token,              int userId, @NonNull String owner, int sensorId,              @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext, @@ -57,7 +57,7 @@ class FingerprintRevokeChallengeClient extends RevokeChallengeClient<AidlSession          }      } -    void onChallengeRevoked(int sensorId, int userId, long challenge) { +    void onChallengeRevoked(long challenge) {          final boolean success = challenge == mChallenge;          mCallback.onClientFinished(FingerprintRevokeChallengeClient.this, success);      } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java index 56b85ceb8e6b..893cb8f9b4fc 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java @@ -23,13 +23,9 @@ import android.content.pm.UserInfo;  import android.hardware.biometrics.BiometricsProtoEnums;  import android.hardware.biometrics.ITestSession;  import android.hardware.biometrics.ITestSessionCallback; -import android.hardware.biometrics.fingerprint.Error;  import android.hardware.biometrics.fingerprint.ISession; -import android.hardware.biometrics.fingerprint.ISessionCallback; -import android.hardware.fingerprint.Fingerprint;  import android.hardware.fingerprint.FingerprintManager;  import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; -import android.hardware.keymaster.HardwareAuthToken;  import android.os.Binder;  import android.os.Handler;  import android.os.IBinder; @@ -39,34 +35,25 @@ import android.os.UserManager;  import android.util.Slog;  import android.util.proto.ProtoOutputStream; -import com.android.internal.annotations.VisibleForTesting;  import com.android.internal.util.FrameworkStatsLog; -import com.android.server.biometrics.HardwareAuthTokenUtils;  import com.android.server.biometrics.SensorServiceStateProto;  import com.android.server.biometrics.SensorStateProto;  import com.android.server.biometrics.UserStateProto;  import com.android.server.biometrics.Utils;  import com.android.server.biometrics.log.BiometricContext;  import com.android.server.biometrics.log.BiometricLogger; -import com.android.server.biometrics.sensors.AcquisitionClient; -import com.android.server.biometrics.sensors.AuthSessionCoordinator; -import com.android.server.biometrics.sensors.AuthenticationConsumer;  import com.android.server.biometrics.sensors.BaseClientMonitor;  import com.android.server.biometrics.sensors.BiometricScheduler;  import com.android.server.biometrics.sensors.BiometricStateCallback; -import com.android.server.biometrics.sensors.EnumerateConsumer;  import com.android.server.biometrics.sensors.ErrorConsumer;  import com.android.server.biometrics.sensors.LockoutCache; -import com.android.server.biometrics.sensors.LockoutConsumer;  import com.android.server.biometrics.sensors.LockoutResetDispatcher; -import com.android.server.biometrics.sensors.RemovalConsumer;  import com.android.server.biometrics.sensors.StartUserClient;  import com.android.server.biometrics.sensors.StopUserClient;  import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;  import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;  import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher; -import java.util.ArrayList;  import java.util.HashMap;  import java.util.Map;  import java.util.function.Supplier; @@ -93,348 +80,6 @@ public class Sensor {      @Nullable AidlSession mCurrentSession;      @NonNull private final Supplier<AidlSession> mLazySession; -    @VisibleForTesting -    public static class HalSessionCallback extends ISessionCallback.Stub { - -        /** -         * Interface to sends results to the HalSessionCallback's owner. -         */ -        public interface Callback { -            /** -             * Invoked when the HAL sends ERROR_HW_UNAVAILABLE. -             */ -            void onHardwareUnavailable(); -        } - -        @NonNull -        private final Context mContext; -        @NonNull -        private final Handler mHandler; -        @NonNull -        private final String mTag; -        @NonNull -        private final UserAwareBiometricScheduler mScheduler; -        private final int mSensorId; -        private final int mUserId; -        @NonNull -        private final LockoutCache mLockoutCache; -        @NonNull -        private final LockoutResetDispatcher mLockoutResetDispatcher; -        @NonNull -        private AuthSessionCoordinator mAuthSessionCoordinator; -        @NonNull -        private final Callback mCallback; - -        HalSessionCallback(@NonNull Context context, @NonNull Handler handler, @NonNull String tag, -                @NonNull UserAwareBiometricScheduler scheduler, int sensorId, int userId, -                @NonNull LockoutCache lockoutTracker, -                @NonNull LockoutResetDispatcher lockoutResetDispatcher, -                @NonNull AuthSessionCoordinator authSessionCoordinator, -                @NonNull Callback callback) { -            mContext = context; -            mHandler = handler; -            mTag = tag; -            mScheduler = scheduler; -            mSensorId = sensorId; -            mUserId = userId; -            mLockoutCache = lockoutTracker; -            mLockoutResetDispatcher = lockoutResetDispatcher; -            mAuthSessionCoordinator = authSessionCoordinator; -            mCallback = callback; -        } - -        @Override -        public int getInterfaceVersion() { -            return this.VERSION; -        } - -        @Override -        public String getInterfaceHash() { -            return this.HASH; -        } - -        @Override -        public void onChallengeGenerated(long challenge) { -            mHandler.post(() -> { -                final BaseClientMonitor client = mScheduler.getCurrentClient(); -                if (!(client instanceof FingerprintGenerateChallengeClient)) { -                    Slog.e(mTag, "onChallengeGenerated for wrong client: " -                            + Utils.getClientName(client)); -                    return; -                } - -                final FingerprintGenerateChallengeClient generateChallengeClient = -                        (FingerprintGenerateChallengeClient) client; -                generateChallengeClient.onChallengeGenerated(mSensorId, mUserId, challenge); -            }); -        } - -        @Override -        public void onChallengeRevoked(long challenge) { -            mHandler.post(() -> { -                final BaseClientMonitor client = mScheduler.getCurrentClient(); -                if (!(client instanceof FingerprintRevokeChallengeClient)) { -                    Slog.e(mTag, "onChallengeRevoked for wrong client: " -                            + Utils.getClientName(client)); -                    return; -                } - -                final FingerprintRevokeChallengeClient revokeChallengeClient = -                        (FingerprintRevokeChallengeClient) client; -                revokeChallengeClient.onChallengeRevoked(mSensorId, mUserId, challenge); -            }); -        } - -        @Override -        public void onAcquired(byte info, int vendorCode) { -            mHandler.post(() -> { -                final BaseClientMonitor client = mScheduler.getCurrentClient(); -                if (!(client instanceof AcquisitionClient)) { -                    Slog.e(mTag, "onAcquired for non-acquisition client: " -                            + Utils.getClientName(client)); -                    return; -                } - -                final AcquisitionClient<?> acquisitionClient = (AcquisitionClient<?>) client; -                acquisitionClient.onAcquired(AidlConversionUtils.toFrameworkAcquiredInfo(info), -                        vendorCode); -            }); -        } - -        @Override -        public void onError(byte error, int vendorCode) { -            mHandler.post(() -> { -                final BaseClientMonitor client = mScheduler.getCurrentClient(); -                Slog.d(mTag, "onError" -                        + ", client: " + Utils.getClientName(client) -                        + ", error: " + error -                        + ", vendorCode: " + vendorCode); -                if (!(client instanceof ErrorConsumer)) { -                    Slog.e(mTag, "onError for non-error consumer: " -                            + Utils.getClientName(client)); -                    return; -                } - -                final ErrorConsumer errorConsumer = (ErrorConsumer) client; -                errorConsumer.onError(AidlConversionUtils.toFrameworkError(error), vendorCode); - -                if (error == Error.HW_UNAVAILABLE) { -                    mCallback.onHardwareUnavailable(); -                } -            }); -        } - -        @Override -        public void onEnrollmentProgress(int enrollmentId, int remaining) { -            mHandler.post(() -> { -                final BaseClientMonitor client = mScheduler.getCurrentClient(); -                if (!(client instanceof FingerprintEnrollClient)) { -                    Slog.e(mTag, "onEnrollmentProgress for non-enroll client: " -                            + Utils.getClientName(client)); -                    return; -                } - -                final int currentUserId = client.getTargetUserId(); -                final CharSequence name = FingerprintUtils.getInstance(mSensorId) -                        .getUniqueName(mContext, currentUserId); -                final Fingerprint fingerprint = new Fingerprint(name, enrollmentId, mSensorId); - -                final FingerprintEnrollClient enrollClient = (FingerprintEnrollClient) client; -                enrollClient.onEnrollResult(fingerprint, remaining); -            }); -        } - -        @Override -        public void onAuthenticationSucceeded(int enrollmentId, HardwareAuthToken hat) { -            mHandler.post(() -> { -                final BaseClientMonitor client = mScheduler.getCurrentClient(); -                if (!(client instanceof AuthenticationConsumer)) { -                    Slog.e(mTag, "onAuthenticationSucceeded for non-authentication consumer: " -                            + Utils.getClientName(client)); -                    return; -                } - -                final AuthenticationConsumer authenticationConsumer = -                        (AuthenticationConsumer) client; -                final Fingerprint fp = new Fingerprint("", enrollmentId, mSensorId); -                final byte[] byteArray = HardwareAuthTokenUtils.toByteArray(hat); -                final ArrayList<Byte> byteList = new ArrayList<>(); -                for (byte b : byteArray) { -                    byteList.add(b); -                } - -                authenticationConsumer.onAuthenticated(fp, true /* authenticated */, byteList); -            }); -        } - -        @Override -        public void onAuthenticationFailed() { -            mHandler.post(() -> { -                final BaseClientMonitor client = mScheduler.getCurrentClient(); -                if (!(client instanceof AuthenticationConsumer)) { -                    Slog.e(mTag, "onAuthenticationFailed for non-authentication consumer: " -                            + Utils.getClientName(client)); -                    return; -                } - -                final AuthenticationConsumer authenticationConsumer = -                        (AuthenticationConsumer) client; -                final Fingerprint fp = new Fingerprint("", 0 /* enrollmentId */, mSensorId); -                authenticationConsumer -                        .onAuthenticated(fp, false /* authenticated */, null /* hat */); -            }); -        } - -        @Override -        public void onLockoutTimed(long durationMillis) { -            mHandler.post(() -> { -                final BaseClientMonitor client = mScheduler.getCurrentClient(); -                if (!(client instanceof LockoutConsumer)) { -                    Slog.e(mTag, "onLockoutTimed for non-lockout consumer: " -                            + Utils.getClientName(client)); -                    return; -                } - -                final LockoutConsumer lockoutConsumer = (LockoutConsumer) client; -                lockoutConsumer.onLockoutTimed(durationMillis); -            }); -        } - -        @Override -        public void onLockoutPermanent() { -            mHandler.post(() -> { -                final BaseClientMonitor client = mScheduler.getCurrentClient(); -                if (!(client instanceof LockoutConsumer)) { -                    Slog.e(mTag, "onLockoutPermanent for non-lockout consumer: " -                            + Utils.getClientName(client)); -                    return; -                } - -                final LockoutConsumer lockoutConsumer = (LockoutConsumer) client; -                lockoutConsumer.onLockoutPermanent(); -            }); -        } - -        @Override -        public void onLockoutCleared() { -            mHandler.post(() -> { -                final BaseClientMonitor client = mScheduler.getCurrentClient(); -                if (!(client instanceof FingerprintResetLockoutClient)) { -                    Slog.d(mTag, "onLockoutCleared outside of resetLockout by HAL"); -                    // Given that onLockoutCleared() can happen at any time, and is not necessarily -                    // coming from a specific client, set this to -1 to indicate it wasn't for a -                    // specific request. -                    FingerprintResetLockoutClient.resetLocalLockoutStateToNone(mSensorId, mUserId, -                            mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator, -                            Utils.getCurrentStrength(mSensorId), -1 /* requestId */); -                } else { -                    Slog.d(mTag, "onLockoutCleared after resetLockout"); -                    final FingerprintResetLockoutClient resetLockoutClient = -                            (FingerprintResetLockoutClient) client; -                    resetLockoutClient.onLockoutCleared(); -                } -            }); -        } - -        @Override -        public void onInteractionDetected() { -            mHandler.post(() -> { -                final BaseClientMonitor client = mScheduler.getCurrentClient(); -                if (!(client instanceof FingerprintDetectClient)) { -                    Slog.e(mTag, "onInteractionDetected for non-detect client: " -                            + Utils.getClientName(client)); -                    return; -                } - -                final FingerprintDetectClient fingerprintDetectClient = -                        (FingerprintDetectClient) client; -                fingerprintDetectClient.onInteractionDetected(); -            }); -        } - -        @Override -        public void onEnrollmentsEnumerated(int[] enrollmentIds) { -            mHandler.post(() -> { -                final BaseClientMonitor client = mScheduler.getCurrentClient(); -                if (!(client instanceof EnumerateConsumer)) { -                    Slog.e(mTag, "onEnrollmentsEnumerated for non-enumerate consumer: " -                            + Utils.getClientName(client)); -                    return; -                } - -                final EnumerateConsumer enumerateConsumer = -                        (EnumerateConsumer) client; -                if (enrollmentIds.length > 0) { -                    for (int i = 0; i < enrollmentIds.length; i++) { -                        final Fingerprint fp = new Fingerprint("", enrollmentIds[i], mSensorId); -                        enumerateConsumer.onEnumerationResult(fp, enrollmentIds.length - i - 1); -                    } -                } else { -                    enumerateConsumer.onEnumerationResult(null /* identifier */, 0); -                } -            }); -        } - -        @Override -        public void onEnrollmentsRemoved(int[] enrollmentIds) { -            mHandler.post(() -> { -                final BaseClientMonitor client = mScheduler.getCurrentClient(); -                if (!(client instanceof RemovalConsumer)) { -                    Slog.e(mTag, "onRemoved for non-removal consumer: " -                            + Utils.getClientName(client)); -                    return; -                } - -                final RemovalConsumer removalConsumer = (RemovalConsumer) client; -                if (enrollmentIds.length > 0) { -                    for (int i  = 0; i < enrollmentIds.length; i++) { -                        final Fingerprint fp = new Fingerprint("", enrollmentIds[i], mSensorId); -                        removalConsumer.onRemoved(fp, enrollmentIds.length - i - 1); -                    } -                } else { -                    removalConsumer.onRemoved(null, 0); -                } -            }); -        } - -        @Override -        public void onAuthenticatorIdRetrieved(long authenticatorId) { -            mHandler.post(() -> { -                final BaseClientMonitor client = mScheduler.getCurrentClient(); -                if (!(client instanceof FingerprintGetAuthenticatorIdClient)) { -                    Slog.e(mTag, "onAuthenticatorIdRetrieved for wrong consumer: " -                            + Utils.getClientName(client)); -                    return; -                } - -                final FingerprintGetAuthenticatorIdClient getAuthenticatorIdClient = -                        (FingerprintGetAuthenticatorIdClient) client; -                getAuthenticatorIdClient.onAuthenticatorIdRetrieved(authenticatorId); -            }); -        } - -        @Override -        public void onAuthenticatorIdInvalidated(long newAuthenticatorId) { -            mHandler.post(() -> { -                final BaseClientMonitor client = mScheduler.getCurrentClient(); -                if (!(client instanceof FingerprintInvalidationClient)) { -                    Slog.e(mTag, "onAuthenticatorIdInvalidated for wrong consumer: " -                            + Utils.getClientName(client)); -                    return; -                } - -                final FingerprintInvalidationClient invalidationClient = -                        (FingerprintInvalidationClient) client; -                invalidationClient.onAuthenticatorIdInvalidated(newAuthenticatorId); -            }); -        } - -        @Override -        public void onSessionClosed() { -            mHandler.post(mScheduler::onUserStopped); -        } -    } -      Sensor(@NonNull String tag, @NonNull FingerprintProvider provider, @NonNull Context context,              @NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties,              @NonNull LockoutResetDispatcher lockoutResetDispatcher, @@ -466,9 +111,9 @@ public class Sensor {                      public StartUserClient<?, ?> getStartUserClient(int newUserId) {                          final int sensorId = mSensorProperties.sensorId; -                        final HalSessionCallback resultController = new HalSessionCallback(mContext, -                                mHandler, mTag, mScheduler, sensorId, newUserId, mLockoutCache, -                                lockoutResetDispatcher, +                        final AidlResponseHandler resultController = new AidlResponseHandler( +                                mContext, mScheduler, sensorId, newUserId, +                                mLockoutCache, lockoutResetDispatcher,                                  biometricContext.getAuthSessionCoordinator(), () -> {                              Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");                              mCurrentSession = null; diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapter.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapter.java new file mode 100644 index 000000000000..b48d232e65af --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapter.java @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.fingerprint.hidl; + +import android.annotation.NonNull; +import android.hardware.biometrics.common.ICancellationSignal; +import android.hardware.biometrics.common.OperationContext; +import android.hardware.biometrics.fingerprint.ISession; +import android.hardware.biometrics.fingerprint.PointerContext; +import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; +import android.hardware.keymaster.HardwareAuthToken; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.biometrics.HardwareAuthTokenUtils; +import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper; +import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler; + +import java.util.function.Supplier; + +/** + * Adapter to convert AIDL-specific interface {@link ISession} methods to HIDL implementation. + */ +public class AidlToHidlAdapter implements ISession { +    private final String TAG = "AidlToHidlAdapter"; +    @VisibleForTesting +    static final int ENROLL_TIMEOUT_SEC = 60; +    @NonNull +    private final Supplier<IBiometricsFingerprint> mSession; +    private final int mUserId; +    private HidlToAidlCallbackConverter mHidlToAidlCallbackConverter; + +    public AidlToHidlAdapter(Supplier<IBiometricsFingerprint> session, int userId, +            AidlResponseHandler aidlResponseHandler) { +        mSession = session; +        mUserId = userId; +        setCallback(aidlResponseHandler); +    } + +    private void setCallback(AidlResponseHandler aidlResponseHandler) { +        mHidlToAidlCallbackConverter = new HidlToAidlCallbackConverter(aidlResponseHandler); +        try { +            mSession.get().setNotify(mHidlToAidlCallbackConverter); +        } catch (RemoteException e) { +            Slog.d(TAG, "Failed to set callback"); +        } +    } + +    @Override +    public IBinder asBinder() { +        return null; +    } + +    @Override +    public void generateChallenge() throws RemoteException { +        long challenge = mSession.get().preEnroll(); +        mHidlToAidlCallbackConverter.onChallengeGenerated(challenge); +    } + +    @Override +    public void revokeChallenge(long challenge) throws RemoteException { +        mSession.get().postEnroll(); +        mHidlToAidlCallbackConverter.onChallengeRevoked(0L); +    } + +    @Override +    public ICancellationSignal enroll(HardwareAuthToken hat) throws RemoteException { +        mSession.get().enroll(HardwareAuthTokenUtils.toByteArray(hat), mUserId, +                ENROLL_TIMEOUT_SEC); +        return new Cancellation(); +    } + +    @Override +    public ICancellationSignal authenticate(long operationId) throws RemoteException { +        mSession.get().authenticate(operationId, mUserId); +        return new Cancellation(); +    } + +    @Override +    public ICancellationSignal detectInteraction() throws RemoteException { +        mSession.get().authenticate(0, mUserId); +        return new Cancellation(); +    } + +    @Override +    public void enumerateEnrollments() throws RemoteException { +        mSession.get().enumerate(); +    } + +    @Override +    public void removeEnrollments(int[] enrollmentIds) throws RemoteException { +        if (enrollmentIds.length > 1) { +            mSession.get().remove(mUserId, 0); +        } else { +            mSession.get().remove(mUserId, enrollmentIds[0]); +        } +    } + +    @Override +    public void onPointerDown(int pointerId, int x, int y, float minor, float major) +            throws RemoteException { +        UdfpsHelper.onFingerDown(mSession.get(), x, y, minor, major); +    } + +    @Override +    public void onPointerUp(int pointerId) throws RemoteException { +        UdfpsHelper.onFingerUp(mSession.get()); +    } + +    @Override +    public void getAuthenticatorId() throws RemoteException { +        //Unsupported in HIDL +    } + +    @Override +    public void invalidateAuthenticatorId() throws RemoteException { +        //Unsupported in HIDL +    } + +    @Override +    public void resetLockout(HardwareAuthToken hat) throws RemoteException { +        mHidlToAidlCallbackConverter.onResetLockout(); +    } + +    @Override +    public void close() throws RemoteException { +        //Unsupported in HIDL +    } + +    @Override +    public void onUiReady() throws RemoteException { +        //Unsupported in HIDL +    } + +    @Override +    public ICancellationSignal authenticateWithContext(long operationId, OperationContext context) +            throws RemoteException { +        //Unsupported in HIDL +        return null; +    } + +    @Override +    public ICancellationSignal enrollWithContext(HardwareAuthToken hat, OperationContext context) +            throws RemoteException { +        //Unsupported in HIDL +        return null; +    } + +    @Override +    public ICancellationSignal detectInteractionWithContext(OperationContext context) +            throws RemoteException { +        //Unsupported in HIDL +        return null; +    } + +    @Override +    public void onPointerDownWithContext(PointerContext context) throws RemoteException { +        //Unsupported in HIDL +    } + +    @Override +    public void onPointerUpWithContext(PointerContext context) throws RemoteException { +        //Unsupported in HIDL +    } + +    @Override +    public void onContextChanged(OperationContext context) throws RemoteException { +        //Unsupported in HIDL +    } + +    @Override +    public void onPointerCancelWithContext(PointerContext context) throws RemoteException { +        //Unsupported in HIDL +    } + +    @Override +    public void setIgnoreDisplayTouches(boolean shouldIgnore) throws RemoteException { +        //Unsupported in HIDL +    } + +    @Override +    public int getInterfaceVersion() throws RemoteException { +        //Unsupported in HIDL +        return 0; +    } + +    @Override +    public String getInterfaceHash() throws RemoteException { +        //Unsupported in HIDL +        return null; +    } + +    private class Cancellation extends ICancellationSignal.Stub { + +        Cancellation() {} +        @Override +        public void cancel() throws RemoteException { +            try { +                mSession.get().cancel(); +            } catch (RemoteException e) { +                Slog.e(TAG, "Remote exception when requesting cancel", e); +            } +        } + +        @Override +        public int getInterfaceVersion() throws RemoteException { +            return 0; +        } + +        @Override +        public String getInterfaceHash() throws RemoteException { +            return null; +        } +    } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java index a655f3601a07..8bfa560b8031 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java @@ -54,6 +54,7 @@ import com.android.internal.annotations.VisibleForTesting;  import com.android.internal.util.FrameworkStatsLog;  import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver;  import com.android.server.biometrics.AuthenticationStatsCollector; +import com.android.server.biometrics.Flags;  import com.android.server.biometrics.SensorServiceStateProto;  import com.android.server.biometrics.SensorStateProto;  import com.android.server.biometrics.UserStateProto; @@ -64,6 +65,7 @@ import com.android.server.biometrics.fingerprint.PerformanceStatsProto;  import com.android.server.biometrics.log.BiometricContext;  import com.android.server.biometrics.log.BiometricLogger;  import com.android.server.biometrics.sensors.AcquisitionClient; +import com.android.server.biometrics.sensors.AuthSessionCoordinator;  import com.android.server.biometrics.sensors.AuthenticationClient;  import com.android.server.biometrics.sensors.AuthenticationConsumer;  import com.android.server.biometrics.sensors.BaseClientMonitor; @@ -74,6 +76,7 @@ import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;  import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;  import com.android.server.biometrics.sensors.EnumerateConsumer;  import com.android.server.biometrics.sensors.ErrorConsumer; +import com.android.server.biometrics.sensors.LockoutCache;  import com.android.server.biometrics.sensors.LockoutResetDispatcher;  import com.android.server.biometrics.sensors.LockoutTracker;  import com.android.server.biometrics.sensors.PerformanceTracker; @@ -82,6 +85,8 @@ import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;  import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;  import com.android.server.biometrics.sensors.fingerprint.ServiceProvider;  import com.android.server.biometrics.sensors.fingerprint.Udfps; +import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler; +import com.android.server.biometrics.sensors.fingerprint.aidl.AidlSession;  import org.json.JSONArray;  import org.json.JSONException; @@ -132,6 +137,7 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider      private final boolean mIsUdfps;      private final int mSensorId;      private final boolean mIsPowerbuttonFps; +    private AidlSession mSession;      private final class BiometricTaskStackListener extends TaskStackListener {          @Override @@ -413,6 +419,20 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider          });      } +    synchronized AidlSession getSession() { +        if (mDaemon != null && mSession != null) { +            return mSession; +        } else { +            return mSession = new AidlSession(this::getDaemon, +                    mCurrentUserId, new AidlResponseHandler(mContext, +                    mScheduler, mSensorId, mCurrentUserId, new LockoutCache(), +                    mLockoutResetDispatcher, new AuthSessionCoordinator(), () -> { +                        mDaemon = null; +                        mCurrentUserId = UserHandle.USER_NULL; +                    })); +        } +    } +      @VisibleForTesting      synchronized IBiometricsFingerprint getDaemon() {          if (mTestHalEnabled) { @@ -518,6 +538,10 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider              public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,                      boolean success) {                  if (success) { +                    if (mCurrentUserId != targetUserId) { +                        // Create new session with updated user ID +                        mSession = null; +                    }                      mCurrentUserId = targetUserId;                  } else {                      Slog.w(TAG, "Failed to change user, still: " + mCurrentUserId); @@ -554,47 +578,116 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider          // Fingerprint2.1 keeps track of lockout in the framework. Let's just do it on the handler          // thread.          mHandler.post(() -> { -            final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient(mContext, -                    userId, mContext.getOpPackageName(), sensorId, -                    createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, -                            BiometricsProtoEnums.CLIENT_UNKNOWN, -                            mAuthenticationStatsCollector), -                    mBiometricContext, mLockoutTracker); -            mScheduler.scheduleClientMonitor(client); +            if (Flags.deHidl()) { +                scheduleResetLockoutAidl(sensorId, userId, hardwareAuthToken); +            } else { +                scheduleResetLockoutHidl(sensorId, userId); +            }          });      } +    private void scheduleResetLockoutAidl(int sensorId, int userId, +            @Nullable byte[] hardwareAuthToken) { +        final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintResetLockoutClient client = +                new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintResetLockoutClient( +                        mContext, this::getSession, userId, mContext.getOpPackageName(), +                        sensorId, +                        createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, +                                BiometricsProtoEnums.CLIENT_UNKNOWN, +                                mAuthenticationStatsCollector), +                        mBiometricContext, hardwareAuthToken, mLockoutTracker, +                        mLockoutResetDispatcher, +                        Utils.getCurrentStrength(sensorId)); +        mScheduler.scheduleClientMonitor(client); +    } + +    private void scheduleResetLockoutHidl(int sensorId, int userId) { +        final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient(mContext, +                userId, mContext.getOpPackageName(), sensorId, +                createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, +                        BiometricsProtoEnums.CLIENT_UNKNOWN, +                        mAuthenticationStatsCollector), +                mBiometricContext, mLockoutTracker); +        mScheduler.scheduleClientMonitor(client); +    } +      @Override      public void scheduleGenerateChallenge(int sensorId, int userId, @NonNull IBinder token,              @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName) {          mHandler.post(() -> { -            final FingerprintGenerateChallengeClient client = -                    new FingerprintGenerateChallengeClient(mContext, mLazyDaemon, token, -                            new ClientMonitorCallbackConverter(receiver), userId, opPackageName, -                            mSensorProperties.sensorId, -                            createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, -                                    BiometricsProtoEnums.CLIENT_UNKNOWN, -                                    mAuthenticationStatsCollector), -                            mBiometricContext); -            mScheduler.scheduleClientMonitor(client); +            if (Flags.deHidl()) { +                scheduleGenerateChallengeAidl(userId, token, receiver, opPackageName); +            } else { +                scheduleGenerateChallengeHidl(userId, token, receiver, opPackageName); +            }          });      } +    private void scheduleGenerateChallengeAidl(int userId, @NonNull IBinder token, +            @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName) { +        final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintGenerateChallengeClient client = +                new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintGenerateChallengeClient( +                        mContext, this::getSession, token, +                        new ClientMonitorCallbackConverter(receiver), userId, opPackageName, +                        mSensorProperties.sensorId, +                        createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, +                                BiometricsProtoEnums.CLIENT_UNKNOWN, +                                mAuthenticationStatsCollector), +                        mBiometricContext); +        mScheduler.scheduleClientMonitor(client); +    } + +    private void scheduleGenerateChallengeHidl(int userId, @NonNull IBinder token, +            @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName) { +        final FingerprintGenerateChallengeClient client = +                new FingerprintGenerateChallengeClient(mContext, mLazyDaemon, token, +                        new ClientMonitorCallbackConverter(receiver), userId, opPackageName, +                        mSensorProperties.sensorId, +                        createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, +                                BiometricsProtoEnums.CLIENT_UNKNOWN, +                                mAuthenticationStatsCollector), +                        mBiometricContext); +        mScheduler.scheduleClientMonitor(client); +    } +      @Override      public void scheduleRevokeChallenge(int sensorId, int userId, @NonNull IBinder token,              @NonNull String opPackageName, long challenge) {          mHandler.post(() -> { -            final FingerprintRevokeChallengeClient client = new FingerprintRevokeChallengeClient( -                    mContext, mLazyDaemon, token, userId, opPackageName, -                    mSensorProperties.sensorId, -                    createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, -                            BiometricsProtoEnums.CLIENT_UNKNOWN, -                            mAuthenticationStatsCollector), -                    mBiometricContext); -            mScheduler.scheduleClientMonitor(client); +            if (Flags.deHidl()) { +                scheduleRevokeChallengeAidl(userId, token, opPackageName); +            } else { +                scheduleRevokeChallengeHidl(userId, token, opPackageName); +            }          });      } +    private void scheduleRevokeChallengeAidl(int userId, @NonNull IBinder token, +            @NonNull String opPackageName) { +        final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintRevokeChallengeClient client = +                new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintRevokeChallengeClient( +                        mContext, this::getSession, +                        token, userId, opPackageName, +                        mSensorProperties.sensorId, +                        createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, +                                BiometricsProtoEnums.CLIENT_UNKNOWN, +                                mAuthenticationStatsCollector), +                        mBiometricContext, 0L); +        mScheduler.scheduleClientMonitor(client); +    } + +    private void scheduleRevokeChallengeHidl(int userId, @NonNull IBinder token, +            @NonNull String opPackageName) { +        final FingerprintRevokeChallengeClient client = new FingerprintRevokeChallengeClient( +                mContext, mLazyDaemon, token, userId, opPackageName, +                mSensorProperties.sensorId, +                createLogger(BiometricsProtoEnums.ACTION_UNKNOWN, +                        BiometricsProtoEnums.CLIENT_UNKNOWN, +                        mAuthenticationStatsCollector), +                mBiometricContext); +        mScheduler.scheduleClientMonitor(client); +    } +      @Override      public long scheduleEnroll(int sensorId, @NonNull IBinder token,              @NonNull byte[] hardwareAuthToken, int userId, @@ -604,38 +697,96 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider          mHandler.post(() -> {              scheduleUpdateActiveUserWithoutHandler(userId); -            final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext, -                    mLazyDaemon, token, id, new ClientMonitorCallbackConverter(receiver), -                    userId, hardwareAuthToken, opPackageName, -                    FingerprintUtils.getLegacyInstance(mSensorId), ENROLL_TIMEOUT_SEC, -                    mSensorProperties.sensorId, -                    createLogger(BiometricsProtoEnums.ACTION_ENROLL, -                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), -                    mBiometricContext, mUdfpsOverlayController, mSidefpsController, enrollReason); -            mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { -                @Override -                public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { -                    mBiometricStateCallback.onClientStarted(clientMonitor); -                } +            if (Flags.deHidl()) { +                scheduleEnrollAidl(token, hardwareAuthToken, userId, receiver, +                        opPackageName, enrollReason, id); +            } else { +                scheduleEnrollHidl(token, hardwareAuthToken, userId, receiver, +                        opPackageName, enrollReason, id); +            } +        }); +        return id; +    } -                @Override -                public void onBiometricAction(int action) { -                    mBiometricStateCallback.onBiometricAction(action); +    private void scheduleEnrollHidl(@NonNull IBinder token, +            @NonNull byte[] hardwareAuthToken, int userId, +            @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName, +            @FingerprintManager.EnrollReason int enrollReason, long id) { +        final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext, +                mLazyDaemon, token, id, new ClientMonitorCallbackConverter(receiver), +                userId, hardwareAuthToken, opPackageName, +                FingerprintUtils.getLegacyInstance(mSensorId), ENROLL_TIMEOUT_SEC, +                mSensorProperties.sensorId, +                createLogger(BiometricsProtoEnums.ACTION_ENROLL, +                        BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), +                mBiometricContext, mUdfpsOverlayController, mSidefpsController, enrollReason); +        mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { +            @Override +            public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { +                mBiometricStateCallback.onClientStarted(clientMonitor); +            } + +            @Override +            public void onBiometricAction(int action) { +                mBiometricStateCallback.onBiometricAction(action); +            } + +            @Override +            public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, +                    boolean success) { +                mBiometricStateCallback.onClientFinished(clientMonitor, success); +                if (success) { +                    // Update authenticatorIds +                    scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId(), +                            true /* force */);                  } +            } +        }); +    } -                @Override -                public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, -                        boolean success) { -                    mBiometricStateCallback.onClientFinished(clientMonitor, success); -                    if (success) { -                        // Update authenticatorIds -                        scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId(), -                                true /* force */); -                    } +    private void scheduleEnrollAidl(@NonNull IBinder token, +            @NonNull byte[] hardwareAuthToken, int userId, +            @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName, +            @FingerprintManager.EnrollReason int enrollReason, long id) { +        final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintEnrollClient +                client = +                new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintEnrollClient( +                        mContext, +                        this::getSession, token, id, +                        new ClientMonitorCallbackConverter(receiver), +                        userId, hardwareAuthToken, opPackageName, +                        FingerprintUtils.getLegacyInstance(mSensorId), +                        mSensorProperties.sensorId, +                        createLogger(BiometricsProtoEnums.ACTION_ENROLL, +                                BiometricsProtoEnums.CLIENT_UNKNOWN, +                                mAuthenticationStatsCollector), +                        mBiometricContext, null /* sensorProps */, +                        mUdfpsOverlayController, mSidefpsController, +                        mContext.getResources().getInteger( +                                com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser), +                        enrollReason); +        mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() { +            @Override +            public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { +                mBiometricStateCallback.onClientStarted(clientMonitor); +            } + +            @Override +            public void onBiometricAction(int action) { +                mBiometricStateCallback.onBiometricAction(action); +            } + +            @Override +            public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, +                    boolean success) { +                mBiometricStateCallback.onClientFinished(clientMonitor, success); +                if (success) { +                    // Update authenticatorIds +                    scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId(), +                            true /* force */);                  } -            }); +            }          }); -        return id;      }      @Override @@ -653,17 +804,46 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider              scheduleUpdateActiveUserWithoutHandler(options.getUserId());              final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId); -            final FingerprintDetectClient client = new FingerprintDetectClient(mContext, -                    mLazyDaemon, token, id, listener, options, -                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient, -                            mAuthenticationStatsCollector), -                    mBiometricContext, mUdfpsOverlayController, isStrongBiometric); -            mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); + +            if (Flags.deHidl()) { +                scheduleFingerDetectAidl(token, listener, options, statsClient, id, +                        isStrongBiometric); +            } else { +                scheduleFingerDetectHidl(token, listener, options, statsClient, id, +                        isStrongBiometric); +            }          });          return id;      } +    private void scheduleFingerDetectHidl(@NonNull IBinder token, +            @NonNull ClientMonitorCallbackConverter listener, +            @NonNull FingerprintAuthenticateOptions options, +            int statsClient, long id, boolean isStrongBiometric) { +        final FingerprintDetectClient client = new FingerprintDetectClient(mContext, +                mLazyDaemon, token, id, listener, options, +                createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient, +                        mAuthenticationStatsCollector), +                mBiometricContext, mUdfpsOverlayController, isStrongBiometric); +        mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); +    } + +    private void scheduleFingerDetectAidl(@NonNull IBinder token, +            @NonNull ClientMonitorCallbackConverter listener, +            @NonNull FingerprintAuthenticateOptions options, +            int statsClient, long id, boolean isStrongBiometric) { +        final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintDetectClient +                client = +                new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintDetectClient( +                        mContext, +                        this::getSession, token, id, listener, options, +                        createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient, +                                mAuthenticationStatsCollector), +                        mBiometricContext, mUdfpsOverlayController, isStrongBiometric); +        mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); +    } +      @Override      public void scheduleAuthenticate(@NonNull IBinder token, long operationId,              int cookie, @NonNull ClientMonitorCallbackConverter listener, @@ -674,20 +854,55 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider              scheduleUpdateActiveUserWithoutHandler(options.getUserId());              final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId); -            final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient( -                    mContext, mLazyDaemon, token, requestId, listener, operationId, -                    restricted, options, cookie, false /* requireConfirmation */, -                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient, -                            mAuthenticationStatsCollector), -                    mBiometricContext, isStrongBiometric, -                    mTaskStackListener, mLockoutTracker, -                    mUdfpsOverlayController, mSidefpsController, -                    allowBackgroundAuthentication, mSensorProperties, -                    Utils.getCurrentStrength(mSensorId)); -            mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); + +            if (Flags.deHidl()) { +                scheduleAuthenticateAidl(token, operationId, cookie, listener, options, requestId, +                        restricted, statsClient, allowBackgroundAuthentication, isStrongBiometric); +            } else { +                scheduleAuthenticateHidl(token, operationId, cookie, listener, options, requestId, +                        restricted, statsClient, allowBackgroundAuthentication, isStrongBiometric); +            }          });      } +    private void scheduleAuthenticateAidl(@NonNull IBinder token, long operationId, +            int cookie, @NonNull ClientMonitorCallbackConverter listener, +            @NonNull FingerprintAuthenticateOptions options, +            long requestId, boolean restricted, int statsClient, +            boolean allowBackgroundAuthentication, boolean isStrongBiometric) { +        final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintAuthenticationClient +                client = +                new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintAuthenticationClient( +                        mContext, this::getSession, token, requestId, listener, operationId, +                        restricted, options, cookie, false /* requireConfirmation */, +                        createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient, +                                mAuthenticationStatsCollector), +                        mBiometricContext, isStrongBiometric, +                        mTaskStackListener, +                        mUdfpsOverlayController, mSidefpsController, +                        allowBackgroundAuthentication, mSensorProperties, mHandler, +                        Utils.getCurrentStrength(mSensorId), null /* clock */, mLockoutTracker); +        mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); +    } + +    private void scheduleAuthenticateHidl(@NonNull IBinder token, long operationId, +            int cookie, @NonNull ClientMonitorCallbackConverter listener, +            @NonNull FingerprintAuthenticateOptions options, +            long requestId, boolean restricted, int statsClient, +            boolean allowBackgroundAuthentication, boolean isStrongBiometric) { +        final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient( +                mContext, mLazyDaemon, token, requestId, listener, operationId, +                restricted, options, cookie, false /* requireConfirmation */, +                createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient, +                        mAuthenticationStatsCollector), +                mBiometricContext, isStrongBiometric, +                mTaskStackListener, mLockoutTracker, +                mUdfpsOverlayController, mSidefpsController, +                allowBackgroundAuthentication, mSensorProperties, +                Utils.getCurrentStrength(mSensorId)); +        mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); +    } +      @Override      public long scheduleAuthenticate(@NonNull IBinder token, long operationId,              int cookie, @NonNull ClientMonitorCallbackConverter listener, @@ -719,17 +934,41 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider          mHandler.post(() -> {              scheduleUpdateActiveUserWithoutHandler(userId); -            final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext, -                    mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), fingerId, -                    userId, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId), -                    mSensorProperties.sensorId, -                    createLogger(BiometricsProtoEnums.ACTION_REMOVE, -                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), -                    mBiometricContext, mAuthenticatorIds); -            mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); +            if (Flags.deHidl()) { +                scheduleRemoveAidl(token, receiver, fingerId, userId, opPackageName); +            } else { +                scheduleRemoveHidl(token, receiver, fingerId, userId, opPackageName); +            }          });      } +    private void scheduleRemoveHidl(@NonNull IBinder token, +            @NonNull IFingerprintServiceReceiver receiver, int fingerId, int userId, +            @NonNull String opPackageName) { +        final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext, +                mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), fingerId, +                userId, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId), +                mSensorProperties.sensorId, +                createLogger(BiometricsProtoEnums.ACTION_REMOVE, +                        BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), +                mBiometricContext, mAuthenticatorIds); +        mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); +    } + +    private void scheduleRemoveAidl(@NonNull IBinder token, +            @NonNull IFingerprintServiceReceiver receiver, int fingerId, int userId, +            @NonNull String opPackageName) { +        final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintRemovalClient client = +                new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintRemovalClient( +                        mContext, this::getSession, token, +                        new ClientMonitorCallbackConverter(receiver), new int[]{fingerId}, userId, +                        opPackageName, FingerprintUtils.getLegacyInstance(mSensorId), mSensorId, +                        createLogger(BiometricsProtoEnums.ACTION_REMOVE, +                                BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), +                        mBiometricContext, mAuthenticatorIds); +        mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); +    } +      @Override      public void scheduleRemoveAll(int sensorId, @NonNull IBinder token,              @NonNull IFingerprintServiceReceiver receiver, int userId, @@ -738,15 +977,11 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider              scheduleUpdateActiveUserWithoutHandler(userId);              // For IBiometricsFingerprint@2.1, remove(0) means remove all enrollments -            final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext, -                    mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), -                    0 /* fingerprintId */, userId, opPackageName, -                    FingerprintUtils.getLegacyInstance(mSensorId), -                    mSensorProperties.sensorId, -                    createLogger(BiometricsProtoEnums.ACTION_REMOVE, -                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), -                    mBiometricContext, mAuthenticatorIds); -            mScheduler.scheduleClientMonitor(client, mBiometricStateCallback); +            if (Flags.deHidl()) { +                scheduleRemoveAidl(token, receiver, 0 /* fingerId */, userId, opPackageName); +            } else { +                scheduleRemoveHidl(token, receiver, 0 /* fingerId */, userId, opPackageName); +            }          });      } @@ -755,17 +990,41 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider          mHandler.post(() -> {              scheduleUpdateActiveUserWithoutHandler(userId); -            final FingerprintInternalCleanupClient client = new FingerprintInternalCleanupClient( -                    mContext, mLazyDaemon, userId, mContext.getOpPackageName(), -                    mSensorProperties.sensorId, -                    createLogger(BiometricsProtoEnums.ACTION_ENUMERATE, -                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), -                    mBiometricContext, -                    FingerprintUtils.getLegacyInstance(mSensorId), mAuthenticatorIds); -            mScheduler.scheduleClientMonitor(client, callback); +            if (Flags.deHidl()) { +                scheduleInternalCleanupAidl(userId, callback); +            } else { +                scheduleInternalCleanupHidl(userId, callback); +            }          });      } +    private void scheduleInternalCleanupHidl(int userId, +            @Nullable ClientMonitorCallback callback) { +        final FingerprintInternalCleanupClient client = new FingerprintInternalCleanupClient( +                mContext, mLazyDaemon, userId, mContext.getOpPackageName(), +                mSensorProperties.sensorId, +                createLogger(BiometricsProtoEnums.ACTION_ENUMERATE, +                        BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector), +                mBiometricContext, +                FingerprintUtils.getLegacyInstance(mSensorId), mAuthenticatorIds); +        mScheduler.scheduleClientMonitor(client, callback); +    } + +    private void scheduleInternalCleanupAidl(int userId, +            @Nullable ClientMonitorCallback callback) { +        final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintInternalCleanupClient +                client = +                new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintInternalCleanupClient( +                        mContext, this::getSession, userId, mContext.getOpPackageName(), +                        mSensorProperties.sensorId, +                        createLogger(BiometricsProtoEnums.ACTION_ENUMERATE, +                                BiometricsProtoEnums.CLIENT_UNKNOWN, +                                mAuthenticationStatsCollector), +                        mBiometricContext, +                        FingerprintUtils.getLegacyInstance(mSensorId), mAuthenticatorIds); +        mScheduler.scheduleClientMonitor(client, callback); +    } +      @Override      public void scheduleInternalCleanup(int sensorId, int userId,              @Nullable ClientMonitorCallback callback) { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlCallbackConverter.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlCallbackConverter.java new file mode 100644 index 000000000000..c3e5cbe7daf4 --- /dev/null +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlCallbackConverter.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.fingerprint.hidl; + +import android.annotation.NonNull; +import android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprintClientCallback; + +import com.android.server.biometrics.HardwareAuthTokenUtils; +import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler; + +import java.util.ArrayList; + +/** + * Convert HIDL-specific callback interface {@link IBiometricsFingerprintClientCallback} to AIDL + * response handler. + */ +public class HidlToAidlCallbackConverter extends IBiometricsFingerprintClientCallback.Stub { + +    final AidlResponseHandler mAidlResponseHandler; + +    public HidlToAidlCallbackConverter(@NonNull AidlResponseHandler aidlResponseHandler) { +        mAidlResponseHandler = aidlResponseHandler; +    } + +    @Override +    public void onEnrollResult(long deviceId, int fingerId, int groupId, int remaining) { +        mAidlResponseHandler.onEnrollmentProgress(fingerId, remaining); +    } + +    @Override +    public void onAcquired(long deviceId, int acquiredInfo, int vendorCode) { +        onAcquired_2_2(deviceId, acquiredInfo, vendorCode); +    } + +    @Override +    public void onAcquired_2_2(long deviceId, int acquiredInfo, int vendorCode) { +        mAidlResponseHandler.onAcquired(acquiredInfo, vendorCode); +    } + +    @Override +    public void onAuthenticated(long deviceId, int fingerId, int groupId, +            ArrayList<Byte> token) { +        if (fingerId != 0) { +            byte[] hardwareAuthToken = new byte[token.size()]; +            for (int i = 0; i < token.size(); i++) { +                hardwareAuthToken[i] = token.get(i); +            } +            mAidlResponseHandler.onAuthenticationSucceeded(fingerId, +                    HardwareAuthTokenUtils.toHardwareAuthToken(hardwareAuthToken)); +        } else { +            mAidlResponseHandler.onAuthenticationFailed(); +        } +    } + +    @Override +    public void onError(long deviceId, int error, int vendorCode) { +        mAidlResponseHandler.onError(error, vendorCode); +    } + +    @Override +    public void onRemoved(long deviceId, int fingerId, int groupId, int remaining) { +        mAidlResponseHandler.onEnrollmentsRemoved(new int[]{fingerId}); +    } + +    @Override +    public void onEnumerate(long deviceId, int fingerId, int groupId, int remaining) { +        mAidlResponseHandler.onEnrollmentsEnumerated(new int[]{fingerId}); +    } + +    void onChallengeGenerated(long challenge) { +        mAidlResponseHandler.onChallengeGenerated(challenge); +    } + +    void onChallengeRevoked(long challenge) { +        mAidlResponseHandler.onChallengeRevoked(challenge); +    } + +    void onResetLockout() { +        mAidlResponseHandler.onLockoutCleared(); +    } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java index 36d56c8a1544..0730c672acd9 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java @@ -89,7 +89,8 @@ public class LockoutFrameworkImpl implements LockoutTracker {      // Attempt counter should only be cleared when Keyguard goes away or when      // a biometric is successfully authenticated. Lockout should eventually be done below the HAL.      // See AuthenticationClient#shouldFrameworkHandleLockout(). -    void resetFailedAttemptsForUser(boolean clearAttemptCounter, int userId) { +    @Override +    public void resetFailedAttemptsForUser(boolean clearAttemptCounter, int userId) {          if (getLockoutModeForUser(userId) != LOCKOUT_NONE) {              Slog.v(TAG, "Reset biometric lockout for user: " + userId                      + ", clearAttemptCounter: " + clearAttemptCounter); @@ -104,7 +105,8 @@ public class LockoutFrameworkImpl implements LockoutTracker {          mLockoutResetCallback.onLockoutReset(userId);      } -    void addFailedAttemptForUser(int userId) { +    @Override +    public void addFailedAttemptForUser(int userId) {          mFailedAttempts.put(userId, mFailedAttempts.get(userId, 0) + 1);          mTimedLockoutCleared.put(userId, false); @@ -114,7 +116,8 @@ public class LockoutFrameworkImpl implements LockoutTracker {      }      @Override -    public @LockoutMode int getLockoutModeForUser(int userId) { +    @LockoutMode +    public int getLockoutModeForUser(int userId) {          final int failedAttempts = mFailedAttempts.get(userId, 0);          if (failedAttempts >= MAX_FAILED_ATTEMPTS_LOCKOUT_PERMANENT) {              return LOCKOUT_PERMANENT; @@ -126,6 +129,19 @@ public class LockoutFrameworkImpl implements LockoutTracker {          return LOCKOUT_NONE;      } +    /** +     * Clears lockout for Fingerprint HIDL HAL +     */ +    @Override +    public void setLockoutModeForUser(int userId, int mode) { +        mFailedAttempts.put(userId, 0); +        mTimedLockoutCleared.put(userId, true); +        // If we're asked to reset failed attempts externally (i.e. from Keyguard), +        // the alarm might still be pending; remove it. +        cancelLockoutResetForUser(userId); +        mLockoutResetCallback.onLockoutReset(userId); +    } +      private void cancelLockoutResetForUser(int userId) {          mAlarmManager.cancel(getLockoutResetIntentForUser(userId));      } diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java index 8736a53bb9f5..ac7d9c171247 100644 --- a/services/core/java/com/android/server/content/SyncManager.java +++ b/services/core/java/com/android/server/content/SyncManager.java @@ -221,9 +221,8 @@ public class SyncManager {      /** Flags used when connecting to a sync adapter service */      private static final Context.BindServiceFlags SYNC_ADAPTER_CONNECTION_FLAGS = -            Context.BindServiceFlags.of( -                    Context.BIND_FILTER_OUT_QUARANTINED_COMPONENTS | Context.BIND_AUTO_CREATE -                            | Context.BIND_NOT_FOREGROUND | Context.BIND_ALLOW_OOM_MANAGEMENT); +            Context.BindServiceFlags.of(Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND +                    | Context.BIND_ALLOW_OOM_MANAGEMENT);      /** Singleton instance. */      @GuardedBy("SyncManager.class") diff --git a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java index 472c1f58dc8a..1ac3a12fad21 100644 --- a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java +++ b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java @@ -135,7 +135,8 @@ class DeviceStateToLayoutMap {                              leadDisplayAddress,                              d.getBrightnessThrottlingMapId(),                              d.getRefreshRateZoneId(), -                            d.getRefreshRateThermalThrottlingMapId()); +                            d.getRefreshRateThermalThrottlingMapId(), +                            d.getPowerThrottlingMapId());                  }                  layout.postProcessLocked();              } diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java index 098cb87940b2..9f4f78794659 100644 --- a/services/core/java/com/android/server/display/DisplayDevice.java +++ b/services/core/java/com/android/server/display/DisplayDevice.java @@ -23,7 +23,7 @@ import android.annotation.Nullable;  import android.content.Context;  import android.graphics.Point;  import android.graphics.Rect; -import android.hardware.display.DisplayManagerInternal; +import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession;  import android.hardware.display.DisplayViewport;  import android.os.IBinder;  import android.util.Slog; @@ -201,20 +201,6 @@ abstract class DisplayDevice {       * @param state The new display state.       * @param brightnessState The new display brightnessState.       * @param sdrBrightnessState The new display brightnessState for SDR layers. -     * @return A runnable containing work to be deferred until after we have -     * exited the critical section, or null if none. -     */ -    public Runnable requestDisplayStateLocked(int state, float brightnessState, -            float sdrBrightnessState) { -        return requestDisplayStateLocked(state, brightnessState, sdrBrightnessState, null); -    } - -    /** -     * Sets the display state, if supported. -     * -     * @param state The new display state. -     * @param brightnessState The new display brightnessState. -     * @param sdrBrightnessState The new display brightnessState for SDR layers.       * @param displayOffloadSession {@link DisplayOffloadSession} associated with current device.       * @return A runnable containing work to be deferred until after we have exited the critical       *     section, or null if none. @@ -223,7 +209,7 @@ abstract class DisplayDevice {              int state,              float brightnessState,              float sdrBrightnessState, -            @Nullable DisplayManagerInternal.DisplayOffloadSession displayOffloadSession) { +            @Nullable DisplayOffloadSession displayOffloadSession) {          return null;      } diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 0689478ded1e..cb2302a60248 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -494,6 +494,7 @@ public final class DisplayManagerService extends SystemService {      // If we would like to keep a particular eye on a package, we can set the package name.      private final boolean mExtraDisplayEventLogging; +    private final String mExtraDisplayLoggingPackageName;      private final BroadcastReceiver mIdleModeReceiver = new BroadcastReceiver() {          @Override @@ -584,8 +585,8 @@ public final class DisplayManagerService extends SystemService {          mOverlayProperties = SurfaceControl.getOverlaySupport();          mSystemReady = false;          mConfigParameterProvider = new DeviceConfigParameterProvider(DeviceConfigInterface.REAL); -        final String name = DisplayProperties.debug_vri_package().orElse(null); -        mExtraDisplayEventLogging = !TextUtils.isEmpty(name); +        mExtraDisplayLoggingPackageName = DisplayProperties.debug_vri_package().orElse(null); +        mExtraDisplayEventLogging = !TextUtils.isEmpty(mExtraDisplayLoggingPackageName);      }      public void setupSchedulerPolicies() { @@ -757,7 +758,8 @@ public final class DisplayManagerService extends SystemService {          mContext.registerReceiver(mIdleModeReceiver, filter); -        mSmallAreaDetectionController = SmallAreaDetectionController.create(mContext); +        mSmallAreaDetectionController = (mFlags.isSmallAreaDetectionEnabled()) +                ? SmallAreaDetectionController.create(mContext) : null;      }      @VisibleForTesting @@ -2934,12 +2936,17 @@ public final class DisplayManagerService extends SystemService {          // Only send updates outside of DisplayManagerService for enabled displays          if (display.isEnabledLocked()) {              sendDisplayEventLocked(display, event); +        } else if (mExtraDisplayEventLogging) { +            Slog.i(TAG, "Not Sending Display Event; display is not enabled: " + display);          }      }      private void sendDisplayEventLocked(@NonNull LogicalDisplay display, @DisplayEvent int event) {          int displayId = display.getDisplayIdLocked();          Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_EVENT, displayId, event); +        if (mExtraDisplayEventLogging) { +            Slog.i(TAG, "Deliver Display Event on Handler: " + event); +        }          mHandler.sendMessage(msg);      } @@ -2967,7 +2974,7 @@ public final class DisplayManagerService extends SystemService {      // Check if the target app is in cached mode      private boolean isUidCached(int uid) { -        if (mActivityManagerInternal == null) { +        if (mActivityManagerInternal == null || uid < FIRST_APPLICATION_UID) {              return false;          }          int procState = mActivityManagerInternal.getUidProcessState(uid); @@ -3005,6 +3012,10 @@ public final class DisplayManagerService extends SystemService {                  // For cached apps, save the pending event until it becomes non-cached                  synchronized (mPendingCallbackSelfLocked) {                      PendingCallback pendingCallback = mPendingCallbackSelfLocked.get(uid); +                    if (extraLogging(callbackRecord.mPackageName)) { +                        Slog.i(TAG, +                                "Uid is cached: " + uid + ", pendingCallback: " + pendingCallback); +                    }                      if (pendingCallback == null) {                          mPendingCallbackSelfLocked.put(uid,                                  new PendingCallback(callbackRecord, displayId, event)); @@ -3019,6 +3030,10 @@ public final class DisplayManagerService extends SystemService {          mTempCallbacks.clear();      } +    private boolean extraLogging(String packageName) { +        return mExtraDisplayEventLogging && mExtraDisplayLoggingPackageName.equals(packageName); +    } +      // Runs on Handler thread.      // Delivers display group event notifications to callbacks.      private void deliverDisplayGroupEvent(int groupId, int event) { @@ -3462,6 +3477,7 @@ public final class DisplayManagerService extends SystemService {          public final int mUid;          private final IDisplayManagerCallback mCallback;          private @EventsMask AtomicLong mEventsMask; +        private final String mPackageName;          public boolean mWifiDisplayScanRequested; @@ -3471,6 +3487,9 @@ public final class DisplayManagerService extends SystemService {              mUid = uid;              mCallback = callback;              mEventsMask = new AtomicLong(eventsMask); + +            String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid); +            mPackageName = packageNames == null ? null : packageNames[0];          }          public void updateEventsMask(@EventsMask long eventsMask) { @@ -3479,7 +3498,8 @@ public final class DisplayManagerService extends SystemService {          @Override          public void binderDied() { -            if (DEBUG) { +            if (DEBUG || mExtraDisplayEventLogging && mExtraDisplayLoggingPackageName.equals( +                    mPackageName)) {                  Slog.d(TAG, "Display listener for pid " + mPid + " died.");              }              onCallbackDied(this); @@ -3490,6 +3510,11 @@ public final class DisplayManagerService extends SystemService {           */          public boolean notifyDisplayEventAsync(int displayId, @DisplayEvent int event) {              if (!shouldSendEvent(event)) { +                if (mExtraDisplayEventLogging && mExtraDisplayLoggingPackageName.equals( +                        mPackageName)) { +                    Slog.i(TAG, +                            "Not sending displayEvent: " + event + " due to mask:" + mEventsMask); +                }                  return true;              } diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java index 1652871963b9..7d9c0182a691 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController2.java +++ b/services/core/java/com/android/server/display/DisplayPowerController2.java @@ -506,7 +506,6 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal          mTag = "DisplayPowerController2[" + mDisplayId + "]";          mThermalBrightnessThrottlingDataId =                  logicalDisplay.getDisplayInfoLocked().thermalBrightnessThrottlingDataId; -          mDisplayDevice = mLogicalDisplay.getPrimaryDisplayDeviceLocked();          mUniqueDisplayId = logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId();          mDisplayStatsId = mUniqueDisplayId.hashCode(); @@ -566,8 +565,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal                  modeChangeCallback::run, new BrightnessClamperController.DisplayDeviceData(                  mUniqueDisplayId,                  mThermalBrightnessThrottlingDataId, -                mDisplayDeviceConfig -        ), mContext); +                logicalDisplay.getPowerThrottlingDataIdLocked(), +                mDisplayDeviceConfig), mContext, flags);          // Seed the cached brightness          saveBrightnessInfo(getScreenBrightnessSetting());          mAutomaticBrightnessStrategy = @@ -821,10 +820,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal                  .getDisplayDeviceInfoLocked().type == Display.TYPE_INTERNAL;          final String thermalBrightnessThrottlingDataId =                  mLogicalDisplay.getDisplayInfoLocked().thermalBrightnessThrottlingDataId; - -        mBrightnessClamperController.onDisplayChanged( -                new BrightnessClamperController.DisplayDeviceData(mUniqueDisplayId, -                        mThermalBrightnessThrottlingDataId, config)); +        final String powerThrottlingDataId = +                mLogicalDisplay.getPowerThrottlingDataIdLocked();          mHandler.postAtTime(() -> {              boolean changed = false; @@ -858,6 +855,14 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal              }              mIsDisplayInternal = isDisplayInternal; +            // using local variables here, when mBrightnessThrottler is removed, +            // mThermalBrightnessThrottlingDataId could be removed as well +            // changed = true will be not needed - clampers are maintaining their state and +            // will call updatePowerState if needed. +            mBrightnessClamperController.onDisplayChanged( +                    new BrightnessClamperController.DisplayDeviceData(uniqueId, +                        thermalBrightnessThrottlingDataId, powerThrottlingDataId, config)); +              if (changed) {                  updatePowerState();              } diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java index bd82b81513df..3d4209e0d6f3 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -190,6 +190,11 @@ final class LogicalDisplay {      private SurfaceControl.RefreshRateRange mLayoutLimitedRefreshRate;      /** +     * The ID of the power throttling data that should be used. +     */ +    private String mPowerThrottlingDataId; + +    /**       * RefreshRateRange limitation for @Temperature.ThrottlingStatus       */      @NonNull @@ -205,6 +210,7 @@ final class LogicalDisplay {          mIsEnabled = true;          mIsInTransition = false;          mThermalBrightnessThrottlingDataId = DisplayDeviceConfig.DEFAULT_ID; +        mPowerThrottlingDataId = DisplayDeviceConfig.DEFAULT_ID;          mBaseDisplayInfo.thermalBrightnessThrottlingDataId = mThermalBrightnessThrottlingDataId;      } @@ -911,6 +917,25 @@ final class LogicalDisplay {      }      /** +     * @param powerThrottlingDataId The ID of the brightness throttling data that this +     *                                  display should use. +     */ +    public void setPowerThrottlingDataIdLocked(String powerThrottlingDataId) { +        if (!Objects.equals(powerThrottlingDataId, mPowerThrottlingDataId)) { +            mPowerThrottlingDataId = powerThrottlingDataId; +            mDirty = true; +        } +    } + +    /** +     * Returns powerThrottlingDataId which is the ID of the brightness +     * throttling data that this display should use. +     */ +    public String getPowerThrottlingDataIdLocked() { +        return mPowerThrottlingDataId; +    } + +    /**       * Sets the display of which this display is a follower, regarding brightness or other       * properties. If set to {@link Layout#NO_LEAD_DISPLAY}, this display does not follow any       * others, and has the potential to be a lead display to others. @@ -976,6 +1001,7 @@ final class LogicalDisplay {          pw.println("mLeadDisplayId=" + mLeadDisplayId);          pw.println("mLayoutLimitedRefreshRate=" + mLayoutLimitedRefreshRate);          pw.println("mThermalRefreshRateThrottling=" + mThermalRefreshRateThrottling); +        pw.println("mPowerThrottlingDataId=" + mPowerThrottlingDataId);      }      @Override diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java index b3b16ade0546..c55bc62f5ae6 100644 --- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java +++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java @@ -1123,13 +1123,15 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {                              displayLayout.getRefreshRateThermalThrottlingMapId()                      )              ); -              setEnabledLocked(newDisplay, displayLayout.isEnabled());              newDisplay.setThermalBrightnessThrottlingDataIdLocked(                      displayLayout.getThermalBrightnessThrottlingMapId() == null                              ? DisplayDeviceConfig.DEFAULT_ID                              : displayLayout.getThermalBrightnessThrottlingMapId()); - +            newDisplay.setPowerThrottlingDataIdLocked( +                    displayLayout.getPowerThrottlingMapId() == null +                            ? DisplayDeviceConfig.DEFAULT_ID +                            : displayLayout.getPowerThrottlingMapId());              newDisplay.setDisplayGroupNameLocked(displayLayout.getDisplayGroupName());          }      } diff --git a/services/core/java/com/android/server/display/SmallAreaDetectionController.java b/services/core/java/com/android/server/display/SmallAreaDetectionController.java index adaa5390cb9b..bf384b02d95e 100644 --- a/services/core/java/com/android/server/display/SmallAreaDetectionController.java +++ b/services/core/java/com/android/server/display/SmallAreaDetectionController.java @@ -20,6 +20,7 @@ import android.annotation.NonNull;  import android.annotation.Nullable;  import android.content.Context;  import android.content.pm.PackageManagerInternal; +import android.os.UserHandle;  import android.provider.DeviceConfig;  import android.provider.DeviceConfigInterface;  import android.util.ArrayMap; @@ -30,15 +31,14 @@ import com.android.internal.annotations.GuardedBy;  import com.android.internal.annotations.VisibleForTesting;  import com.android.internal.os.BackgroundThread;  import com.android.server.LocalServices; -import com.android.server.pm.UserManagerInternal; +import com.android.server.pm.pkg.PackageStateInternal;  import java.io.PrintWriter; -import java.util.Arrays;  import java.util.Map;  final class SmallAreaDetectionController { -    private static native void nativeUpdateSmallAreaDetection(int[] uids, float[] thresholds); -    private static native void nativeSetSmallAreaDetectionThreshold(int uid, float threshold); +    private static native void nativeUpdateSmallAreaDetection(int[] appIds, float[] thresholds); +    private static native void nativeSetSmallAreaDetectionThreshold(int appId, float threshold);      // TODO(b/281720315): Move this to DeviceConfig once server side ready.      private static final String KEY_SMALL_AREA_DETECTION_ALLOWLIST = @@ -47,12 +47,8 @@ final class SmallAreaDetectionController {      private final Object mLock = new Object();      private final Context mContext;      private final PackageManagerInternal mPackageManager; -    private final UserManagerInternal mUserManager;      @GuardedBy("mLock")      private final Map<String, Float> mAllowPkgMap = new ArrayMap<>(); -    // TODO(b/298722189): Update allowlist when user changes -    @GuardedBy("mLock") -    private int[] mUserIds;      static SmallAreaDetectionController create(@NonNull Context context) {          final SmallAreaDetectionController controller = @@ -67,7 +63,6 @@ final class SmallAreaDetectionController {      SmallAreaDetectionController(Context context, DeviceConfigInterface deviceConfig) {          mContext = context;          mPackageManager = LocalServices.getService(PackageManagerInternal.class); -        mUserManager = LocalServices.getService(UserManagerInternal.class);          deviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,                  BackgroundThread.getExecutor(),                  new SmallAreaDetectionController.OnPropertiesChangedListener()); @@ -76,6 +71,7 @@ final class SmallAreaDetectionController {      @VisibleForTesting      void updateAllowlist(@Nullable String property) { +        final Map<String, Float> allowPkgMap = new ArrayMap<>();          synchronized (mLock) {              mAllowPkgMap.clear();              if (property != null) { @@ -86,8 +82,11 @@ final class SmallAreaDetectionController {                          .getStringArray(R.array.config_smallAreaDetectionAllowlist);                  for (String defaultMapString : defaultMapStrings) putToAllowlist(defaultMapString);              } -            updateSmallAreaDetection(); + +            if (mAllowPkgMap.isEmpty()) return; +            allowPkgMap.putAll(mAllowPkgMap);          } +        updateSmallAreaDetection(allowPkgMap);      }      @GuardedBy("mLock") @@ -105,43 +104,32 @@ final class SmallAreaDetectionController {          }      } -    @GuardedBy("mLock") -    private void updateUidListForAllUsers(SparseArray<Float> list, String pkg, float threshold) { -        for (int i = 0; i < mUserIds.length; i++) { -            final int userId = mUserIds[i]; -            final int uid = mPackageManager.getPackageUid(pkg, 0, userId); -            if (uid > 0) list.put(uid, threshold); -        } -    } - -    @GuardedBy("mLock") -    private void updateSmallAreaDetection() { -        if (mAllowPkgMap.isEmpty()) return; - -        mUserIds = mUserManager.getUserIds(); - -        final SparseArray<Float> uidThresholdList = new SparseArray<>(); -        for (String pkg : mAllowPkgMap.keySet()) { -            final float threshold = mAllowPkgMap.get(pkg); -            updateUidListForAllUsers(uidThresholdList, pkg, threshold); +    private void updateSmallAreaDetection(Map<String, Float> allowPkgMap) { +        final SparseArray<Float> appIdThresholdList = new SparseArray(allowPkgMap.size()); +        for (String pkg : allowPkgMap.keySet()) { +            final float threshold = allowPkgMap.get(pkg); +            final PackageStateInternal stage = mPackageManager.getPackageStateInternal(pkg); +            if (stage != null) { +                appIdThresholdList.put(stage.getAppId(), threshold); +            }          } -        final int[] uids = new int[uidThresholdList.size()]; -        final float[] thresholds = new float[uidThresholdList.size()]; -        for (int i = 0; i < uidThresholdList.size();  i++) { -            uids[i] = uidThresholdList.keyAt(i); -            thresholds[i] = uidThresholdList.valueAt(i); +        final int[] appIds = new int[appIdThresholdList.size()]; +        final float[] thresholds = new float[appIdThresholdList.size()]; +        for (int i = 0; i < appIdThresholdList.size();  i++) { +            appIds[i] = appIdThresholdList.keyAt(i); +            thresholds[i] = appIdThresholdList.valueAt(i);          } -        updateSmallAreaDetection(uids, thresholds); +        updateSmallAreaDetection(appIds, thresholds);      }      @VisibleForTesting -    void updateSmallAreaDetection(int[] uids, float[] thresholds) { -        nativeUpdateSmallAreaDetection(uids, thresholds); +    void updateSmallAreaDetection(int[] appIds, float[] thresholds) { +        nativeUpdateSmallAreaDetection(appIds, thresholds);      } -    void setSmallAreaDetectionThreshold(int uid, float threshold) { -        nativeSetSmallAreaDetectionThreshold(uid, threshold); +    void setSmallAreaDetectionThreshold(int appId, float threshold) { +        nativeSetSmallAreaDetectionThreshold(appId, threshold);      }      void dump(PrintWriter pw) { @@ -151,7 +139,6 @@ final class SmallAreaDetectionController {              for (String pkg : mAllowPkgMap.keySet()) {                  pw.println("    " + pkg + " threshold = " + mAllowPkgMap.get(pkg));              } -            pw.println("  mUserIds=" + Arrays.toString(mUserIds));          }      } @@ -167,11 +154,15 @@ final class SmallAreaDetectionController {      private final class PackageReceiver implements PackageManagerInternal.PackageListObserver {          @Override          public void onPackageAdded(@NonNull String packageName, int uid) { +            float threshold = 0.0f;              synchronized (mLock) {                  if (mAllowPkgMap.containsKey(packageName)) { -                    setSmallAreaDetectionThreshold(uid, mAllowPkgMap.get(packageName)); +                    threshold = mAllowPkgMap.get(packageName);                  }              } +            if (threshold > 0.0f) { +                setSmallAreaDetectionThreshold(UserHandle.getAppId(uid), threshold); +            }          }      }  } diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java index d910e16de8e8..b0025872aa3d 100644 --- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java +++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java @@ -43,6 +43,7 @@ import static com.android.server.display.DisplayDeviceInfo.FLAG_TRUSTED;  import android.annotation.Nullable;  import android.content.Context;  import android.graphics.Point; +import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession;  import android.hardware.display.IVirtualDisplayCallback;  import android.hardware.display.VirtualDisplayConfig;  import android.media.projection.IMediaProjection; @@ -395,7 +396,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter {          @Override          public Runnable requestDisplayStateLocked(int state, float brightnessState, -                float sdrBrightnessState) { +                float sdrBrightnessState, DisplayOffloadSession displayOffloadSession) {              if (state != mDisplayState) {                  mDisplayState = state;                  if (state == Display.STATE_OFF) { diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java index 54a280fddb6c..68f72d3b085a 100644 --- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java +++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java @@ -52,7 +52,8 @@ abstract class BrightnessClamper<T> {      abstract void stop(); -    enum Type { -        THERMAL +    protected enum Type { +        THERMAL, +        POWER      }  } diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java index 14637afe4f76..787f786a97da 100644 --- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java +++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java @@ -34,9 +34,12 @@ import android.util.Slog;  import com.android.internal.annotations.VisibleForTesting;  import com.android.server.display.DisplayBrightnessState;  import com.android.server.display.DisplayDeviceConfig; +import com.android.server.display.DisplayDeviceConfig.PowerThrottlingConfigData; +import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData;  import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;  import com.android.server.display.brightness.BrightnessReason;  import com.android.server.display.feature.DeviceConfigParameterProvider; +import com.android.server.display.feature.DisplayManagerFlags;  import java.io.PrintWriter;  import java.util.ArrayList; @@ -48,11 +51,9 @@ import java.util.concurrent.Executor;   */  public class BrightnessClamperController {      private static final String TAG = "BrightnessClamperController"; -      private final DeviceConfigParameterProvider mDeviceConfigParameterProvider;      private final Handler mHandler;      private final ClamperChangeListener mClamperChangeListenerExternal; -      private final Executor mExecutor;      private final List<BrightnessClamper<? super DisplayDeviceData>> mClampers; @@ -64,13 +65,15 @@ public class BrightnessClamperController {      private boolean mClamperApplied = false;      public BrightnessClamperController(Handler handler, -            ClamperChangeListener clamperChangeListener, DisplayDeviceData data, Context context) { -        this(new Injector(), handler, clamperChangeListener, data, context); +            ClamperChangeListener clamperChangeListener, DisplayDeviceData data,  Context context, +            DisplayManagerFlags flags) { +        this(new Injector(), handler, clamperChangeListener, data, context, flags);      }      @VisibleForTesting      BrightnessClamperController(Injector injector, Handler handler, -            ClamperChangeListener clamperChangeListener, DisplayDeviceData data, Context context) { +            ClamperChangeListener clamperChangeListener, DisplayDeviceData data,  Context context, +            DisplayManagerFlags flags) {          mDeviceConfigParameterProvider = injector.getDeviceConfigParameterProvider();          mHandler = handler;          mClamperChangeListenerExternal = clamperChangeListener; @@ -84,7 +87,7 @@ public class BrightnessClamperController {              }          }; -        mClampers = injector.getClampers(handler, clamperChangeListenerInternal, data); +        mClampers = injector.getClampers(handler, clamperChangeListenerInternal, data, flags);          mModifiers = injector.getModifiers(context);          mOnPropertiesChangedListener =                  properties -> mClampers.forEach(BrightnessClamper::onDeviceConfigChanged); @@ -144,6 +147,8 @@ public class BrightnessClamperController {              return BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;          } else if (mClamperType == Type.THERMAL) {              return BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL; +        } else if (mClamperType == Type.POWER) { +            return BrightnessInfo.BRIGHTNESS_MAX_REASON_POWER_IC;          } else {              Slog.wtf(TAG, "BrightnessMaxReason not mapped for type=" + mClamperType);              return BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE; @@ -193,6 +198,7 @@ public class BrightnessClamperController {              mClamperType = clamperType;              mClamperChangeListenerExternal.onChanged();          } +      }      private void start() { @@ -219,10 +225,15 @@ public class BrightnessClamperController {          }          List<BrightnessClamper<? super DisplayDeviceData>> getClampers(Handler handler, -                ClamperChangeListener clamperChangeListener, DisplayDeviceData data) { +                ClamperChangeListener clamperChangeListener, DisplayDeviceData data, +                DisplayManagerFlags flags) {              List<BrightnessClamper<? super DisplayDeviceData>> clampers = new ArrayList<>();              clampers.add(                      new BrightnessThermalClamper(handler, clamperChangeListener, data)); +            if (flags.isPowerThrottlingClamperEnabled()) { +                clampers.add(new BrightnessPowerClamper(handler, clamperChangeListener, +                            data)); +            }              return clampers;          } @@ -235,21 +246,26 @@ public class BrightnessClamperController {      }      /** -     * Data for clampers +     * Config Data for clampers       */ -    public static class DisplayDeviceData implements BrightnessThermalClamper.ThermalData { +    public static class DisplayDeviceData implements BrightnessThermalClamper.ThermalData, +                BrightnessPowerClamper.PowerData {          @NonNull          private final String mUniqueDisplayId;          @NonNull          private final String mThermalThrottlingDataId; +        @NonNull +        private final String mPowerThrottlingDataId;          private final DisplayDeviceConfig mDisplayDeviceConfig;          public DisplayDeviceData(@NonNull String uniqueDisplayId,                  @NonNull String thermalThrottlingDataId, +                @NonNull String powerThrottlingDataId,                  @NonNull DisplayDeviceConfig displayDeviceConfig) {              mUniqueDisplayId = uniqueDisplayId;              mThermalThrottlingDataId = thermalThrottlingDataId; +            mPowerThrottlingDataId = powerThrottlingDataId;              mDisplayDeviceConfig = displayDeviceConfig;          } @@ -272,5 +288,24 @@ public class BrightnessClamperController {              return mDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId().get(                      mThermalThrottlingDataId);          } + +        @NonNull +        @Override +        public String getPowerThrottlingDataId() { +            return mPowerThrottlingDataId; +        } + +        @Nullable +        @Override +        public PowerThrottlingData getPowerThrottlingData() { +            return mDisplayDeviceConfig.getPowerThrottlingDataMapByThrottlingId().get( +                    mPowerThrottlingDataId); +        } + +        @Nullable +        @Override +        public PowerThrottlingConfigData getPowerThrottlingConfigData() { +            return mDisplayDeviceConfig.getPowerThrottlingConfigData(); +        }      }  } diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java new file mode 100644 index 000000000000..339b5896f3f7 --- /dev/null +++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java @@ -0,0 +1,295 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display.brightness.clamper; + +import static com.android.server.display.DisplayDeviceConfig.DEFAULT_ID; +import static com.android.server.display.brightness.clamper.BrightnessClamperController.ClamperChangeListener; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Handler; +import android.os.PowerManager; +import android.os.Temperature; +import android.provider.DeviceConfigInterface; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.display.DisplayDeviceConfig.PowerThrottlingConfigData; +import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData; +import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData.ThrottlingLevel; +import com.android.server.display.feature.DeviceConfigParameterProvider; +import com.android.server.display.utils.DeviceConfigParsingUtils; + +import java.io.PrintWriter; +import java.util.List; +import java.util.Map; +import java.util.function.BiFunction; +import java.util.function.Function; + + +class BrightnessPowerClamper extends +        BrightnessClamper<BrightnessPowerClamper.PowerData> { + +    private static final String TAG = "BrightnessPowerClamper"; +    @NonNull +    private final Injector mInjector; +    @NonNull +    private final DeviceConfigParameterProvider mConfigParameterProvider; +    @NonNull +    private final Handler mHandler; +    @NonNull +    private final ClamperChangeListener mChangeListener; +    @Nullable +    private PmicMonitor mPmicMonitor; +    // data from DeviceConfig, for all displays, for all dataSets +    // mapOf(uniqueDisplayId to mapOf(dataSetId to PowerThrottlingData)) +    @NonNull +    private Map<String, Map<String, PowerThrottlingData>> +            mPowerThrottlingDataOverride = Map.of(); +    // data from DisplayDeviceConfig, for particular display+dataSet +    @Nullable +    private PowerThrottlingData mPowerThrottlingDataFromDDC = null; +    // Active data, if mPowerThrottlingDataOverride contains data for mUniqueDisplayId, +    // mDataId, then use it, otherwise mPowerThrottlingDataFromDDC. +    @Nullable +    private PowerThrottlingData mPowerThrottlingDataActive = null; +    @Nullable +    private PowerThrottlingConfigData mPowerThrottlingConfigData = null; + +    private @Temperature.ThrottlingStatus int mCurrentThermalLevel = Temperature.THROTTLING_NONE; +    private float mCurrentAvgPowerConsumed = 0; +    @Nullable +    private String mUniqueDisplayId = null; +    @Nullable +    private String mDataId = null; + +    private final BiFunction<String, String, ThrottlingLevel> mDataPointMapper = (key, value) -> { +        try { +            int status = DeviceConfigParsingUtils.parseThermalStatus(key); +            float powerQuota = Float.parseFloat(value); +            return new ThrottlingLevel(status, powerQuota); +        } catch (IllegalArgumentException iae) { +            return null; +        } +    }; + +    private final Function<List<ThrottlingLevel>, PowerThrottlingData> +            mDataSetMapper = PowerThrottlingData::create; + + +    BrightnessPowerClamper(Handler handler, ClamperChangeListener listener, +            PowerData powerData) { +        this(new Injector(), handler, listener, powerData); +    } + +    @VisibleForTesting +    BrightnessPowerClamper(Injector injector, Handler handler, ClamperChangeListener listener, +            PowerData powerData) { +        mInjector = injector; +        mConfigParameterProvider = injector.getDeviceConfigParameterProvider(); +        mHandler = handler; +        mChangeListener = listener; + +        mHandler.post(() -> { +            setDisplayData(powerData); +            loadOverrideData(); +            start(); +        }); + +    } + +    @Override +    @NonNull +    BrightnessClamper.Type getType() { +        return Type.POWER; +    } + +    @Override +    void onDeviceConfigChanged() { +        mHandler.post(() -> { +            loadOverrideData(); +            recalculateActiveData(); +        }); +    } + +    @Override +    void onDisplayChanged(PowerData data) { +        mHandler.post(() -> { +            setDisplayData(data); +            recalculateActiveData(); +        }); +    } + +    @Override +    void stop() { +        if (mPmicMonitor != null) { +            mPmicMonitor.shutdown(); +        } +    } + +    /** +     * Dumps the state of BrightnessPowerClamper. +     */ +    public void dump(PrintWriter pw) { +        pw.println("BrightnessPowerClamper:"); +        pw.println("  mCurrentAvgPowerConsumed=" + mCurrentAvgPowerConsumed); +        pw.println("  mUniqueDisplayId=" + mUniqueDisplayId); +        pw.println("  mCurrentThermalLevel=" + mCurrentThermalLevel); +        pw.println("  mPowerThrottlingDataFromDDC=" + (mPowerThrottlingDataFromDDC == null ? "null" +                : mPowerThrottlingDataFromDDC.toString())); +        super.dump(pw); +    } + +    private void recalculateActiveData() { +        if (mUniqueDisplayId == null || mDataId == null) { +            return; +        } +        mPowerThrottlingDataActive = mPowerThrottlingDataOverride +                .getOrDefault(mUniqueDisplayId, Map.of()).getOrDefault(mDataId, +                        mPowerThrottlingDataFromDDC); +        if (mPowerThrottlingDataActive != null) { +            if (mPmicMonitor != null) { +                mPmicMonitor.stop(); +                mPmicMonitor.start(); +            } +        } else { +            if (mPmicMonitor != null) { +                mPmicMonitor.stop(); +            } +        } +        recalculateBrightnessCap(); +    } + +    private void loadOverrideData() { +        String throttlingDataOverride = mConfigParameterProvider.getPowerThrottlingData(); +        mPowerThrottlingDataOverride = DeviceConfigParsingUtils.parseDeviceConfigMap( +                throttlingDataOverride, mDataPointMapper, mDataSetMapper); +    } + +    private void setDisplayData(@NonNull PowerData data) { +        mUniqueDisplayId = data.getUniqueDisplayId(); +        mDataId = data.getPowerThrottlingDataId(); +        mPowerThrottlingDataFromDDC = data.getPowerThrottlingData(); +        if (mPowerThrottlingDataFromDDC == null && !DEFAULT_ID.equals(mDataId)) { +            Slog.wtf(TAG, +                    "Power throttling data is missing for powerThrottlingDataId=" + mDataId); +        } + +        mPowerThrottlingConfigData = data.getPowerThrottlingConfigData(); +        if (mPowerThrottlingConfigData == null) { +            Slog.d(TAG, +                    "Power throttling data is missing for configuration data."); +        } +    } + +    private void recalculateBrightnessCap() { +        boolean isActive = false; +        float targetBrightnessCap = PowerManager.BRIGHTNESS_MAX; +        float powerQuota = getPowerQuotaForThermalStatus(mCurrentThermalLevel); +        if (mPowerThrottlingDataActive == null) { +            return; +        } +        if (powerQuota > 0 && mCurrentAvgPowerConsumed > powerQuota) { +            isActive = true; +            // calculate new brightness Cap. +            // Brightness has a linear relation to power-consumed. +            targetBrightnessCap = +                    (powerQuota / mCurrentAvgPowerConsumed) * PowerManager.BRIGHTNESS_MAX; +            // Cap to lowest allowed brightness on device. +            targetBrightnessCap = Math.max(targetBrightnessCap, +                    mPowerThrottlingConfigData.brightnessLowestCapAllowed); +        } + +        if (mBrightnessCap != targetBrightnessCap || mIsActive != isActive) { +            mIsActive = isActive; +            mBrightnessCap = targetBrightnessCap; +            mChangeListener.onChanged(); +        } +    } + +    private float getPowerQuotaForThermalStatus(@Temperature.ThrottlingStatus int thermalStatus) { +        float powerQuota = 0f; +        if (mPowerThrottlingDataActive != null) { +            // Throttling levels are sorted by increasing severity +            for (ThrottlingLevel level : mPowerThrottlingDataActive.throttlingLevels) { +                if (level.thermalStatus <= thermalStatus) { +                    powerQuota = level.powerQuotaMilliWatts; +                } else { +                    // Throttling levels that are greater than the current status are irrelevant +                    break; +                } +            } +        } +        return powerQuota; +    } + +    private void recalculatePowerQuotaChange(float avgPowerConsumed, int thermalStatus) { +        mHandler.post(() -> { +            mCurrentThermalLevel = thermalStatus; +            mCurrentAvgPowerConsumed = avgPowerConsumed; +            recalculateBrightnessCap(); +        }); +    } + +    private void start() { +        if (mPowerThrottlingConfigData == null) { +            return; +        } +        PowerChangeListener listener = (powerConsumed, thermalStatus) -> { +            recalculatePowerQuotaChange(powerConsumed, thermalStatus); +        }; +        mPmicMonitor = +            mInjector.getPmicMonitor(listener, mPowerThrottlingConfigData.pollingWindowMillis); +        mPmicMonitor.start(); +    } + +    public interface PowerData { +        @NonNull +        String getUniqueDisplayId(); + +        @NonNull +        String getPowerThrottlingDataId(); + +        @Nullable +        PowerThrottlingData getPowerThrottlingData(); + +        @Nullable +        PowerThrottlingConfigData getPowerThrottlingConfigData(); +    } + +    /** +     * Power change listener +     */ +    @FunctionalInterface +    public interface PowerChangeListener { +        /** +         * Notifies that power state changed from power controller. +         */ +        void onChanged(float avgPowerConsumed, @Temperature.ThrottlingStatus int thermalStatus); +    } + +    @VisibleForTesting +    static class Injector { +        PmicMonitor getPmicMonitor(PowerChangeListener listener, int pollingTime) { +            return new PmicMonitor(listener, pollingTime); +        } + +        DeviceConfigParameterProvider getDeviceConfigParameterProvider() { +            return new DeviceConfigParameterProvider(DeviceConfigInterface.REAL); +        } +    } +} diff --git a/services/core/java/com/android/server/display/brightness/clamper/PmicMonitor.java b/services/core/java/com/android/server/display/brightness/clamper/PmicMonitor.java new file mode 100644 index 000000000000..26784f2353ff --- /dev/null +++ b/services/core/java/com/android/server/display/brightness/clamper/PmicMonitor.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display.brightness.clamper; + +import static com.android.server.display.brightness.clamper.BrightnessPowerClamper.PowerChangeListener; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.hardware.power.stats.EnergyConsumer; +import android.hardware.power.stats.EnergyConsumerResult; +import android.hardware.power.stats.EnergyConsumerType; +import android.os.IThermalService; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.Temperature; +import android.power.PowerStatsInternal; +import android.util.IntArray; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.LocalServices; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +/** + * Monitors the display consumed power and helps make informed decision, + * regarding overconsumption. + */ +public class PmicMonitor { +    private static final String TAG = "PmicMonitor"; + +    // The executor to periodically monitor the display power. +    private final ScheduledExecutorService mExecutor; +    @NonNull +    private final PowerChangeListener mPowerChangeListener; +    private final long mPowerMonitorPeriodConfigSecs; +    private final PowerStatsInternal mPowerStatsInternal; +    @VisibleForTesting final IThermalService mThermalService; +    private ScheduledFuture<?> mPmicMonitorFuture; +    private float mLastEnergyConsumed = 0; +    private float mCurrentAvgPower = 0; +    private Temperature mCurrentTemperature; +    private long mCurrentTimestampMillis = 0; + +    PmicMonitor(PowerChangeListener listener, int powerMonitorPeriodConfigSecs) { +        mPowerChangeListener = listener; +        mPowerStatsInternal = LocalServices.getService(PowerStatsInternal.class); +        mThermalService = IThermalService.Stub.asInterface( +                ServiceManager.getService(Context.THERMAL_SERVICE)); +        // start a periodic worker thread. +        mExecutor = Executors.newSingleThreadScheduledExecutor(); +        mPowerMonitorPeriodConfigSecs = (long) powerMonitorPeriodConfigSecs; +    } + +    @Nullable +    private Temperature getDisplayTemperature() { +        Temperature retTemperature = null; +        try { +            Temperature[] temperatures; +            // TODO b/279114539 Try DISPLAY first and then fallback to SKIN. +            temperatures = mThermalService.getCurrentTemperaturesWithType( +                        Temperature.TYPE_SKIN); +            if (temperatures.length > 1) { +                Slog.w(TAG, "Multiple skin temperatures not allowed!"); +            } +            if (temperatures.length > 0) { +                retTemperature = temperatures[0]; +            } +        } catch (RemoteException e) { +            Slog.w(TAG, "getDisplayTemperature failed" + e); +        } +        return retTemperature; +    } + +    private void capturePeriodicDisplayPower() { +        final EnergyConsumer[] energyConsumers = mPowerStatsInternal.getEnergyConsumerInfo(); +        if (energyConsumers == null || energyConsumers.length == 0) { +            return; +        } +        final IntArray energyConsumerIds = new IntArray(); +        for (int i = 0; i < energyConsumers.length; i++) { +            if (energyConsumers[i].type == EnergyConsumerType.DISPLAY) { +                energyConsumerIds.add(energyConsumers[i].id); +            } +        } + +        if (energyConsumerIds.size() == 0) { +            Slog.w(TAG, "DISPLAY energyConsumerIds size is null"); +            return; +        } +        CompletableFuture<EnergyConsumerResult[]> futureECRs = +                mPowerStatsInternal.getEnergyConsumedAsync(energyConsumerIds.toArray()); +        if (futureECRs == null) { +            Slog.w(TAG, "Energy consumers results are null"); +            return; +        } + +        EnergyConsumerResult[] displayResults; +        try { +            displayResults = futureECRs.get(); +        } catch (InterruptedException e) { +            Slog.w(TAG, "timeout or interrupt reading getEnergyConsumedAsync failed", e); +            displayResults = null; +        } catch (ExecutionException e) { +            Slog.wtf(TAG, "exception reading getEnergyConsumedAsync: ", e); +            displayResults = null; +        } + +        if (displayResults == null || displayResults.length == 0) { +            Slog.w(TAG, "displayResults are null"); +            return; +        } +        // Support for only 1 display rail. +        float energyConsumed = (displayResults[0].energyUWs - mLastEnergyConsumed); +        float timeIntervalSeconds = +                (displayResults[0].timestampMs - mCurrentTimestampMillis) / 1000.f; +        // energy consumed is received in microwatts-seconds. +        float currentPower = energyConsumed / timeIntervalSeconds; +        // convert power received in microwatts to milliwatts. +        currentPower = currentPower / 1000.f; + +        // capture thermal state. +        Temperature displayTemperature = getDisplayTemperature(); +        mCurrentAvgPower = currentPower; +        mCurrentTemperature = displayTemperature; +        mLastEnergyConsumed = displayResults[0].energyUWs; +        mCurrentTimestampMillis = displayResults[0].timestampMs; +        if (mCurrentTemperature != null) { +            mPowerChangeListener.onChanged(mCurrentAvgPower, mCurrentTemperature.getStatus()); +        } +    } + +    /** +    * Start polling the power IC. +    */ +    public void start() { +        if (mPowerStatsInternal == null) { +            Slog.w(TAG, "Power stats service not found for monitoring."); +            return; +        } +        if (mThermalService == null) { +            Slog.w(TAG, "Thermal service not found."); +            return; +        } +        if (mPmicMonitorFuture == null) { +            mPmicMonitorFuture = mExecutor.scheduleAtFixedRate( +                                    this::capturePeriodicDisplayPower, +                                    mPowerMonitorPeriodConfigSecs, +                                    mPowerMonitorPeriodConfigSecs, +                                    TimeUnit.SECONDS); +        } else { +            Slog.e(TAG, "already scheduled, stop() called before start."); +        } +    } + +    /** +     * Stop polling to power IC. +     */ +    public void stop() { +        if (mPmicMonitorFuture != null) { +            mPmicMonitorFuture.cancel(true); +            mPmicMonitorFuture = null; +        } +    } + +    /** +     * Shutdown power IC service and worker thread. +     */ +    public void shutdown() { +        mExecutor.shutdownNow(); +    } +} diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java index 03b0cfc3d844..e3aa161f001a 100644 --- a/services/core/java/com/android/server/display/color/ColorDisplayService.java +++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java @@ -234,7 +234,9 @@ public final class ColorDisplayService extends SystemService {          }      } -    @VisibleForTesting void onUserChanged(int userHandle) { +    // should be called in handler thread (same thread that started animation) +    @VisibleForTesting +    void onUserChanged(int userHandle) {          final ContentResolver cr = getContext().getContentResolver();          if (mCurrentUser != UserHandle.USER_NULL) { @@ -473,6 +475,15 @@ public final class ColorDisplayService extends SystemService {          }      } +    // should be called in handler thread (same thread that started animation) +    @VisibleForTesting +    void cancelAllAnimators() { +        mNightDisplayTintController.cancelAnimator(); +        mGlobalSaturationTintController.cancelAnimator(); +        mReduceBrightColorsTintController.cancelAnimator(); +        mDisplayWhiteBalanceTintController.cancelAnimator(); +    } +      private boolean resetReduceBrightColors() {          if (mCurrentUser == UserHandle.USER_NULL) {              return false; diff --git a/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java b/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java index 23ffe5948da8..465584c3d90c 100644 --- a/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java +++ b/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java @@ -77,6 +77,12 @@ public class DeviceConfigParameterProvider {      // Test parameters      // usage e.g.: adb shell device_config put display_manager refresh_rate_in_hbm_sunlight 90 +    // allows to customize power throttling data +    public String getPowerThrottlingData() { +        return mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, +                DisplayManager.DeviceConfig.KEY_POWER_THROTTLING_DATA, null); +    } +      // allows to customize brightness throttling data      public String getBrightnessThrottlingData() {          return mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java index 7050c5a4168f..d953e8e52365 100644 --- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java +++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java @@ -67,20 +67,35 @@ public class DisplayManagerFlags {              Flags.FLAG_BACK_UP_SMOOTH_DISPLAY_AND_FORCE_PEAK_REFRESH_RATE,              Flags::backUpSmoothDisplayAndForcePeakRefreshRate); +    private final FlagState mPowerThrottlingClamperFlagState = new FlagState( +            Flags.FLAG_ENABLE_POWER_THROTTLING_CLAMPER, +            Flags::enablePowerThrottlingClamper); + +    private final FlagState mSmallAreaDetectionFlagState = new FlagState( +            Flags.FLAG_ENABLE_SMALL_AREA_DETECTION, +            Flags::enableSmallAreaDetection); +      /** Returns whether connected display management is enabled or not. */      public boolean isConnectedDisplayManagementEnabled() {          return mConnectedDisplayManagementFlagState.isEnabled();      } -    /** Returns whether hdr clamper is enabled on not*/ +    /** Returns whether NBM Controller is enabled or not. */      public boolean isNbmControllerEnabled() {          return mNbmControllerFlagState.isEnabled();      } +    /** Returns whether hdr clamper is enabled on not. */      public boolean isHdrClamperEnabled() {          return mHdrClamperFlagState.isEnabled();      } +    /** Returns whether power throttling clamper is enabled on not. */ +    public boolean isPowerThrottlingClamperEnabled() { +        return mPowerThrottlingClamperFlagState.isEnabled(); +    } + +      /**       * Returns whether adaptive tone improvements are enabled       */ @@ -136,6 +151,10 @@ public class DisplayManagerFlags {          return mBackUpSmoothDisplayAndForcePeakRefreshRateFlagState.isEnabled();      } +    public boolean isSmallAreaDetectionEnabled() { +        return mSmallAreaDetectionFlagState.isEnabled(); +    } +      private static class FlagState {          private final String mName; diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig index a85e10dcfe2e..9141814da6fc 100644 --- a/services/core/java/com/android/server/display/feature/display_flags.aconfig +++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig @@ -27,6 +27,14 @@ flag {  }  flag { +    name: "enable_power_throttling_clamper" +    namespace: "display_manager" +    description: "Feature flag for Power Throttling Clamper" +    bug: "294777007" +    is_fixed_read_only: true +} + +flag {      name: "enable_adaptive_tone_improvements_1"      namespace: "display_manager"      description: "Feature flag for Adaptive Tone Improvements" @@ -96,3 +104,12 @@ flag {      bug: "211737588"      is_fixed_read_only: true  } + +flag { +    name: "enable_small_area_detection" +    namespace: "display_manager" +    description: "Feature flag for SmallAreaDetection" +    bug: "298722189" +    is_fixed_read_only: true +} + diff --git a/services/core/java/com/android/server/display/layout/Layout.java b/services/core/java/com/android/server/display/layout/Layout.java index d9ec3de46b0e..40cb3303adda 100644 --- a/services/core/java/com/android/server/display/layout/Layout.java +++ b/services/core/java/com/android/server/display/layout/Layout.java @@ -80,7 +80,8 @@ public class Layout {          createDisplayLocked(address, /* isDefault= */ true, /* isEnabled= */ true,                  DEFAULT_DISPLAY_GROUP_NAME, idProducer, POSITION_UNKNOWN,                  /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ null, -                /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null); +                /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null, +                /* powerThrottlingMapId= */ null);      }      /** @@ -97,6 +98,7 @@ public class Layout {       * @param refreshRateZoneId Layout limited refresh rate zone name.       * @param refreshRateThermalThrottlingMapId Name of which refresh rate throttling       *                                          policy should be used. +     * @param powerThrottlingMapId Name of which power throttling policy should be used.       *       * @exception IllegalArgumentException When a default display owns a display group other than       *            DEFAULT_DISPLAY_GROUP. @@ -106,7 +108,8 @@ public class Layout {              String displayGroupName, DisplayIdProducer idProducer, int position,              @Nullable DisplayAddress leadDisplayAddress, String brightnessThrottlingMapId,              @Nullable String refreshRateZoneId, -            @Nullable String refreshRateThermalThrottlingMapId) { +            @Nullable String refreshRateThermalThrottlingMapId, +            @Nullable String powerThrottlingMapId) {          if (contains(address)) {              Slog.w(TAG, "Attempting to add second definition for display-device: " + address);              return; @@ -139,7 +142,7 @@ public class Layout {          final Display display = new Display(address, logicalDisplayId, isEnabled, displayGroupName,                  brightnessThrottlingMapId, position, leadDisplayAddress, refreshRateZoneId, -                refreshRateThermalThrottlingMapId); +                refreshRateThermalThrottlingMapId, powerThrottlingMapId);          mDisplays.add(display);      } @@ -311,6 +314,9 @@ public class Layout {          @Nullable          private final String mThermalRefreshRateThrottlingMapId; +        @Nullable +        private final String mPowerThrottlingMapId; +          // The ID of the lead display that this display will follow in a layout. -1 means no lead.          // This is determined using {@code mLeadDisplayAddress}.          private int mLeadDisplayId; @@ -318,7 +324,8 @@ public class Layout {          private Display(@NonNull DisplayAddress address, int logicalDisplayId, boolean isEnabled,                  @NonNull String displayGroupName, String brightnessThrottlingMapId, int position,                  @Nullable DisplayAddress leadDisplayAddress, @Nullable String refreshRateZoneId, -                @Nullable String refreshRateThermalThrottlingMapId) { +                @Nullable String refreshRateThermalThrottlingMapId, +                @Nullable String powerThrottlingMapId) {              mAddress = address;              mLogicalDisplayId = logicalDisplayId;              mIsEnabled = isEnabled; @@ -328,6 +335,7 @@ public class Layout {              mLeadDisplayAddress = leadDisplayAddress;              mRefreshRateZoneId = refreshRateZoneId;              mThermalRefreshRateThrottlingMapId = refreshRateThermalThrottlingMapId; +            mPowerThrottlingMapId = powerThrottlingMapId;              mLeadDisplayId = NO_LEAD_DISPLAY;          } @@ -344,6 +352,7 @@ public class Layout {                      + ", mLeadDisplayId: " + mLeadDisplayId                      + ", mLeadDisplayAddress: " + mLeadDisplayAddress                      + ", mThermalRefreshRateThrottlingMapId: " + mThermalRefreshRateThrottlingMapId +                    + ", mPowerThrottlingMapId: " + mPowerThrottlingMapId                      + "}";          } @@ -366,7 +375,9 @@ public class Layout {                      && this.mLeadDisplayId == otherDisplay.mLeadDisplayId                      && Objects.equals(mLeadDisplayAddress, otherDisplay.mLeadDisplayAddress)                      && Objects.equals(mThermalRefreshRateThrottlingMapId, -                    otherDisplay.mThermalRefreshRateThrottlingMapId); +                    otherDisplay.mThermalRefreshRateThrottlingMapId) +                    && Objects.equals(mPowerThrottlingMapId, +                    otherDisplay.mPowerThrottlingMapId);          }          @Override @@ -382,6 +393,7 @@ public class Layout {              result = 31 * result + mLeadDisplayId;              result = 31 * result + Objects.hashCode(mLeadDisplayAddress);              result = 31 * result + Objects.hashCode(mThermalRefreshRateThrottlingMapId); +            result = 31 * result + Objects.hashCode(mPowerThrottlingMapId);              return result;          } @@ -441,6 +453,15 @@ public class Layout {              return mThermalRefreshRateThrottlingMapId;          } +        /** +         * Gets the id of the power throttling map that should be used. +         * @return The ID of the power throttling map that this display should use, +         *         null if unspecified, will fall back to default. +         */ +        public String getPowerThrottlingMapId() { +            return mPowerThrottlingMapId; +        } +          private void setLeadDisplayId(int id) {              mLeadDisplayId = id;          } diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java index ca23844044ca..d023913c9694 100644 --- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java @@ -723,7 +723,9 @@ public class DisplayModeDirector {              if (mode.getPhysicalWidth() > maxAllowedWidth                      || mode.getPhysicalHeight() > maxAllowedHeight                      || mode.getPhysicalWidth() < outSummary.minWidth -                    || mode.getPhysicalHeight() < outSummary.minHeight) { +                    || mode.getPhysicalHeight() < outSummary.minHeight +                    || mode.getRefreshRate() < outSummary.minPhysicalRefreshRate +                    || mode.getRefreshRate() > outSummary.maxPhysicalRefreshRate) {                  continue;              } diff --git a/services/core/java/com/android/server/feature/Android.bp b/services/core/java/com/android/server/feature/Android.bp deleted file mode 100644 index 067288d6650d..000000000000 --- a/services/core/java/com/android/server/feature/Android.bp +++ /dev/null @@ -1,12 +0,0 @@ -aconfig_declarations { -    name: "dropbox_flags", -    package: "com.android.server.feature.flags", -    srcs: [ -        "dropbox_flags.aconfig", -    ], -} - -java_aconfig_library { -    name: "dropbox_flags_lib", -    aconfig_declarations: "dropbox_flags", -} diff --git a/services/core/java/com/android/server/feature/dropbox_flags.aconfig b/services/core/java/com/android/server/feature/dropbox_flags.aconfig deleted file mode 100644 index fee4bf377ddc..000000000000 --- a/services/core/java/com/android/server/feature/dropbox_flags.aconfig +++ /dev/null @@ -1,8 +0,0 @@ -package: "com.android.server.feature.flags" - -flag{ -    name: "enable_read_dropbox_permission" -    namespace: "preload_safety" -    description: "Feature flag for permission to Read dropbox data" -    bug: "287512663" -}
\ No newline at end of file diff --git a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java index b090334db352..27e1b9a303f2 100644 --- a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java +++ b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java @@ -60,84 +60,99 @@ public final class ImeTrackerService extends IImeTracker.Stub {      private static final long TIMEOUT_MS = 10_000;      /** Handler for registering timeouts for live entries. */ +    @GuardedBy("mLock")      private final Handler mHandler;      /** Singleton instance of the History. */ -    @GuardedBy("ImeTrackerService.this") +    @GuardedBy("mLock")      private final History mHistory = new History(); +    private final Object mLock = new Object(); +      ImeTrackerService(@NonNull Looper looper) {          mHandler = new Handler(looper, null /* callback */, true /* async */);      }      @NonNull      @Override -    public synchronized ImeTracker.Token onRequestShow(@NonNull String tag, int uid, +    public ImeTracker.Token onRequestShow(@NonNull String tag, int uid,              @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) {          final var binder = new Binder();          final var token = new ImeTracker.Token(binder, tag);          final var entry = new History.Entry(tag, uid, ImeTracker.TYPE_SHOW, ImeTracker.STATUS_RUN,                  origin, reason); -        mHistory.addEntry(binder, entry); - -        // Register a delayed task to handle the case where the new entry times out. -        mHandler.postDelayed(() -> { -            synchronized (ImeTrackerService.this) { -                mHistory.setFinished(token, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET); -            } -        }, TIMEOUT_MS); - +        synchronized (mLock) { +            mHistory.addEntry(binder, entry); + +            // Register a delayed task to handle the case where the new entry times out. +            mHandler.postDelayed(() -> { +                synchronized (mLock) { +                    mHistory.setFinished(token, ImeTracker.STATUS_TIMEOUT, +                            ImeTracker.PHASE_NOT_SET); +                } +            }, TIMEOUT_MS); +        }          return token;      }      @NonNull      @Override -    public synchronized ImeTracker.Token onRequestHide(@NonNull String tag, int uid, +    public ImeTracker.Token onRequestHide(@NonNull String tag, int uid,              @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) {          final var binder = new Binder();          final var token = new ImeTracker.Token(binder, tag);          final var entry = new History.Entry(tag, uid, ImeTracker.TYPE_HIDE, ImeTracker.STATUS_RUN,                  origin, reason); -        mHistory.addEntry(binder, entry); - -        // Register a delayed task to handle the case where the new entry times out. -        mHandler.postDelayed(() -> { -            synchronized (ImeTrackerService.this) { -                mHistory.setFinished(token, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET); -            } -        }, TIMEOUT_MS); - +        synchronized (mLock) { +            mHistory.addEntry(binder, entry); + +            // Register a delayed task to handle the case where the new entry times out. +            mHandler.postDelayed(() -> { +                synchronized (mLock) { +                    mHistory.setFinished(token, ImeTracker.STATUS_TIMEOUT, +                            ImeTracker.PHASE_NOT_SET); +                } +            }, TIMEOUT_MS); +        }          return token;      }      @Override -    public synchronized void onProgress(@NonNull IBinder binder, @ImeTracker.Phase int phase) { -        final var entry = mHistory.getEntry(binder); -        if (entry == null) return; +    public void onProgress(@NonNull IBinder binder, @ImeTracker.Phase int phase) { +        synchronized (mLock) { +            final var entry = mHistory.getEntry(binder); +            if (entry == null) return; -        entry.mPhase = phase; +            entry.mPhase = phase; +        }      }      @Override -    public synchronized void onFailed(@NonNull ImeTracker.Token statsToken, -            @ImeTracker.Phase int phase) { -        mHistory.setFinished(statsToken, ImeTracker.STATUS_FAIL, phase); +    public void onFailed(@NonNull ImeTracker.Token statsToken, @ImeTracker.Phase int phase) { +        synchronized (mLock) { +            mHistory.setFinished(statsToken, ImeTracker.STATUS_FAIL, phase); +        }      }      @Override -    public synchronized void onCancelled(@NonNull ImeTracker.Token statsToken, -            @ImeTracker.Phase int phase) { -        mHistory.setFinished(statsToken, ImeTracker.STATUS_CANCEL, phase); +    public void onCancelled(@NonNull ImeTracker.Token statsToken, @ImeTracker.Phase int phase) { +        synchronized (mLock) { +            mHistory.setFinished(statsToken, ImeTracker.STATUS_CANCEL, phase); +        }      }      @Override -    public synchronized void onShown(@NonNull ImeTracker.Token statsToken) { -        mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET); +    public void onShown(@NonNull ImeTracker.Token statsToken) { +        synchronized (mLock) { +            mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET); +        }      }      @Override -    public synchronized void onHidden(@NonNull ImeTracker.Token statsToken) { -        mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET); +    public void onHidden(@NonNull ImeTracker.Token statsToken) { +        synchronized (mLock) { +            mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET); +        }      }      /** @@ -146,25 +161,30 @@ public final class ImeTrackerService extends IImeTracker.Stub {       * @param statsToken the token corresponding to the current IME request.       * @param requestWindowName the name of the window that created the IME request.       */ -    public synchronized void onImmsUpdate(@NonNull ImeTracker.Token statsToken, +    public void onImmsUpdate(@NonNull ImeTracker.Token statsToken,              @NonNull String requestWindowName) { -        final var entry = mHistory.getEntry(statsToken.getBinder()); -        if (entry == null) return; +        synchronized (mLock) { +            final var entry = mHistory.getEntry(statsToken.getBinder()); +            if (entry == null) return; -        entry.mRequestWindowName = requestWindowName; +            entry.mRequestWindowName = requestWindowName; +        }      }      /** Dumps the contents of the history. */ -    public synchronized void dump(@NonNull PrintWriter pw, @NonNull String prefix) { -        mHistory.dump(pw, prefix); +    public void dump(@NonNull PrintWriter pw, @NonNull String prefix) { +        synchronized (mLock) { +            mHistory.dump(pw, prefix); +        }      }      @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)      @Override -    public synchronized boolean hasPendingImeVisibilityRequests() { +    public boolean hasPendingImeVisibilityRequests() {          super.hasPendingImeVisibilityRequests_enforcePermission(); - -        return !mHistory.mLiveEntries.isEmpty(); +        synchronized (mLock) { +            return !mHistory.mLiveEntries.isEmpty(); +        }      }      /** diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java index 1c5ecb70a3ae..1e8b387fc189 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java @@ -45,7 +45,7 @@ import com.android.internal.util.IndentingPrintWriter;  import com.android.internal.util.Preconditions;  import com.android.internal.widget.LockPatternUtils;  import com.android.server.LocalServices; -import com.android.server.PersistentDataBlockManagerInternal; +import com.android.server.pdb.PersistentDataBlockManagerInternal;  import java.io.ByteArrayInputStream;  import java.io.ByteArrayOutputStream; diff --git a/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java index eb997badca52..8bc69c226d1a 100644 --- a/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java +++ b/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java @@ -345,7 +345,7 @@ import java.util.Set;      }      private void notifyBluetoothRoutesUpdated() { -        mListener.onBluetoothRoutesUpdated(getAllBluetoothRoutes()); +        mListener.onBluetoothRoutesUpdated();      }      private BluetoothRouteInfo createBluetoothRoute(BluetoothDevice device) { diff --git a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java index 93f6ff3de3c2..33190ade4f42 100644 --- a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java +++ b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java @@ -231,7 +231,7 @@ import java.util.Objects;              }              if (isDeviceRouteChanged) { -                mOnDeviceRouteChangedListener.onDeviceRouteChanged(deviceRoute); +                mOnDeviceRouteChangedListener.onDeviceRouteChanged();              }          }      } diff --git a/services/core/java/com/android/server/media/BluetoothRouteController.java b/services/core/java/com/android/server/media/BluetoothRouteController.java index ddeeacc76579..2b01001fd7d1 100644 --- a/services/core/java/com/android/server/media/BluetoothRouteController.java +++ b/services/core/java/com/android/server/media/BluetoothRouteController.java @@ -136,12 +136,8 @@ import java.util.Objects;       */      interface BluetoothRoutesUpdatedListener { -        /** -         * Called when Bluetooth routes have changed. -         * -         * @param routes updated Bluetooth routes list. -         */ -        void onBluetoothRoutesUpdated(@NonNull List<MediaRoute2Info> routes); +        /** Called when Bluetooth routes have changed. */ +        void onBluetoothRoutesUpdated();      }      /** diff --git a/services/core/java/com/android/server/media/DeviceRouteController.java b/services/core/java/com/android/server/media/DeviceRouteController.java index e17f4a3fd42f..7876095a548a 100644 --- a/services/core/java/com/android/server/media/DeviceRouteController.java +++ b/services/core/java/com/android/server/media/DeviceRouteController.java @@ -93,12 +93,8 @@ import com.android.media.flags.Flags;       */      interface OnDeviceRouteChangedListener { -        /** -         * Called when device route has changed. -         * -         * @param deviceRoute non-null device route. -         */ -        void onDeviceRouteChanged(@NonNull MediaRoute2Info deviceRoute); +        /** Called when device route has changed. */ +        void onDeviceRouteChanged();      }  } diff --git a/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java b/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java index 198040378dc6..ba3cecf7c091 100644 --- a/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java +++ b/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java @@ -280,7 +280,7 @@ class LegacyBluetoothRouteController implements BluetoothRouteController {      private void notifyBluetoothRoutesUpdated() {          if (mListener != null) { -            mListener.onBluetoothRoutesUpdated(getAllBluetoothRoutes()); +            mListener.onBluetoothRoutesUpdated();          }      } diff --git a/services/core/java/com/android/server/media/LegacyDeviceRouteController.java b/services/core/java/com/android/server/media/LegacyDeviceRouteController.java index 971d11f24b9c..6ba40ae33f3c 100644 --- a/services/core/java/com/android/server/media/LegacyDeviceRouteController.java +++ b/services/core/java/com/android/server/media/LegacyDeviceRouteController.java @@ -165,8 +165,8 @@ import java.util.Objects;          }      } -    private void notifyDeviceRouteUpdate(@NonNull MediaRoute2Info deviceRoute) { -        mOnDeviceRouteChangedListener.onDeviceRouteChanged(deviceRoute); +    private void notifyDeviceRouteUpdate() { +        mOnDeviceRouteChangedListener.onDeviceRouteChanged();      }      private class AudioRoutesObserver extends IAudioRoutesObserver.Stub { @@ -177,7 +177,7 @@ import java.util.Objects;              synchronized (LegacyDeviceRouteController.this) {                  mDeviceRoute = deviceRoute;              } -            notifyDeviceRouteUpdate(deviceRoute); +            notifyDeviceRouteUpdate();          }      } diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index 95ca08cc7fe9..a158b18d91b4 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -97,20 +97,23 @@ import java.util.concurrent.CopyOnWriteArrayList;  public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionRecordImpl {      /** -     * {@link MediaSession#setMediaButtonBroadcastReceiver(ComponentName)} throws an {@link -     * IllegalArgumentException} if the provided {@link ComponentName} does not resolve to a valid -     * {@link android.content.BroadcastReceiver broadcast receiver} for apps targeting Android U and -     * above. For apps targeting Android T and below, the request will be ignored. +     * {@link android.media.session.MediaSession#setMediaButtonBroadcastReceiver( +     * android.content.ComponentName)} throws an {@link +     * java.lang.IllegalArgumentException} if the provided {@link android.content.ComponentName} +     * does not resolve to a valid {@link android.content.BroadcastReceiver broadcast receiver} +     * for apps targeting Android U and above. For apps targeting Android T and below, the request +     * will be ignored.       */      @ChangeId      @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)      static final long THROW_FOR_INVALID_BROADCAST_RECEIVER = 270049379L;      /** -     * {@link MediaSession#setMediaButtonReceiver(PendingIntent)} throws an {@link -     * IllegalArgumentException} if the provided {@link PendingIntent} targets an {@link -     * android.app.Activity activity} for apps targeting Android V and above. For apps targeting -     * Android U and below, the request will be ignored. +     * {@link android.media.session.MediaSession#setMediaButtonReceiver(android.app.PendingIntent)} +     * throws an {@link java.lang.IllegalArgumentException} if the provided +     * {@link android.app.PendingIntent} targets an {@link android.app.Activity activity} for +     * apps targeting Android V and above. For apps targeting Android U and below, the request will +     * be ignored.       */      @ChangeId      @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 803ab28f2a65..0e8f90795ef9 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -19,6 +19,7 @@ package com.android.server.media;  import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;  import static android.os.UserHandle.ALL;  import static android.os.UserHandle.CURRENT; +  import static com.android.server.media.MediaKeyDispatcher.KEY_EVENT_LONG_PRESS;  import static com.android.server.media.MediaKeyDispatcher.isDoubleTapOverridden;  import static com.android.server.media.MediaKeyDispatcher.isLongPressOverridden; @@ -1904,6 +1905,15 @@ public class MediaSessionService extends SystemService implements Monitor {                                  keyEvent, AudioManager.USE_DEFAULT_STREAM_TYPE, false);                          return;                      } +                    if (Flags.fallbackToDefaultHandlingWhenMediaSessionHasFixedVolumeHandling() +                            && !record.canHandleVolumeKey()) { +                        Log.d(TAG, "Session with packageName=" + record.getPackageName() +                                + " doesn't support volume adjustment." +                                + " Fallbacks to the default handling."); +                        dispatchVolumeKeyEventLocked(packageName, opPackageName, pid, uid, true, +                                keyEvent, AudioManager.USE_DEFAULT_STREAM_TYPE, false); +                        return; +                    }                      switch (keyEvent.getAction()) {                          case KeyEvent.ACTION_DOWN: {                              int direction = 0; diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java index 6c9aa4b0d849..67a1ccd17835 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java @@ -109,21 +109,28 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {          mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); -        mBluetoothRouteController = BluetoothRouteController.createInstance(context, (routes) -> { -            publishProviderState(); -            if (updateSessionInfosIfNeeded()) { -                notifySessionInfoUpdated(); -            } -        }); - -        mDeviceRouteController = DeviceRouteController.createInstance(context, (deviceRoute) -> { -            mHandler.post(() -> { -                publishProviderState(); -                if (updateSessionInfosIfNeeded()) { -                    notifySessionInfoUpdated(); -                } -            }); -        }); +        mBluetoothRouteController = +                BluetoothRouteController.createInstance( +                        context, +                        () -> { +                            publishProviderState(); +                            if (updateSessionInfosIfNeeded()) { +                                notifySessionInfoUpdated(); +                            } +                        }); + +        mDeviceRouteController = +                DeviceRouteController.createInstance( +                        context, +                        () -> { +                            mHandler.post( +                                    () -> { +                                        publishProviderState(); +                                        if (updateSessionInfosIfNeeded()) { +                                            notifySessionInfoUpdated(); +                                        } +                                    }); +                        });          mAudioManager.addOnDevicesForAttributesChangedListener(                  AudioAttributesUtils.ATTRIBUTES_MEDIA, mContext.getMainExecutor(), diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java index 13d166294603..8cbc368467bb 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java @@ -76,6 +76,7 @@ import com.android.internal.annotations.GuardedBy;  import com.android.internal.annotations.VisibleForTesting;  import com.android.internal.util.ArrayUtils;  import com.android.internal.util.DumpUtils; +import com.android.internal.util.FrameworkStatsLog;  import com.android.server.LocalServices;  import com.android.server.SystemService;  import com.android.server.Watchdog; @@ -134,6 +135,7 @@ public final class MediaProjectionManagerService extends SystemService      private final MediaRouter mMediaRouter;      private final MediaRouterCallback mMediaRouterCallback; +    private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;      private MediaRouter.RouteInfo mMediaRouteInfo;      @GuardedBy("mLock") @@ -160,6 +162,7 @@ public final class MediaProjectionManagerService extends SystemService          mWmInternal = LocalServices.getService(WindowManagerInternal.class);          mMediaRouter = (MediaRouter) mContext.getSystemService(Context.MEDIA_ROUTER_SERVICE);          mMediaRouterCallback = new MediaRouterCallback(); +        mMediaProjectionMetricsLogger = injector.mediaProjectionMetricsLogger();          Watchdog.getInstance().addMonitor(this);      } @@ -193,6 +196,10 @@ public final class MediaProjectionManagerService extends SystemService          Looper createCallbackLooper() {              return Looper.getMainLooper();          } + +        MediaProjectionMetricsLogger mediaProjectionMetricsLogger() { +            return MediaProjectionMetricsLogger.getInstance(); +        }      }      @Override @@ -372,6 +379,10 @@ public final class MediaProjectionManagerService extends SystemService              if (mProjectionGrant != null) {                  // Cache the session details.                  mProjectionGrant.mSession = incomingSession; +                mMediaProjectionMetricsLogger.notifyProjectionStateChange( +                        mProjectionGrant.uid, +                        FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS, +                        FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);                  dispatchSessionSet(mProjectionGrant.getProjectionInfo(), incomingSession);              }              return true; @@ -818,6 +829,19 @@ public final class MediaProjectionManagerService extends SystemService          }          @Override // Binder call +        @EnforcePermission(MANAGE_MEDIA_PROJECTION) +        public void notifyPermissionRequestStateChange(int hostUid, int state, +                int sessionCreationSource) { +            notifyPermissionRequestStateChange_enforcePermission(); +            final long token = Binder.clearCallingIdentity(); +            try { +                mMediaProjectionMetricsLogger.notifyProjectionStateChange(hostUid, state, sessionCreationSource); +            } finally { +                Binder.restoreCallingIdentity(token); +            } +        } + +        @Override // Binder call          public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {              if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;              final long token = Binder.clearCallingIdentity(); diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java b/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java new file mode 100644 index 000000000000..f18ecad09c42 --- /dev/null +++ b/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.media.projection; + + +import com.android.internal.util.FrameworkStatsLog; + +/** + * Class for emitting logs describing a MediaProjection session. + */ +public class MediaProjectionMetricsLogger { +    private static MediaProjectionMetricsLogger sSingleton = null; + +    public static MediaProjectionMetricsLogger getInstance() { +        if (sSingleton == null) { +            sSingleton = new MediaProjectionMetricsLogger(); +        } +        return sSingleton; +    } + +    void notifyProjectionStateChange(int hostUid, int state, int sessionCreationSource) { +        write(hostUid, state, sessionCreationSource); +    } + +    private void write(int hostUid, int state, int sessionCreationSource) { +        FrameworkStatsLog.write( +                /* code */ FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED, +                /* session_id */ 123, +                /* state */ state, +                /* previous_state */ FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN, +                /* host_uid */ hostUid, +                /* target_uid */ -1, +                /* time_since_last_active */ 0, +                /* creation_source */ sessionCreationSource); +    } +} diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionSessionIdGenerator.java b/services/core/java/com/android/server/media/projection/MediaProjectionSessionIdGenerator.java new file mode 100644 index 000000000000..ff70cb35f9dc --- /dev/null +++ b/services/core/java/com/android/server/media/projection/MediaProjectionSessionIdGenerator.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.media.projection; + +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Environment; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; + +import java.io.File; + +public class MediaProjectionSessionIdGenerator { + +    private static final String PREFERENCES_FILE_NAME = "media_projection_session_id"; +    private static final String SESSION_ID_PREF_KEY = "media_projection_session_id_key"; +    private static final int SESSION_ID_DEFAULT_VALUE = 0; + +    private static final Object sInstanceLock = new Object(); + +    @GuardedBy("sInstanceLock") +    private static MediaProjectionSessionIdGenerator sInstance; + +    private final Object mSessionIdLock = new Object(); + +    @GuardedBy("mSessionIdLock") +    private final SharedPreferences mSharedPreferences; + +    /** Creates or returns an existing instance of {@link MediaProjectionSessionIdGenerator}. */ +    public static MediaProjectionSessionIdGenerator getInstance(Context context) { +        synchronized (sInstanceLock) { +            if (sInstance == null) { +                File preferencesFile = +                        new File(Environment.getDataSystemDirectory(), PREFERENCES_FILE_NAME); +                SharedPreferences preferences = +                        context.getSharedPreferences(preferencesFile, Context.MODE_PRIVATE); +                sInstance = new MediaProjectionSessionIdGenerator(preferences); +            } +            return sInstance; +        } +    } + +    @VisibleForTesting +    public MediaProjectionSessionIdGenerator(SharedPreferences sharedPreferences) { +        this.mSharedPreferences = sharedPreferences; +    } + +    /** Returns the current session ID. This value is persisted across reboots. */ +    public int getCurrentSessionId() { +        synchronized (mSessionIdLock) { +            return getCurrentSessionIdInternal(); +        } +    } + +    /** +     * Creates and returns a new session ID. This value will be persisted as the new current session +     * ID, and will be persisted across reboots. +     */ +    public int createAndGetNewSessionId() { +        synchronized (mSessionIdLock) { +            int newSessionId = getCurrentSessionId() + 1; +            setSessionIdInternal(newSessionId); +            return newSessionId; +        } +    } + +    @GuardedBy("mSessionIdLock") +    private void setSessionIdInternal(int value) { +        mSharedPreferences.edit().putInt(SESSION_ID_PREF_KEY, value).apply(); +    } + +    @GuardedBy("mSessionIdLock") +    private int getCurrentSessionIdInternal() { +        return mSharedPreferences.getInt(SESSION_ID_PREF_KEY, SESSION_ID_DEFAULT_VALUE); +    } +} diff --git a/services/core/java/com/android/server/oemlock/OemLockService.java b/services/core/java/com/android/server/oemlock/OemLockService.java index 4c6110b99efd..d00268899a07 100644 --- a/services/core/java/com/android/server/oemlock/OemLockService.java +++ b/services/core/java/com/android/server/oemlock/OemLockService.java @@ -35,8 +35,8 @@ import android.service.oemlock.IOemLockService;  import android.util.Slog;  import com.android.server.LocalServices; -import com.android.server.PersistentDataBlockManagerInternal;  import com.android.server.SystemService; +import com.android.server.pdb.PersistentDataBlockManagerInternal;  import com.android.server.pm.UserManagerInternal;  import com.android.server.pm.UserManagerInternal.UserRestrictionsListener;  import com.android.server.pm.UserRestrictionsUtils; diff --git a/services/core/java/com/android/server/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java index c7c8136ff0da..79c13461fc6b 100644 --- a/services/core/java/com/android/server/os/NativeTombstoneManager.java +++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java @@ -569,6 +569,10 @@ public final class NativeTombstoneManager {          @Override          public void onEvent(int event, @Nullable String path) { +            if (path == null) { +                Slog.w(TAG, "path is null at TombstoneWatcher.onEvent()"); +                return; +            }              mHandler.post(() -> {                  // Ignore .tmp files.                  if (path.endsWith(".tmp")) { diff --git a/services/core/java/com/android/server/pdb/OWNERS b/services/core/java/com/android/server/pdb/OWNERS new file mode 100644 index 000000000000..6f322eea0f2e --- /dev/null +++ b/services/core/java/com/android/server/pdb/OWNERS @@ -0,0 +1,2 @@ +victorhsieh@google.com +swillden@google.com diff --git a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java b/services/core/java/com/android/server/pdb/PersistentDataBlockManagerInternal.java index 21fa9f9a9401..66ad7169d6ec 100644 --- a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java +++ b/services/core/java/com/android/server/pdb/PersistentDataBlockManagerInternal.java @@ -14,7 +14,7 @@   * limitations under the License.   */ -package com.android.server; +package com.android.server.pdb;  /**   * Internal interface for storing and retrieving persistent data. diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java index 754a7ede8006..b006ac862533 100644 --- a/services/core/java/com/android/server/PersistentDataBlockService.java +++ b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java @@ -14,7 +14,7 @@   * limitations under the License.   */ -package com.android.server; +package com.android.server.pdb;  import static com.android.internal.util.Preconditions.checkArgument; @@ -37,6 +37,9 @@ import com.android.internal.R;  import com.android.internal.annotations.GuardedBy;  import com.android.internal.util.DumpUtils;  import com.android.server.pm.UserManagerInternal; +import com.android.server.LocalServices; +import com.android.server.SystemServerInitThreadPool; +import com.android.server.SystemService;  import libcore.io.IoUtils; diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index 7db7bf538c37..30017be96085 100644 --- a/services/core/java/com/android/server/pm/ComputerEngine.java +++ b/services/core/java/com/android/server/pm/ComputerEngine.java @@ -505,6 +505,10 @@ public class ComputerEngine implements Computer {              int filterCallingUid, int userId, boolean resolveForStart,              boolean allowDynamicSplits) {          if (!mUserManager.exists(userId)) return Collections.emptyList(); + +        // Allow to match activities of quarantined packages. +        flags |= PackageManager.MATCH_QUARANTINED_COMPONENTS; +          final String instantAppPkgName = getInstantAppPackageName(filterCallingUid);          enforceCrossUserPermission(Binder.getCallingUid(), userId,                  false /* requireFullPermission */, false /* checkShell */, @@ -647,11 +651,6 @@ public class ComputerEngine implements Computer {          flags = updateFlagsForResolve(flags, userId, callingUid, includeInstantApps,                  false /* isImplicitImageCaptureIntentAndNotSetByDpc */); -        // Only if the query is coming from the system process, -        // it should be allowed to match quarantined components -        if (callingUid != Process.SYSTEM_UID) { -            flags |= PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS; -        }          Intent originalIntent = null;          ComponentName comp = intent.getComponent();          if (comp == null) { @@ -4047,9 +4046,6 @@ public class ComputerEngine implements Computer {          flags = updateFlagsForComponent(flags, userId);          enforceCrossUserPermission(callingUid, userId, false /* requireFullPermission */,                  false /* checkShell */, "get provider info"); -        if (callingUid != Process.SYSTEM_UID) { -            flags |= PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS; -        }          ParsedProvider p = mComponentResolver.getProvider(component);          if (DEBUG_PACKAGE_INFO) Log.v(                  TAG, "getProviderInfo " + component + ": " + p); @@ -4679,9 +4675,6 @@ public class ComputerEngine implements Computer {              int callingUid) {          if (!mUserManager.exists(userId)) return null;          flags = updateFlagsForComponent(flags, userId); -        if (callingUid != Process.SYSTEM_UID) { -            flags |= PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS; -        }          final ProviderInfo providerInfo = mComponentResolver.queryProvider(this, name, flags,                  userId);          boolean checkedGrants = false; @@ -4794,13 +4787,6 @@ public class ComputerEngine implements Computer {                  false /* checkShell */, "queryContentProviders");          if (!mUserManager.exists(userId)) return ParceledListSlice.emptyList();          flags = updateFlagsForComponent(flags, userId); - -        // Only if the service query is coming from the system process, -        // it should be allowed to match quarantined components -        if (callingUid != Process.SYSTEM_UID) { -            flags |= PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS; -        } -          ArrayList<ProviderInfo> finalList = null;          final List<ProviderInfo> matchList = mComponentResolver.queryProviders(this, processName,                  metaDataKey, uid, flags, userId); diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index edb45aa0ffb4..29678181679b 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -1269,10 +1269,9 @@ final class InstallPackageHelper {                      replace = true;                      if (DEBUG_INSTALL) Slog.d(TAG, "Replace existing package: " + pkgName);                  } -                final AndroidPackage oldPackage = mPm.mPackages.get(pkgName); -                if (replace && oldPackage != null) { +                if (replace) {                      // Prevent apps opting out from runtime permissions -                    final int oldTargetSdk = oldPackage.getTargetSdkVersion(); +                    final int oldTargetSdk = ps.getTargetSdkVersion();                      final int newTargetSdk = parsedPackage.getTargetSdkVersion();                      if (oldTargetSdk > Build.VERSION_CODES.LOLLIPOP_MR1                              && newTargetSdk <= Build.VERSION_CODES.LOLLIPOP_MR1) { @@ -1284,10 +1283,10 @@ final class InstallPackageHelper {                                          + " target SDK " + oldTargetSdk + " does.");                      }                      // Prevent persistent apps from being updated -                    if (oldPackage.isPersistent() +                    if (ps.isPersistent()                              && ((installFlags & PackageManager.INSTALL_STAGED) == 0)) {                          throw new PrepareFailure(PackageManager.INSTALL_FAILED_INVALID_APK, -                                "Package " + oldPackage.getPackageName() + " is a persistent app. " +                                "Package " + pkgName + " is a persistent app. "                                          + "Persistent apps are not updateable.");                      }                  } @@ -1668,7 +1667,7 @@ final class InstallPackageHelper {                      }                      // don't allow a system upgrade unless the upgrade hash matches -                    if (oldPackage != null && oldPackage.getRestrictUpdateHash() != null +                    if (oldPackageState.getRestrictUpdateHash() != null                              && oldPackageState.isSystem()) {                          final byte[] digestBytes;                          try { @@ -1684,12 +1683,13 @@ final class InstallPackageHelper {                              throw new PrepareFailure(INSTALL_FAILED_INVALID_APK,                                      "Could not compute hash: " + pkgName11);                          } -                        if (!Arrays.equals(oldPackage.getRestrictUpdateHash(), digestBytes)) { +                        if (!Arrays.equals(oldPackageState.getRestrictUpdateHash(), digestBytes)) {                              throw new PrepareFailure(INSTALL_FAILED_INVALID_APK,                                      "New package fails restrict-update check: " + pkgName11);                          }                          // retain upgrade restriction -                        parsedPackage.setRestrictUpdateHash(oldPackage.getRestrictUpdateHash()); +                        parsedPackage.setRestrictUpdateHash( +                                oldPackageState.getRestrictUpdateHash());                      }                      if (oldPackage != null) { diff --git a/services/core/java/com/android/server/pm/MovePackageHelper.java b/services/core/java/com/android/server/pm/MovePackageHelper.java index 148e0df7cf64..9ad8318c2b5f 100644 --- a/services/core/java/com/android/server/pm/MovePackageHelper.java +++ b/services/core/java/com/android/server/pm/MovePackageHelper.java @@ -89,6 +89,21 @@ public final class MovePackageHelper {          if (packageState == null || packageState.getPkg() == null) {              throw new PackageManagerException(MOVE_FAILED_DOESNT_EXIST, "Missing package");          } +        final int[] installedUserIds = PackageStateUtils.queryInstalledUsers(packageState, +                mPm.mUserManager.getUserIds(), true); +        final UserHandle userForMove; +        if (installedUserIds.length > 0) { +            userForMove = UserHandle.of(installedUserIds[0]); +        } else { +            throw new PackageManagerException(MOVE_FAILED_DOESNT_EXIST, +                    "Package is not installed for any user"); +        } +        for (int userId : installedUserIds) { +            if (snapshot.shouldFilterApplicationIncludingUninstalled(packageState, callingUid, +                    userId)) { +                throw new PackageManagerException(MOVE_FAILED_DOESNT_EXIST, "Missing package"); +            } +        }          final AndroidPackage pkg = packageState.getPkg();          if (packageState.isSystem()) {              throw new PackageManagerException(MOVE_FAILED_SYSTEM_PACKAGE, @@ -134,8 +149,6 @@ public final class MovePackageHelper {          final String label = String.valueOf(pm.getApplicationLabel(                  AndroidPackageUtils.generateAppInfoWithoutState(pkg)));          final int targetSdkVersion = pkg.getTargetSdkVersion(); -        final int[] installedUserIds = PackageStateUtils.queryInstalledUsers(packageState, -                mPm.mUserManager.getUserIds(), true);          final String fromCodePath;          if (codeFile.getParentFile().getName().startsWith(                  PackageManagerService.RANDOM_DIR_PREFIX)) { @@ -303,8 +316,8 @@ public final class MovePackageHelper {          final PackageLite lite = ret.isSuccess() ? ret.getResult() : null;          final InstallingSession installingSession = new InstallingSession(origin, move,                  installObserver, installFlags, /* developmentInstallFlags= */ 0, installSource, -                volumeUuid, user, packageAbiOverride,  PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED, -                lite, mPm); +                volumeUuid, userForMove, packageAbiOverride, +                PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED, lite, mPm);          installingSession.movePackage();      } diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java index f59188e9fd93..0fb1f7a0780c 100644 --- a/services/core/java/com/android/server/pm/PackageArchiver.java +++ b/services/core/java/com/android/server/pm/PackageArchiver.java @@ -196,8 +196,12 @@ public class PackageArchiver {              for (int i = 0, size = mainActivities.length; i < size; ++i) {                  var mainActivity = mainActivities[i];                  Path iconPath = storeIconForParcel(packageName, mainActivity, userId, i); -                ArchiveActivityInfo activityInfo = new ArchiveActivityInfo( -                        mainActivity.title, iconPath, null); +                ArchiveActivityInfo activityInfo = +                        new ArchiveActivityInfo( +                                mainActivity.title, +                                mainActivity.originalComponentName, +                                iconPath, +                                null);                  archiveActivityInfos.add(activityInfo);              } @@ -215,8 +219,12 @@ public class PackageArchiver {          for (int i = 0, size = mainActivities.size(); i < size; i++) {              LauncherActivityInfo mainActivity = mainActivities.get(i);              Path iconPath = storeIcon(packageName, mainActivity, userId, i); -            ArchiveActivityInfo activityInfo = new ArchiveActivityInfo( -                    mainActivity.getLabel().toString(), iconPath, null); +            ArchiveActivityInfo activityInfo = +                    new ArchiveActivityInfo( +                            mainActivity.getLabel().toString(), +                            mainActivity.getComponentName(), +                            iconPath, +                            null);              archiveActivityInfos.add(activityInfo);          } @@ -593,6 +601,7 @@ public class PackageArchiver {              }              var archivedActivity = new ArchivedActivityParcel();              archivedActivity.title = info.getTitle(); +            archivedActivity.originalComponentName = info.getOriginalComponentName();              archivedActivity.iconBitmap = bytesFromBitmapFile(info.getIconBitmap());              archivedActivity.monochromeIconBitmap = bytesFromBitmapFile(                      info.getMonochromeIconBitmap()); @@ -624,6 +633,7 @@ public class PackageArchiver {              }              var archivedActivity = new ArchivedActivityParcel();              archivedActivity.title = info.getLabel().toString(); +            archivedActivity.originalComponentName = info.getComponentName();              archivedActivity.iconBitmap =                      info.getActivityInfo().getIconResource() == 0 ? null : bytesFromBitmap(                              drawableToBitmap(info.getIcon(/* density= */ 0))); diff --git a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java index 651845e71924..e7499680b9a2 100644 --- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java +++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java @@ -747,7 +747,7 @@ abstract class PackageManagerInternalBase extends PackageManagerInternal {      @Override      public void notifyComponentUsed(@NonNull String packageName, @UserIdInt int userId, -            @NonNull String recentCallingPackage, @NonNull String debugInfo) { +            @Nullable String recentCallingPackage, @NonNull String debugInfo) {          mService.notifyComponentUsed(snapshot(), packageName, userId,                  recentCallingPackage, debugInfo);      } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 68aa93d28330..839b6998bd8b 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -36,6 +36,7 @@ import static android.os.storage.StorageManager.FLAG_STORAGE_CE;  import static android.os.storage.StorageManager.FLAG_STORAGE_DE;  import static android.os.storage.StorageManager.FLAG_STORAGE_EXTERNAL;  import static android.provider.DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE; +import static android.util.FeatureFlagUtils.SETTINGS_TREAT_PAUSE_AS_QUARANTINE;  import static com.android.internal.annotations.VisibleForTesting.Visibility;  import static com.android.internal.util.FrameworkStatsLog.BOOT_TIME_EVENT_DURATION__EVENT__OTA_PACKAGE_MANAGER_INIT_TIME; @@ -164,6 +165,7 @@ import android.util.ArraySet;  import android.util.DisplayMetrics;  import android.util.EventLog;  import android.util.ExceptionUtils; +import android.util.FeatureFlagUtils;  import android.util.Log;  import android.util.Pair;  import android.util.Slog; @@ -1469,8 +1471,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService              archPkg.versionCodeMajor = (int) (longVersionCode >> 32);              archPkg.versionCode = (int) longVersionCode; -            // TODO(b/297916136): extract target sdk version. -            archPkg.targetSdkVersion = MIN_INSTALLABLE_TARGET_SDK; +            archPkg.targetSdkVersion = ps.getTargetSdkVersion();              // These get translated in flags important for user data management.              archPkg.defaultToDeviceProtectedStorage = String.valueOf( @@ -4577,7 +4578,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService      }      void notifyComponentUsed(@NonNull Computer snapshot, @NonNull String packageName, -            @UserIdInt int userId, @NonNull String recentCallingPackage, +            @UserIdInt int userId, @Nullable String recentCallingPackage,              @NonNull String debugInfo) {          synchronized (mLock) {              final PackageSetting pkgSetting = mSettings.getPackageLPr(packageName); @@ -6134,8 +6135,16 @@ public class PackageManagerService implements PackageSender, TestUtilityService              final Computer snapshot = snapshotComputer();              enforceCanSetPackagesSuspendedAsUser(snapshot, callingPackage, callingUid, userId,                      "setPackagesSuspendedAsUser"); -            boolean quarantined = ((flags & PackageManager.FLAG_SUSPEND_QUARANTINED) != 0) -                    && Flags.quarantinedEnabled(); +            boolean quarantined = false; +            if (Flags.quarantinedEnabled()) { +                if ((flags & PackageManager.FLAG_SUSPEND_QUARANTINED) != 0) { +                    quarantined = true; +                } else if (FeatureFlagUtils.isEnabled(mContext, +                        SETTINGS_TREAT_PAUSE_AS_QUARANTINE)) { +                    final String wellbeingPkg = mContext.getString(R.string.config_systemWellbeing); +                    quarantined = callingPackage.equals(wellbeingPkg); +                } +            }              return mSuspendPackageHelper.setPackagesSuspended(snapshot, packageNames, suspended,                      appExtras, launcherExtras, dialogInfo, callingPackage, userId, callingUid,                      false /* forQuietMode */, quarantined); diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java index 2e6006465bd9..3ca933a66656 100644 --- a/services/core/java/com/android/server/pm/PackageSetting.java +++ b/services/core/java/com/android/server/pm/PackageSetting.java @@ -99,6 +99,7 @@ public class PackageSetting extends SettingBase implements PackageStateInternal          private static final int DEFAULT_TO_DEVICE_PROTECTED_STORAGE = 1 << 1;          private static final int UPDATE_AVAILABLE = 1 << 2;          private static final int FORCE_QUERYABLE_OVERRIDE = 1 << 3; +        private static final int PERSISTENT = 1 << 4;      }      private int mBooleans; @@ -217,6 +218,11 @@ public class PackageSetting extends SettingBase implements PackageStateInternal      @Nullable      private String mAppMetadataFilePath; +    private int mTargetSdkVersion; + +    @Nullable +    private byte[] mRestrictUpdateHash; +      /**       * Snapshot support.       */ @@ -535,6 +541,24 @@ public class PackageSetting extends SettingBase implements PackageStateInternal          onChanged();      } +    public PackageSetting setIsPersistent(boolean isPersistent) { +        setBoolean(Booleans.PERSISTENT, isPersistent); +        onChanged(); +        return this; +    } + +    public PackageSetting setTargetSdkVersion(int targetSdkVersion) { +        mTargetSdkVersion = targetSdkVersion; +        onChanged(); +        return this; +    } + +    public PackageSetting setRestrictUpdateHash(byte[] restrictUpdateHash) { +        mRestrictUpdateHash = restrictUpdateHash; +        onChanged(); +        return this; +    } +      @Override      public int getSharedUserAppId() {          return mSharedUserAppId; @@ -701,6 +725,9 @@ public class PackageSetting extends SettingBase implements PackageStateInternal          categoryOverride = other.categoryOverride;          mDomainSetId = other.mDomainSetId;          mAppMetadataFilePath = other.mAppMetadataFilePath; +        mTargetSdkVersion = other.mTargetSdkVersion; +        mRestrictUpdateHash = other.mRestrictUpdateHash == null +                ? null : other.mRestrictUpdateHash.clone();          usesSdkLibraries = other.usesSdkLibraries != null                  ? Arrays.copyOf(other.usesSdkLibraries, @@ -1201,6 +1228,9 @@ public class PackageSetting extends SettingBase implements PackageStateInternal              long activityInfoToken = proto.start(                      PackageProto.UserInfoProto.ArchiveState.ACTIVITY_INFOS);              proto.write(ArchiveActivityInfo.TITLE, activityInfo.getTitle()); +            proto.write( +                    ArchiveActivityInfo.ORIGINAL_COMPONENT_NAME, +                    activityInfo.getOriginalComponentName().flattenToString());              if (activityInfo.getIconBitmap() != null) {                  proto.write(ArchiveActivityInfo.ICON_BITMAP_PATH,                          activityInfo.getIconBitmap().toAbsolutePath().toString()); @@ -1572,6 +1602,11 @@ public class PackageSetting extends SettingBase implements PackageStateInternal          return getBoolean(Booleans.DEFAULT_TO_DEVICE_PROTECTED_STORAGE);      } +    @Override +    public boolean isPersistent() { +        return getBoolean(Booleans.PERSISTENT); +    } +      // Code below generated by codegen v1.0.23. @@ -1714,11 +1749,21 @@ public class PackageSetting extends SettingBase implements PackageStateInternal          return mAppMetadataFilePath;      } +    @DataClass.Generated.Member +    public int getTargetSdkVersion() { +        return mTargetSdkVersion; +    } + +    @DataClass.Generated.Member +    public @Nullable byte[] getRestrictUpdateHash() { +        return mRestrictUpdateHash; +    } +      @DataClass.Generated( -            time = 1694196905013L, +            time = 1696979728639L,              codegenVersion = "1.0.23",              sourceFile = "frameworks/base/services/core/java/com/android/server/pm/PackageSetting.java", -            inputSignatures = "private  int mBooleans\nprivate  int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate  int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate  float mLoadingProgress\nprivate  long mLoadingCompletedTime\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate  long mLastModifiedTime\nprivate  long lastUpdateTime\nprivate  long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate  int categoryOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate @android.annotation.Nullable java.lang.String mAppMetadataFilePath\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\nprivate  com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic  com.android.server.pm.PackageSetting snapshot()\npublic  void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic  com.android.server.pm.PackageSetting setAppId(int)\npublic  com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic  com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic  com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic  com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic  com.android.server.pm.PackageSetting setInstallerPackage(java.lang.String,int)\npublic  com.android.server.pm.PackageSetting setUpdateOwnerPackage(java.lang.String)\npublic  com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n  com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic  com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic  com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic  com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic  com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic  com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic  boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic  com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic  com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic  com.android.server.pm.PackageSetting setDefaultToDeviceProtectedStorage(boolean)\npublic @java.lang.Override boolean isExternalStorage()\npublic  com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic  void setSharedUserAppId(int)\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprotected  void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  void updateFrom(com.android.server.pm.PackageSetting)\n  com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic  com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic  boolean isPrivileged()\npublic  boolean isOem()\npublic  boolean isVendor()\npublic  boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic  boolean isSystemExt()\npublic  boolean isOdm()\npublic  boolean isSystem()\npublic  android.content.pm.SigningDetails getSigningDetails()\npublic  com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic  void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic  com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n  void setEnabled(int,int,java.lang.String)\n  int getEnabled(int)\n  void setInstalled(boolean,int)\n  boolean getInstalled(int)\n  int getInstallReason(int)\n  void setInstallReason(int,int)\n  int getUninstallReason(int)\n  void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n  boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n  boolean isInstalledOrHasDataOnAnyOtherUser(int[],int)\n  int[] queryInstalledUsers(int[],boolean)\n  long getCeDataInode(int)\n  void setCeDataInode(long,int)\n  void setDeDataInode(long,int)\n  boolean getStopped(int)\n  void setStopped(boolean,int)\n  boolean getNotLaunched(int)\n  void setNotLaunched(boolean,int)\n  boolean getHidden(int)\n  void setHidden(boolean,int)\n  int getDistractionFlags(int)\n  void setDistractionFlags(int,int)\npublic  boolean getInstantApp(int)\n  void setInstantApp(boolean,int)\n  boolean getVirtualPreload(int)\n  void setVirtualPreload(boolean,int)\n  void setUserState(int,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long,int,com.android.server.pm.pkg.ArchiveState)\n  void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n  void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n  void addDisabledComponent(java.lang.String,int)\n  void addEnabledComponent(java.lang.String,int)\n  boolean enableComponentLPw(java.lang.String,int)\n  boolean disableComponentLPw(java.lang.String,int)\n  boolean restoreComponentLPw(java.lang.String,int)\n  int getCurrentEnabledStateLPr(java.lang.String,int)\n  void removeUser(int)\npublic  int[] getNotInstalledUserIds()\n  void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected  void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\nprivate static  void writeArchiveState(android.util.proto.ProtoOutputStream,com.android.server.pm.pkg.ArchiveState)\n  com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic  void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic  boolean isIncremental()\npublic  boolean isLoading()\npublic  com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic  com.android.server.pm.PackageSetting setLoadingCompletedTime(long)\npublic  com.android.server.pm.PackageSetting setAppMetadataFilePath(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getApexModuleName()\npublic  com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic  com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic  com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic  com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic  com.android.server.pm.PackageSetting setApexModuleName(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic  com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\npublic @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy @java.lang.Override int getHiddenApiEnforcementPolicy()\npublic @java.lang.Override boolean isApex()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isDefaultToDeviceProtectedStorage()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\nprivate static final  int INSTALL_PERMISSION_FIXED\nprivate static final  int DEFAULT_TO_DEVICE_PROTECTED_STORAGE\nprivate static final  int UPDATE_AVAILABLE\nprivate static final  int FORCE_QUERYABLE_OVERRIDE\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)") +            inputSignatures = "private  int mBooleans\nprivate  int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate  int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate  float mLoadingProgress\nprivate  long mLoadingCompletedTime\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate  long mLastModifiedTime\nprivate  long lastUpdateTime\nprivate  long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate  int categoryOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate @android.annotation.Nullable java.lang.String mAppMetadataFilePath\nprivate  int mTargetSdkVersion\nprivate @android.annotation.Nullable byte[] mRestrictUpdateHash\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\nprivate  com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic  com.android.server.pm.PackageSetting snapshot()\npublic  void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic  com.android.server.pm.PackageSetting setAppId(int)\npublic  com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic  com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic  com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic  com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic  com.android.server.pm.PackageSetting setInstallerPackage(java.lang.String,int)\npublic  com.android.server.pm.PackageSetting setUpdateOwnerPackage(java.lang.String)\npublic  com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n  com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic  com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic  com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic  com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic  com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic  com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic  boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic  com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic  com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic  com.android.server.pm.PackageSetting setDefaultToDeviceProtectedStorage(boolean)\npublic @java.lang.Override boolean isExternalStorage()\npublic  com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic  void setSharedUserAppId(int)\npublic  com.android.server.pm.PackageSetting setIsPersistent(boolean)\npublic  com.android.server.pm.PackageSetting setTargetSdkVersion(int)\npublic  com.android.server.pm.PackageSetting setRestrictUpdateHash(byte[])\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprotected  void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  void updateFrom(com.android.server.pm.PackageSetting)\n  com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic  com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic  boolean isPrivileged()\npublic  boolean isOem()\npublic  boolean isVendor()\npublic  boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic  boolean isSystemExt()\npublic  boolean isOdm()\npublic  boolean isSystem()\npublic  boolean isRequestLegacyExternalStorage()\npublic  boolean isUserDataFragile()\npublic  android.content.pm.SigningDetails getSigningDetails()\npublic  com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic  void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic  com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n  void setEnabled(int,int,java.lang.String)\n  int getEnabled(int)\n  void setInstalled(boolean,int)\n  boolean getInstalled(int)\n  int getInstallReason(int)\n  void setInstallReason(int,int)\n  int getUninstallReason(int)\n  void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n  boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n  boolean isInstalledOrHasDataOnAnyOtherUser(int[],int)\n  int[] queryInstalledUsers(int[],boolean)\n  int[] queryUsersInstalledOrHasData(int[])\n  long getCeDataInode(int)\n  long getDeDataInode(int)\n  void setCeDataInode(long,int)\n  void setDeDataInode(long,int)\n  boolean getStopped(int)\n  void setStopped(boolean,int)\n  boolean getNotLaunched(int)\n  void setNotLaunched(boolean,int)\n  boolean getHidden(int)\n  void setHidden(boolean,int)\n  int getDistractionFlags(int)\n  void setDistractionFlags(int,int)\npublic  boolean getInstantApp(int)\n  void setInstantApp(boolean,int)\n  boolean getVirtualPreload(int)\n  void setVirtualPreload(boolean,int)\n  void setUserState(int,long,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long,int,com.android.server.pm.pkg.ArchiveState)\n  void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n  void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n  void addDisabledComponent(java.lang.String,int)\n  void addEnabledComponent(java.lang.String,int)\n  boolean enableComponentLPw(java.lang.String,int)\n  boolean disableComponentLPw(java.lang.String,int)\n  boolean restoreComponentLPw(java.lang.String,int)\n  int getCurrentEnabledStateLPr(java.lang.String,int)\n  void removeUser(int)\npublic  int[] getNotInstalledUserIds()\n  void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected  void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\nprivate static  void writeArchiveState(android.util.proto.ProtoOutputStream,com.android.server.pm.pkg.ArchiveState)\n  com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic  void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic  boolean isIncremental()\npublic  boolean isLoading()\npublic  com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic  com.android.server.pm.PackageSetting setLoadingCompletedTime(long)\npublic  com.android.server.pm.PackageSetting setAppMetadataFilePath(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getApexModuleName()\npublic  com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic  com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic  com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic  com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic  com.android.server.pm.PackageSetting setApexModuleName(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic  com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\npublic @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy @java.lang.Override int getHiddenApiEnforcementPolicy()\npublic @java.lang.Override boolean isApex()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isDefaultToDeviceProtectedStorage()\npublic @java.lang.Override boolean isPersistent()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\nprivate static final  int INSTALL_PERMISSION_FIXED\nprivate static final  int DEFAULT_TO_DEVICE_PROTECTED_STORAGE\nprivate static final  int UPDATE_AVAILABLE\nprivate static final  int FORCE_QUERYABLE_OVERRIDE\nprivate static final  int PERSISTENT\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")      @Deprecated      private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/ResolveIntentHelper.java b/services/core/java/com/android/server/pm/ResolveIntentHelper.java index da14397b9c92..203e1de61f2f 100644 --- a/services/core/java/com/android/server/pm/ResolveIntentHelper.java +++ b/services/core/java/com/android/server/pm/ResolveIntentHelper.java @@ -517,12 +517,6 @@ final class ResolveIntentHelper {          if (!mUserManager.exists(userId)) return Collections.emptyList();          final int callingUid = Binder.getCallingUid(); -        // Only if the service query is coming from the system process, -        // it should be allowed to match quarantined components -        if (callingUid != Process.SYSTEM_UID) { -            flags |= PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS; -        } -          final String instantAppPkgName = computer.getInstantAppPackageName(callingUid);          flags = computer.updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/,                  false /* isImplicitImageCaptureIntentAndNotSetByDpc */); diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java index 0cac790fd910..8d8acfd421de 100644 --- a/services/core/java/com/android/server/pm/ScanPackageUtils.java +++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java @@ -220,7 +220,8 @@ final class ScanPackageUtils {                      UserManagerService.getInstance(), usesSdkLibraries,                      parsedPackage.getUsesSdkLibrariesVersionsMajor(), usesStaticLibraries,                      parsedPackage.getUsesStaticLibrariesVersions(), parsedPackage.getMimeGroups(), -                    newDomainSetId); +                    newDomainSetId, parsedPackage.isPersistent(), +                    parsedPackage.getTargetSdkVersion(), parsedPackage.getRestrictUpdateHash());          } else {              // make a deep copy to avoid modifying any existing system state.              pkgSetting = new PackageSetting(pkgSetting); @@ -240,7 +241,8 @@ final class ScanPackageUtils {                      UserManagerService.getInstance(),                      usesSdkLibraries, parsedPackage.getUsesSdkLibrariesVersionsMajor(),                      usesStaticLibraries, parsedPackage.getUsesStaticLibrariesVersions(), -                    parsedPackage.getMimeGroups(), newDomainSetId); +                    parsedPackage.getMimeGroups(), newDomainSetId, parsedPackage.isPersistent(), +                    parsedPackage.getTargetSdkVersion(), parsedPackage.getRestrictUpdateHash());          }          if (createNewPackage && originalPkgSetting != null) { diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 6e3b538c4849..e726d91c30a0 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -368,6 +368,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile      private static final String ATTR_VALUE = "value";      private static final String ATTR_FIRST_INSTALL_TIME = "first-install-time";      private static final String ATTR_ARCHIVE_ACTIVITY_TITLE = "activity-title"; +    private static final String ATTR_ARCHIVE_ORIGINAL_COMPONENT_NAME = "original-component-name";      private static final String ATTR_ARCHIVE_INSTALLER_TITLE = "installer-title";      private static final String ATTR_ARCHIVE_ICON_PATH = "icon-path";      private static final String ATTR_ARCHIVE_MONOCHROME_ICON_PATH = "monochrome-icon-path"; @@ -1060,7 +1061,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile              boolean virtualPreload, boolean isStoppedSystemApp, UserManagerService userManager,              String[] usesSdkLibraries, long[] usesSdkLibrariesVersions,              String[] usesStaticLibraries, long[] usesStaticLibrariesVersions, -            Set<String> mimeGroupNames, @NonNull UUID domainSetId) { +            Set<String> mimeGroupNames, @NonNull UUID domainSetId, boolean isPersistent, +            int targetSdkVersion, byte[] restrictUpdatedHash) {          final PackageSetting pkgSetting;          if (originalPkg != null) {              if (PackageManagerService.DEBUG_UPGRADE) Log.v(PackageManagerService.TAG, "Package " @@ -1080,7 +1082,10 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile                      .setUsesStaticLibrariesVersions(usesStaticLibrariesVersions)                      // Update new package state.                      .setLastModifiedTime(codePath.lastModified()) -                    .setDomainSetId(domainSetId); +                    .setDomainSetId(domainSetId) +                    .setIsPersistent(isPersistent) +                    .setTargetSdkVersion(targetSdkVersion) +                    .setRestrictUpdateHash(restrictUpdatedHash);              pkgSetting.setFlags(pkgFlags)                      .setPrivateFlags(pkgPrivateFlags);          } else { @@ -1092,7 +1097,10 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile                      null /*cpuAbiOverrideString*/, versionCode, pkgFlags, pkgPrivateFlags,                      0 /*sharedUserAppId*/, usesSdkLibraries, usesSdkLibrariesVersions,                      usesStaticLibraries, usesStaticLibrariesVersions, -                    createMimeGroups(mimeGroupNames), domainSetId); +                    createMimeGroups(mimeGroupNames), domainSetId) +                    .setIsPersistent(isPersistent) +                    .setTargetSdkVersion(targetSdkVersion) +                    .setRestrictUpdateHash(restrictUpdatedHash);              pkgSetting.setLastModifiedTime(codePath.lastModified());              if (sharedUser != null) {                  pkgSetting.setSharedUserAppId(sharedUser.mAppId); @@ -1206,7 +1214,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile              int pkgPrivateFlags, @NonNull UserManagerService userManager,              @Nullable String[] usesSdkLibraries, @Nullable long[] usesSdkLibrariesVersions,              @Nullable String[] usesStaticLibraries, @Nullable long[] usesStaticLibrariesVersions, -            @Nullable Set<String> mimeGroupNames, @NonNull UUID domainSetId) +            @Nullable Set<String> mimeGroupNames, @NonNull UUID domainSetId, boolean isPersistent, +            int targetSdkVersion, byte[] restrictUpdatedHash)                      throws PackageManagerException {          final String pkgName = pkgSetting.getPackageName();          if (sharedUser != null) { @@ -1258,7 +1267,10 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile          pkgSetting.setPrimaryCpuAbi(primaryCpuAbi)                  .setSecondaryCpuAbi(secondaryCpuAbi)                  .updateMimeGroups(mimeGroupNames) -                .setDomainSetId(domainSetId); +                .setDomainSetId(domainSetId) +                .setIsPersistent(isPersistent) +                .setTargetSdkVersion(targetSdkVersion) +                .setRestrictUpdateHash(restrictUpdatedHash);          // Update SDK library dependencies if needed.          if (usesSdkLibraries != null && usesSdkLibrariesVersions != null                  && usesSdkLibraries.length == usesSdkLibrariesVersions.length) { @@ -2068,6 +2080,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile              if (tagName.equals(TAG_ARCHIVE_ACTIVITY_INFO)) {                  String title = parser.getAttributeValue(null,                          ATTR_ARCHIVE_ACTIVITY_TITLE); +                String originalComponentName = +                        parser.getAttributeValue(null, ATTR_ARCHIVE_ORIGINAL_COMPONENT_NAME);                  String iconAttribute = parser.getAttributeValue(null,                          ATTR_ARCHIVE_ICON_PATH);                  Path iconPath = iconAttribute == null ? null : Path.of(iconAttribute); @@ -2076,17 +2090,27 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile                  Path monochromeIconPath = monochromeAttribute == null ? null : Path.of(                          monochromeAttribute); -                if (title == null || iconPath == null) { -                    Slog.wtf(TAG, -                            TextUtils.formatSimple("Missing attributes in tag %s. %s: %s, %s: %s", -                                    TAG_ARCHIVE_ACTIVITY_INFO, ATTR_ARCHIVE_ACTIVITY_TITLE, title, +                if (title == null || originalComponentName == null || iconPath == null) { +                    Slog.wtf( +                            TAG, +                            TextUtils.formatSimple( +                                    "Missing attributes in tag %s. %s: %s, %s: %s, %s: %s", +                                    TAG_ARCHIVE_ACTIVITY_INFO, +                                    ATTR_ARCHIVE_ACTIVITY_TITLE, +                                    title, +                                    ATTR_ARCHIVE_ORIGINAL_COMPONENT_NAME, +                                    originalComponentName,                                      ATTR_ARCHIVE_ICON_PATH,                                      iconPath));                      continue;                  }                  activityInfos.add( -                        new ArchiveState.ArchiveActivityInfo(title, iconPath, monochromeIconPath)); +                        new ArchiveState.ArchiveActivityInfo( +                                title, +                                ComponentName.unflattenFromString(originalComponentName), +                                iconPath, +                                monochromeIconPath));              }          }          return activityInfos; @@ -2458,6 +2482,10 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile          for (ArchiveState.ArchiveActivityInfo activityInfo : archiveState.getActivityInfos()) {              serializer.startTag(null, TAG_ARCHIVE_ACTIVITY_INFO);              serializer.attribute(null, ATTR_ARCHIVE_ACTIVITY_TITLE, activityInfo.getTitle()); +            serializer.attribute( +                    null, +                    ATTR_ARCHIVE_ORIGINAL_COMPONENT_NAME, +                    activityInfo.getOriginalComponentName().flattenToString());              if (activityInfo.getIconBitmap() != null) {                  serializer.attribute(null, ATTR_ARCHIVE_ICON_PATH,                          activityInfo.getIconBitmap().toAbsolutePath().toString()); @@ -6400,16 +6428,25 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile      }      boolean clearPersistentPreferredActivity(IntentFilter filter, int userId) { +        ArrayList<PersistentPreferredActivity> removed = null;          PersistentPreferredIntentResolver ppir = mPersistentPreferredActivities.get(userId);          Iterator<PersistentPreferredActivity> it = ppir.filterIterator();          boolean changed = false;          while (it.hasNext()) {              PersistentPreferredActivity ppa = it.next();              if (IntentFilter.filterEquals(ppa.getIntentFilter(), filter)) { +                if (removed == null) { +                    removed = new ArrayList<>(); +                } +                removed.add(ppa); +            } +        } +        if (removed != null) { +            for (int i = 0; i < removed.size(); i++) { +                PersistentPreferredActivity ppa = removed.get(i);                  ppir.removeFilter(ppa); -                changed = true; -                break;              } +            changed = true;          }          if (changed) {              onChanged(); diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 0e98158d7210..7331bc141ec2 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -1335,18 +1335,6 @@ public class UserManagerService extends IUserManager.Stub {                  && user.profileGroupId == profile.profileGroupId);      } -    private Intent buildProfileAvailabilityIntent(UserInfo profile, boolean enableQuietMode, -            boolean useManagedActions) { -        Intent intent = new Intent(); -        intent.setAction(getAvailabilityIntentAction(enableQuietMode, useManagedActions)); -        intent.putExtra(Intent.EXTRA_QUIET_MODE, enableQuietMode); -        intent.putExtra(Intent.EXTRA_USER, profile.getUserHandle()); -        intent.putExtra(Intent.EXTRA_USER_HANDLE, profile.getUserHandle().getIdentifier()); -        intent.addFlags( -                Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND); -        return intent; -    } -      private String getAvailabilityIntentAction(boolean enableQuietMode, boolean useManagedActions) {          return useManagedActions ?                  enableQuietMode ? @@ -1359,12 +1347,20 @@ public class UserManagerService extends IUserManager.Stub {      private void broadcastProfileAvailabilityChanges(UserInfo profileInfo,              UserHandle parentHandle, boolean enableQuietMode, boolean useManagedActions) { -        Intent availabilityIntent = buildProfileAvailabilityIntent(profileInfo, enableQuietMode, -                useManagedActions); +        Intent availabilityIntent = new Intent(); +        availabilityIntent.setAction( +                getAvailabilityIntentAction(enableQuietMode, useManagedActions)); +        availabilityIntent.putExtra(Intent.EXTRA_QUIET_MODE, enableQuietMode); +        availabilityIntent.putExtra(Intent.EXTRA_USER, profileInfo.getUserHandle()); +        availabilityIntent.putExtra(Intent.EXTRA_USER_HANDLE, +                profileInfo.getUserHandle().getIdentifier());          if (profileInfo.isManagedProfile()) {              getDevicePolicyManagerInternal().broadcastIntentToManifestReceivers(                      availabilityIntent, parentHandle, /* requiresPermission= */ true);          } +        availabilityIntent.addFlags( +                Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND); +          // TODO(b/302708423): Restrict the apps that can receive these intents in case of a private          //  profile.          final Bundle options = new BroadcastOptions() diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java index d804e01aa31e..61e96ca3dd59 100644 --- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java +++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java @@ -379,7 +379,7 @@ public class PackageInfoUtils {          ai.privateFlags |= flag(state.isInstantApp(), ApplicationInfo.PRIVATE_FLAG_INSTANT)                  | flag(state.isVirtualPreload(), ApplicationInfo.PRIVATE_FLAG_VIRTUAL_PRELOAD)                  | flag(state.isHidden(), ApplicationInfo.PRIVATE_FLAG_HIDDEN); -        if ((flags & PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS) != 0 +        if ((flags & PackageManager.MATCH_QUARANTINED_COMPONENTS) == 0                  && state.isQuarantined()) {              ai.enabled = false;          } else  if (state.getEnabledState() == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) { diff --git a/services/core/java/com/android/server/pm/pkg/ArchiveState.java b/services/core/java/com/android/server/pm/pkg/ArchiveState.java index 4916a4a6d72a..1e40d44bd4ca 100644 --- a/services/core/java/com/android/server/pm/pkg/ArchiveState.java +++ b/services/core/java/com/android/server/pm/pkg/ArchiveState.java @@ -16,9 +16,11 @@  package com.android.server.pm.pkg; +import android.content.ComponentName;  import android.annotation.NonNull;  import android.annotation.Nullable; +import com.android.internal.util.AnnotationValidations;  import com.android.internal.util.DataClass;  import java.nio.file.Path; @@ -56,6 +58,10 @@ public class ArchiveState {          @NonNull          private final String mTitle; +        /** The component name of the original activity (pre-archival). */ +        @NonNull +        private final ComponentName mOriginalComponentName; +          /**           * The path to the stored icon of the activity in the app's locale. Null if the app does           * not define any icon (default icon would be shown on the launcher). @@ -96,11 +102,13 @@ public class ArchiveState {          @DataClass.Generated.Member          public ArchiveActivityInfo(                  @NonNull String title, +                @NonNull ComponentName originalComponentName,                  @Nullable Path iconBitmap,                  @Nullable Path monochromeIconBitmap) {              this.mTitle = title; -            com.android.internal.util.AnnotationValidations.validate( -                    NonNull.class, null, mTitle); +            this.mOriginalComponentName = originalComponentName; +            AnnotationValidations.validate(NonNull.class, null, mTitle); +            AnnotationValidations.validate(NonNull.class, null, mOriginalComponentName);              this.mIconBitmap = iconBitmap;              this.mMonochromeIconBitmap = monochromeIconBitmap; @@ -116,6 +124,14 @@ public class ArchiveState {          }          /** +         * The component name of the original activity (pre-archival). +            */ +        @DataClass.Generated.Member +        public @NonNull ComponentName getOriginalComponentName() { +            return mOriginalComponentName; +        } + +        /**           * The path to the stored icon of the activity in the app's locale. Null if the app does           * not define any icon (default icon would be shown on the launcher).           */ @@ -140,6 +156,7 @@ public class ArchiveState {              return "ArchiveActivityInfo { " +                      "title = " + mTitle + ", " + +                    "originalComponentName = " + mOriginalComponentName + ", " +                      "iconBitmap = " + mIconBitmap + ", " +                      "monochromeIconBitmap = " + mMonochromeIconBitmap +              " }"; @@ -159,6 +176,7 @@ public class ArchiveState {              //noinspection PointlessBooleanExpression              return true                      && java.util.Objects.equals(mTitle, that.mTitle) +                    && java.util.Objects.equals(mOriginalComponentName, that.mOriginalComponentName)                      && java.util.Objects.equals(mIconBitmap, that.mIconBitmap)                      && java.util.Objects.equals(mMonochromeIconBitmap, that.mMonochromeIconBitmap);          } @@ -171,6 +189,7 @@ public class ArchiveState {              int _hash = 1;              _hash = 31 * _hash + java.util.Objects.hashCode(mTitle); +            _hash = 31* _hash + java.util.Objects.hashCode(mOriginalComponentName);              _hash = 31 * _hash + java.util.Objects.hashCode(mIconBitmap);              _hash = 31 * _hash + java.util.Objects.hashCode(mMonochromeIconBitmap);              return _hash; @@ -180,7 +199,8 @@ public class ArchiveState {                  time = 1693590309015L,                  codegenVersion = "1.0.23",                  sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/ArchiveState.java", -                inputSignatures = "private final @android.annotation.NonNull java.lang.String mTitle\nprivate final @android.annotation.Nullable java.nio.file.Path mIconBitmap\nprivate final @android.annotation.Nullable java.nio.file.Path mMonochromeIconBitmap\nclass ArchiveActivityInfo extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true)") +                inputSignatures = +                        "private final @android.annotation.NonNull java.lang.String mTitle\nprivate final @android.annotation.NonNull android.content.ComponentName mOriginalComponentName\nprivate final @android.annotation.Nullable java.nio.file.Path mIconBitmap\nprivate final @android.annotation.Nullable java.nio.file.Path mMonochromeIconBitmap\nclass ArchiveActivityInfo extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true)")          @Deprecated          private void __metadata() {} @@ -224,11 +244,9 @@ public class ArchiveState {              @NonNull List<ArchiveActivityInfo> activityInfos,              @NonNull String installerTitle) {          this.mActivityInfos = activityInfos; -        com.android.internal.util.AnnotationValidations.validate( -                NonNull.class, null, mActivityInfos); +        AnnotationValidations.validate(NonNull.class, null, mActivityInfos);          this.mInstallerTitle = installerTitle; -        com.android.internal.util.AnnotationValidations.validate( -                NonNull.class, null, mInstallerTitle); +        AnnotationValidations.validate(NonNull.class, null, mInstallerTitle);          // onConstructed(); // You can define this method to get a callback      } diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java index 3f347e465f81..e7137bbd7969 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageState.java +++ b/services/core/java/com/android/server/pm/pkg/PackageState.java @@ -434,4 +434,26 @@ public interface PackageState {       */      @Nullable      String getApexModuleName(); + +    /** +     * @see ApplicationInfo#FLAG_PERSISTENT +     * @see R.styleable#AndroidManifestApplication_persistent +     * @hide +     */ +    boolean isPersistent(); + +    /** +     * @see ApplicationInfo#targetSdkVersion +     * @see R.styleable#AndroidManifestUsesSdk_targetSdkVersion +     * @hide +     */ +    int getTargetSdkVersion(); + +    /** +     * @see R.styleable#AndroidManifestRestrictUpdate +     * @hide +     */ +    @Immutable.Ignore +    @Nullable +    byte[] getRestrictUpdateHash();  } diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java index 7b07e5b2bb6b..cd3583b814a4 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java +++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateUtils.java @@ -16,9 +16,9 @@  package com.android.server.pm.pkg; -import static android.content.pm.PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS;  import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;  import static android.content.pm.PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS; +import static android.content.pm.PackageManager.MATCH_QUARANTINED_COMPONENTS;  import android.annotation.NonNull;  import android.content.pm.ComponentInfo; @@ -147,7 +147,7 @@ public class PackageUserStateUtils {              return true;          } -        if ((flags & FILTER_OUT_QUARANTINED_COMPONENTS) != 0 && state.isQuarantined()) { +        if ((flags & MATCH_QUARANTINED_COMPONENTS) == 0 && state.isQuarantined()) {              return false;          } diff --git a/services/core/java/com/android/server/pm/pkg/SuspendParams.java b/services/core/java/com/android/server/pm/pkg/SuspendParams.java index 86391c91bea5..153238fa6866 100644 --- a/services/core/java/com/android/server/pm/pkg/SuspendParams.java +++ b/services/core/java/com/android/server/pm/pkg/SuspendParams.java @@ -17,7 +17,6 @@  package com.android.server.pm.pkg;  import android.annotation.Nullable; -import android.content.pm.Flags;  import android.content.pm.SuspendDialogInfo;  import android.os.BaseBundle;  import android.os.PersistableBundle; @@ -142,8 +141,7 @@ public final class SuspendParams {          PersistableBundle readAppExtras = null;          PersistableBundle readLauncherExtras = null; -        final boolean quarantined = in.getAttributeBoolean(null, ATTR_QUARANTINED, false) -                && Flags.quarantinedEnabled(); +        final boolean quarantined = in.getAttributeBoolean(null, ATTR_QUARANTINED, false);          final int currentDepth = in.getDepth();          int type; diff --git a/services/core/java/com/android/server/power/Android.bp b/services/core/java/com/android/server/power/Android.bp index 1da9dd770ba6..607d435b5410 100644 --- a/services/core/java/com/android/server/power/Android.bp +++ b/services/core/java/com/android/server/power/Android.bp @@ -1,5 +1,5 @@  aconfig_declarations { -    name: "power_optimization_flags", +    name: "backstage_power_flags",      package: "com.android.server.power.optimization",      srcs: [          "stats/*.aconfig", @@ -7,6 +7,6 @@ aconfig_declarations {  }  java_aconfig_library { -    name: "power_optimization_flags_lib", -    aconfig_declarations: "power_optimization_flags", +    name: "backstage_power_flags_lib", +    aconfig_declarations: "backstage_power_flags",  } diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java index 6cc9d0a54607..bc90f5c46130 100644 --- a/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java +++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStats.java @@ -18,15 +18,26 @@ package com.android.server.power.stats;  import android.annotation.CurrentTimeMillisLong;  import android.annotation.DurationMillisLong; +import android.annotation.NonNull; +import android.os.BatteryConsumer;  import android.os.UserHandle;  import android.text.format.DateFormat;  import android.util.IndentingPrintWriter; +import android.util.Slog; +import android.util.TimeUtils;  import com.android.internal.os.PowerStats; +import com.android.modules.utils.TypedXmlPullParser; +import com.android.modules.utils.TypedXmlSerializer; -import java.io.PrintWriter; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.ArrayList;  import java.util.Arrays;  import java.util.HashSet; +import java.util.List;  import java.util.Set;  /** @@ -34,25 +45,75 @@ import java.util.Set;   * etc) covering a specific period of power usage history.   */  class AggregatedPowerStats { +    private static final String TAG = "AggregatedPowerStats"; +    private static final int MAX_CLOCK_UPDATES = 100; +    private static final String XML_TAG_AGGREGATED_POWER_STATS = "agg-power-stats"; +      private final PowerComponentAggregatedPowerStats[] mPowerComponentStats; -    @CurrentTimeMillisLong -    private long mStartTime; +    static class ClockUpdate { +        public long monotonicTime; +        @CurrentTimeMillisLong public long currentTime; +    } + +    private final List<ClockUpdate> mClockUpdates = new ArrayList<>();      @DurationMillisLong      private long mDurationMs; -    AggregatedPowerStats(PowerComponentAggregatedPowerStats... powerComponentAggregatedPowerStats) { -        this.mPowerComponentStats = powerComponentAggregatedPowerStats; +    AggregatedPowerStats(AggregatedPowerStatsConfig aggregatedPowerStatsConfig) { +        List<AggregatedPowerStatsConfig.PowerComponent> configs = +                aggregatedPowerStatsConfig.getPowerComponentsAggregatedStatsConfigs(); +        mPowerComponentStats = new PowerComponentAggregatedPowerStats[configs.size()]; +        for (int i = 0; i < configs.size(); i++) { +            mPowerComponentStats[i] = createPowerComponentAggregatedPowerStats(configs.get(i)); +        } +    } + +    private PowerComponentAggregatedPowerStats createPowerComponentAggregatedPowerStats( +            AggregatedPowerStatsConfig.PowerComponent config) { +        switch (config.getPowerComponentId()) { +            case BatteryConsumer.POWER_COMPONENT_CPU: +                return new CpuAggregatedPowerStats(config); +            default: +                return new PowerComponentAggregatedPowerStats(config); +        } +    } + +    /** +     * Records a mapping of monotonic time to wall-clock time. Since wall-clock time can change, +     * there may be multiple clock updates in one set of aggregated stats. +     * +     * @param monotonicTime monotonic time in milliseconds, see +     * {@link com.android.internal.os.MonotonicClock} +     * @param currentTime   current time in milliseconds, see {@link System#currentTimeMillis()} +     */ +    void addClockUpdate(long monotonicTime, @CurrentTimeMillisLong long currentTime) { +        ClockUpdate clockUpdate = new ClockUpdate(); +        clockUpdate.monotonicTime = monotonicTime; +        clockUpdate.currentTime = currentTime; +        if (mClockUpdates.size() < MAX_CLOCK_UPDATES) { +            mClockUpdates.add(clockUpdate); +        } else { +            Slog.i(TAG, "Too many clock updates. Replacing the previous update with " +                        + DateFormat.format("yyyy-MM-dd-HH-mm-ss", currentTime)); +            mClockUpdates.set(mClockUpdates.size() - 1, clockUpdate); +        }      } -    void setStartTime(@CurrentTimeMillisLong long startTime) { -        mStartTime = startTime; +    /** +     * Start time according to {@link com.android.internal.os.MonotonicClock} +     */ +    long getStartTime() { +        if (mClockUpdates.isEmpty()) { +            return 0; +        } else { +            return mClockUpdates.get(0).monotonicTime; +        }      } -    @CurrentTimeMillisLong -    public long getStartTime() { -        return mStartTime; +    List<ClockUpdate> getClockUpdates() { +        return mClockUpdates;      }      void setDuration(long durationMs) { @@ -73,13 +134,14 @@ class AggregatedPowerStats {          return null;      } -    void setDeviceState(@PowerStatsAggregator.TrackedState int stateId, int state, long time) { +    void setDeviceState(@AggregatedPowerStatsConfig.TrackedState int stateId, int state, +            long time) {          for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {              stats.setState(stateId, state, time);          }      } -    void setUidState(int uid, @PowerStatsAggregator.TrackedState int stateId, int state, +    void setUidState(int uid, @AggregatedPowerStatsConfig.TrackedState int stateId, int state,              long time) {          for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {              stats.setUidState(uid, stateId, state, time); @@ -106,20 +168,90 @@ class AggregatedPowerStats {      }      void reset() { -        mStartTime = 0; +        mClockUpdates.clear();          mDurationMs = 0;          for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) {              stats.reset();          }      } -    void dump(PrintWriter pw) { -        IndentingPrintWriter ipw = new IndentingPrintWriter(pw); -        ipw.print("Start time: "); -        ipw.print(DateFormat.format("yyyy-MM-dd-HH-mm-ss", mStartTime)); -        ipw.print(" duration: "); -        ipw.print(mDurationMs); -        ipw.println(); +    public void writeXml(TypedXmlSerializer serializer) throws IOException { +        serializer.startTag(null, XML_TAG_AGGREGATED_POWER_STATS); +        for (PowerComponentAggregatedPowerStats stats : mPowerComponentStats) { +            stats.writeXml(serializer); +        } +        serializer.endTag(null, XML_TAG_AGGREGATED_POWER_STATS); +        serializer.flush(); +    } + +    @NonNull +    public static AggregatedPowerStats createFromXml( +            TypedXmlPullParser parser, AggregatedPowerStatsConfig aggregatedPowerStatsConfig) +            throws XmlPullParserException, IOException { +        AggregatedPowerStats stats = new AggregatedPowerStats(aggregatedPowerStatsConfig); +        boolean inElement = false; +        boolean skipToEnd = false; +        int eventType = parser.getEventType(); +        while (eventType != XmlPullParser.END_DOCUMENT +                   && !(eventType == XmlPullParser.END_TAG +                        && parser.getName().equals(XML_TAG_AGGREGATED_POWER_STATS))) { +            if (!skipToEnd && eventType == XmlPullParser.START_TAG) { +                switch (parser.getName()) { +                    case XML_TAG_AGGREGATED_POWER_STATS: +                        inElement = true; +                        break; +                    case PowerComponentAggregatedPowerStats.XML_TAG_POWER_COMPONENT: +                        if (!inElement) { +                            break; +                        } + +                        int powerComponentId = parser.getAttributeInt(null, +                                PowerComponentAggregatedPowerStats.XML_ATTR_ID); +                        for (PowerComponentAggregatedPowerStats powerComponent : +                                stats.mPowerComponentStats) { +                            if (powerComponent.powerComponentId == powerComponentId) { +                                if (!powerComponent.readFromXml(parser)) { +                                    skipToEnd = true; +                                } +                                break; +                            } +                        } +                        break; +                } +            } +            eventType = parser.next(); +        } +        return stats; +    } + +    void dump(IndentingPrintWriter ipw) { +        StringBuilder sb = new StringBuilder(); +        long baseTime = 0; +        for (int i = 0; i < mClockUpdates.size(); i++) { +            ClockUpdate clockUpdate = mClockUpdates.get(i); +            sb.setLength(0); +            if (i == 0) { +                baseTime = clockUpdate.monotonicTime; +                sb.append("Start time: ") +                        .append(DateFormat.format("yyyy-MM-dd-HH-mm-ss", clockUpdate.currentTime)) +                        .append(" (") +                        .append(baseTime) +                        .append(") duration: ") +                        .append(mDurationMs); +                ipw.println(sb); +            } else { +                sb.setLength(0); +                sb.append("Clock update:  "); +                TimeUtils.formatDuration( +                        clockUpdate.monotonicTime - baseTime, sb, +                        TimeUtils.HUNDRED_DAY_FIELD_LEN + 3); +                sb.append(" ").append( +                        DateFormat.format("yyyy-MM-dd-HH-mm-ss", clockUpdate.currentTime)); +                ipw.increaseIndent(); +                ipw.println(sb); +                ipw.decreaseIndent(); +            } +        }          ipw.println("Device");          ipw.increaseIndent(); diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java new file mode 100644 index 000000000000..477c2286abe0 --- /dev/null +++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.power.stats; + +import android.annotation.IntDef; +import android.os.BatteryConsumer; + +import com.android.internal.os.MultiStateStats; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; + +/** + * Configuration that controls how power stats are aggregated.  It determines which state changes + * are to be considered as essential dimensions ("tracked states") for each power component (CPU, + * WiFi, etc).  Also, it determines which states are tracked globally and which ones on a per-UID + * basis. + */ +public class AggregatedPowerStatsConfig { +    public static final int STATE_POWER = 0; +    public static final int STATE_SCREEN = 1; +    public static final int STATE_PROCESS_STATE = 2; + +    @IntDef({ +            STATE_POWER, +            STATE_SCREEN, +            STATE_PROCESS_STATE, +    }) +    @Retention(RetentionPolicy.SOURCE) +    public @interface TrackedState { +    } + +    static final String STATE_NAME_POWER = "pwr"; +    static final int POWER_STATE_BATTERY = 0; +    static final int POWER_STATE_OTHER = 1;   // Plugged in, or on wireless charger, etc. +    static final String[] STATE_LABELS_POWER = {"pwr-battery", "pwr-other"}; + +    static final String STATE_NAME_SCREEN = "scr"; +    static final int SCREEN_STATE_ON = 0; +    static final int SCREEN_STATE_OTHER = 1;  // Off, doze etc +    static final String[] STATE_LABELS_SCREEN = {"scr-on", "scr-other"}; + +    static final String STATE_NAME_PROCESS_STATE = "ps"; +    static final String[] STATE_LABELS_PROCESS_STATE; + +    static { +        String[] procStateLabels = new String[BatteryConsumer.PROCESS_STATE_COUNT]; +        for (int i = 0; i < BatteryConsumer.PROCESS_STATE_COUNT; i++) { +            procStateLabels[i] = BatteryConsumer.processStateToString(i); +        } +        STATE_LABELS_PROCESS_STATE = procStateLabels; +    } + +    /** +     * Configuration for a give power component (CPU, WiFi, etc) +     */ +    public static class PowerComponent { +        private final int mPowerComponentId; +        private @TrackedState int[] mTrackedDeviceStates; +        private @TrackedState int[] mTrackedUidStates; + +        PowerComponent(int powerComponentId) { +            this.mPowerComponentId = powerComponentId; +        } + +        /** +         * Configures which states should be tracked as separate dimensions for the entire device. +         */ +        public PowerComponent trackDeviceStates(@TrackedState int... states) { +            mTrackedDeviceStates = states; +            return this; +        } + +        /** +         * Configures which states should be tracked as separate dimensions on a per-UID basis. +         */ +        public PowerComponent trackUidStates(@TrackedState int... states) { +            mTrackedUidStates = states; +            return this; +        } + +        public int getPowerComponentId() { +            return mPowerComponentId; +        } + +        public MultiStateStats.States[] getDeviceStateConfig() { +            return new MultiStateStats.States[]{ +                    new MultiStateStats.States(STATE_NAME_POWER, +                            isTracked(mTrackedDeviceStates, STATE_POWER), +                            STATE_LABELS_POWER), +                    new MultiStateStats.States(STATE_NAME_SCREEN, +                            isTracked(mTrackedDeviceStates, STATE_SCREEN), +                            STATE_LABELS_SCREEN), +            }; +        } + +        public MultiStateStats.States[] getUidStateConfig() { +            return new MultiStateStats.States[]{ +                    new MultiStateStats.States(STATE_NAME_POWER, +                            isTracked(mTrackedUidStates, STATE_POWER), +                            AggregatedPowerStatsConfig.STATE_LABELS_POWER), +                    new MultiStateStats.States(STATE_NAME_SCREEN, +                            isTracked(mTrackedUidStates, STATE_SCREEN), +                            AggregatedPowerStatsConfig.STATE_LABELS_SCREEN), +                    new MultiStateStats.States(STATE_NAME_PROCESS_STATE, +                            isTracked(mTrackedUidStates, STATE_PROCESS_STATE), +                            AggregatedPowerStatsConfig.STATE_LABELS_PROCESS_STATE), +            }; +        } + +        private boolean isTracked(int[] trackedStates, int state) { +            if (trackedStates == null) { +                return false; +            } + +            for (int trackedState : trackedStates) { +                if (trackedState == state) { +                    return true; +                } +            } +            return false; +        } +    } + +    private final List<PowerComponent> mPowerComponents = new ArrayList<>(); + +    /** +     * Creates a configuration for the specified power component, which may be one of the +     * standard power component IDs, e.g. {@link BatteryConsumer#POWER_COMPONENT_CPU}, or +     * a custom power component. +     */ +    public PowerComponent trackPowerComponent(int powerComponentId) { +        PowerComponent builder = new PowerComponent(powerComponentId); +        mPowerComponents.add(builder); +        return builder; +    } + +    public List<PowerComponent> getPowerComponentsAggregatedStatsConfigs() { +        return mPowerComponents; +    } +} diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsSection.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsSection.java new file mode 100644 index 000000000000..7ba433017413 --- /dev/null +++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsSection.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power.stats; + +import android.util.IndentingPrintWriter; + +import com.android.modules.utils.TypedXmlSerializer; + +import java.io.IOException; + +class AggregatedPowerStatsSection extends PowerStatsSpan.Section { +    public static final String TYPE = "aggregated-power-stats"; + +    private final AggregatedPowerStats mAggregatedPowerStats; + +    AggregatedPowerStatsSection(AggregatedPowerStats aggregatedPowerStats) { +        super(TYPE); +        mAggregatedPowerStats = aggregatedPowerStats; +    } + +    public AggregatedPowerStats getAggregatedPowerStats() { +        return mAggregatedPowerStats; +    } + +    @Override +    void write(TypedXmlSerializer serializer) throws IOException { +        mAggregatedPowerStats.writeXml(serializer); +    } + +    @Override +    public void dump(IndentingPrintWriter ipw) { +        mAggregatedPowerStats.dump(ipw); +    } +} diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java index 613f18982b86..a9c2bc28b7a0 100644 --- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java +++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java @@ -124,6 +124,7 @@ import com.android.internal.os.KernelMemoryBandwidthStats;  import com.android.internal.os.KernelSingleUidTimeReader;  import com.android.internal.os.LongArrayMultiStateCounter;  import com.android.internal.os.LongMultiStateCounter; +import com.android.internal.os.MonotonicClock;  import com.android.internal.os.PowerProfile;  import com.android.internal.os.PowerStats;  import com.android.internal.os.RailStats; @@ -283,7 +284,6 @@ public class BatteryStatsImpl extends BatteryStats {      private final LongSparseArray<SamplingTimer> mKernelMemoryStats = new LongSparseArray<>();      private int[] mCpuPowerBracketMap;      private final CpuPowerStatsCollector mCpuPowerStatsCollector; -    private final PowerStatsAggregator mPowerStatsAggregator;      public LongSparseArray<SamplingTimer> getKernelMemoryStats() {          return mKernelMemoryStats; @@ -356,6 +356,11 @@ public class BatteryStatsImpl extends BatteryStats {      protected Queue<UidToRemove> mPendingRemovedUids = new LinkedList<>();      @NonNull +    public BatteryStatsHistory getHistory() { +        return mHistory; +    } + +    @NonNull      BatteryStatsHistory copyHistory() {          return mHistory.copy();      } @@ -1718,14 +1723,7 @@ public class BatteryStatsImpl extends BatteryStats {          return mMaxLearnedBatteryCapacityUah;      } -    public BatteryStatsImpl() { -        this(Clock.SYSTEM_CLOCK); -    } - -    public BatteryStatsImpl(Clock clock) { -        this(clock, null); -    } - +    @VisibleForTesting      public BatteryStatsImpl(Clock clock, File historyDirectory) {          init(clock);          mBatteryStatsConfig = new BatteryStatsConfig.Builder().build(); @@ -1737,18 +1735,19 @@ public class BatteryStatsImpl extends BatteryStats {              mCheckinFile = null;              mStatsFile = null;              mHistory = new BatteryStatsHistory(mConstants.MAX_HISTORY_FILES, -                    mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock); +                    mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, +                    new MonotonicClock(0, mClock));          } else {              mCheckinFile = new AtomicFile(new File(historyDirectory, "batterystats-checkin.bin"));              mStatsFile = new AtomicFile(new File(historyDirectory, "batterystats.bin"));              mHistory = new BatteryStatsHistory(historyDirectory, mConstants.MAX_HISTORY_FILES, -                    mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock); +                    mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, +                    new MonotonicClock(0, mClock));          }          mPlatformIdleStateCallback = null;          mEnergyConsumerRetriever = null;          mUserInfoProvider = null;          mCpuPowerStatsCollector = null; -        mPowerStatsAggregator = null;      }      private void init(Clock clock) { @@ -10906,20 +10905,12 @@ public class BatteryStatsImpl extends BatteryStats {          return mTmpCpuTimeInFreq;      } -    public BatteryStatsImpl(@NonNull BatteryStatsConfig config, @Nullable File systemDir, +    public BatteryStatsImpl(@NonNull BatteryStatsConfig config, @NonNull Clock clock, +            @NonNull MonotonicClock monotonicClock, @Nullable File systemDir,              @NonNull Handler handler, @Nullable PlatformIdleStateCallback cb,              @Nullable EnergyStatsRetriever energyStatsCb,              @NonNull UserInfoProvider userInfoProvider, @NonNull PowerProfile powerProfile,              @NonNull CpuScalingPolicies cpuScalingPolicies) { -        this(config, Clock.SYSTEM_CLOCK, systemDir, handler, cb, energyStatsCb, userInfoProvider, -                powerProfile, cpuScalingPolicies); -    } - -    private BatteryStatsImpl(@NonNull BatteryStatsConfig config, @NonNull Clock clock, -            @Nullable File systemDir, @NonNull Handler handler, -            @Nullable PlatformIdleStateCallback cb, @Nullable EnergyStatsRetriever energyStatsCb, -            @NonNull UserInfoProvider userInfoProvider, @NonNull PowerProfile powerProfile, -            @NonNull CpuScalingPolicies cpuScalingPolicies) {          init(clock);          mBatteryStatsConfig = config; @@ -10936,31 +10927,19 @@ public class BatteryStatsImpl extends BatteryStats {              mCheckinFile = null;              mDailyFile = null;              mHistory = new BatteryStatsHistory(mConstants.MAX_HISTORY_FILES, -                    mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock); +                    mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, monotonicClock);          } else {              mStatsFile = new AtomicFile(new File(systemDir, "batterystats.bin"));              mCheckinFile = new AtomicFile(new File(systemDir, "batterystats-checkin.bin"));              mDailyFile = new AtomicFile(new File(systemDir, "batterystats-daily.xml"));              mHistory = new BatteryStatsHistory(systemDir, mConstants.MAX_HISTORY_FILES, -                    mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock); +                    mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, monotonicClock);          }          mCpuPowerStatsCollector = new CpuPowerStatsCollector(mCpuScalingPolicies, mPowerProfile,                  mHandler, mBatteryStatsConfig.getPowerStatsThrottlePeriodCpu());          mCpuPowerStatsCollector.addConsumer(this::recordPowerStats); -        PowerStatsAggregator.Builder builder = new PowerStatsAggregator.Builder(mHistory); -        builder.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_CPU) -                .trackDeviceStates( -                        PowerStatsAggregator.STATE_POWER, -                        PowerStatsAggregator.STATE_SCREEN) -                .trackUidStates( -                        PowerStatsAggregator.STATE_POWER, -                        PowerStatsAggregator.STATE_SCREEN, -                        PowerStatsAggregator.STATE_PROCESS_STATE); - -        mPowerStatsAggregator = builder.build(); -          mStartCount++;          initTimersAndCounters();          mOnBattery = mOnBatteryInternal = false; @@ -15702,18 +15681,21 @@ public class BatteryStatsImpl extends BatteryStats {      }      /** -     * Grabs one sample of PowerStats and prints it. +     * Schedules an immediate (but asynchronous) collection of PowerStats samples. +     * Callers will need to wait for the collection to complete on the handler thread.       */ -    public void dumpStatsSample(PrintWriter pw) { -        mCpuPowerStatsCollector.collectAndDump(pw); +    public void schedulePowerStatsSampleCollection() { +        if (mCpuPowerStatsCollector == null) { +            return; +        } +        mCpuPowerStatsCollector.forceSchedule();      }      /** -     * Aggregates power stats between the specified times and prints them. +     * Grabs one sample of PowerStats and prints it.       */ -    public void dumpAggregatedStats(PrintWriter pw, long startTimeMs, long endTimeMs) { -        mPowerStatsAggregator.aggregateBatteryStats(startTimeMs, endTimeMs, -                stats-> stats.dump(pw)); +    public void dumpStatsSample(PrintWriter pw) { +        mCpuPowerStatsCollector.collectAndDump(pw);      }      private final Runnable mWriteAsyncRunnable = () -> { diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java index f6fa9f244252..851a3f7bdaba 100644 --- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java +++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java @@ -46,7 +46,7 @@ public class BatteryUsageStatsProvider {      private static final String TAG = "BatteryUsageStatsProv";      private final Context mContext;      private final BatteryStats mStats; -    private final BatteryUsageStatsStore mBatteryUsageStatsStore; +    private final PowerStatsStore mPowerStatsStore;      private final PowerProfile mPowerProfile;      private final CpuScalingPolicies mCpuScalingPolicies;      private final Object mLock = new Object(); @@ -58,10 +58,10 @@ public class BatteryUsageStatsProvider {      @VisibleForTesting      public BatteryUsageStatsProvider(Context context, BatteryStats stats, -            BatteryUsageStatsStore batteryUsageStatsStore) { +            PowerStatsStore powerStatsStore) {          mContext = context;          mStats = stats; -        mBatteryUsageStatsStore = batteryUsageStatsStore; +        mPowerStatsStore = powerStatsStore;          mPowerProfile = stats instanceof BatteryStatsImpl                  ? ((BatteryStatsImpl) stats).getPowerProfile()                  : new PowerProfile(context); @@ -314,20 +314,52 @@ public class BatteryUsageStatsProvider {          final BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(                  customEnergyConsumerNames, includePowerModels, includeProcessStateData,                  minConsumedPowerThreshold); -        if (mBatteryUsageStatsStore == null) { -            Log.e(TAG, "BatteryUsageStatsStore is unavailable"); +        if (mPowerStatsStore == null) { +            Log.e(TAG, "PowerStatsStore is unavailable");              return builder.build();          } -        final long[] timestamps = mBatteryUsageStatsStore.listBatteryUsageStatsTimestamps(); -        for (long timestamp : timestamps) { -            if (timestamp > query.getFromTimestamp() && timestamp <= query.getToTimestamp()) { -                final BatteryUsageStats snapshot = -                        mBatteryUsageStatsStore.loadBatteryUsageStats(timestamp); -                if (snapshot == null) { -                    continue; -                } +        List<PowerStatsSpan.Metadata> toc = mPowerStatsStore.getTableOfContents(); +        for (PowerStatsSpan.Metadata spanMetadata : toc) { +            if (!spanMetadata.getSections().contains(BatteryUsageStatsSection.TYPE)) { +                continue; +            } + +            // BatteryUsageStatsQuery is expressed in terms of wall-clock time range for the +            // session end time. +            // +            // The following algorithm is correct when there is only one time frame in the span. +            // When the wall-clock time is adjusted in the middle of an stats span, +            // constraining it by wall-clock time becomes ambiguous. In this case, the algorithm +            // only covers some situations, but not others.  When using the resulting data for +            // analysis, we should always pay attention to the full set of included timeframes. +            // TODO(b/298459065): switch to monotonic clock +            long minTime = Long.MAX_VALUE; +            long maxTime = 0; +            for (PowerStatsSpan.TimeFrame timeFrame : spanMetadata.getTimeFrames()) { +                long spanEndTime = timeFrame.startTime + timeFrame.duration; +                minTime = Math.min(minTime, spanEndTime); +                maxTime = Math.max(maxTime, spanEndTime); +            } + +            // Per BatteryUsageStatsQuery API, the "from" timestamp is *exclusive*, +            // while the "to" timestamp is *inclusive*. +            boolean isInRange = +                    (query.getFromTimestamp() == 0 || minTime > query.getFromTimestamp()) +                    && (query.getToTimestamp() == 0 || maxTime <= query.getToTimestamp()); +            if (!isInRange) { +                continue; +            } + +            PowerStatsSpan powerStatsSpan = mPowerStatsStore.loadPowerStatsSpan( +                    spanMetadata.getId(), BatteryUsageStatsSection.TYPE); +            if (powerStatsSpan == null) { +                continue; +            } +            for (PowerStatsSpan.Section section : powerStatsSpan.getSections()) { +                BatteryUsageStats snapshot = +                        ((BatteryUsageStatsSection) section).getBatteryUsageStats();                  if (!Arrays.equals(snapshot.getCustomPowerComponentNames(),                          customEnergyConsumerNames)) {                      Log.w(TAG, "Ignoring older BatteryUsageStats snapshot, which has different " diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsSection.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsSection.java new file mode 100644 index 000000000000..b95faac7c111 --- /dev/null +++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsSection.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power.stats; + +import android.os.BatteryUsageStats; +import android.util.IndentingPrintWriter; + +import com.android.modules.utils.TypedXmlSerializer; + +import java.io.IOException; + +class BatteryUsageStatsSection extends PowerStatsSpan.Section { +    public static final String TYPE = "battery-usage-stats"; + +    private final BatteryUsageStats mBatteryUsageStats; + +    BatteryUsageStatsSection(BatteryUsageStats batteryUsageStats) { +        super(TYPE); +        mBatteryUsageStats = batteryUsageStats; +    } + +    public BatteryUsageStats getBatteryUsageStats() { +        return mBatteryUsageStats; +    } + +    @Override +    void write(TypedXmlSerializer serializer) throws IOException { +        mBatteryUsageStats.writeXml(serializer); +    } + +    @Override +    public void dump(IndentingPrintWriter ipw) { +        mBatteryUsageStats.dump(ipw, ""); +    } +} diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsStore.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsStore.java deleted file mode 100644 index 0d7a1406f751..000000000000 --- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsStore.java +++ /dev/null @@ -1,338 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - *      http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.power.stats; - -import android.annotation.Nullable; -import android.content.Context; -import android.os.BatteryUsageStats; -import android.os.BatteryUsageStatsQuery; -import android.os.Handler; -import android.util.AtomicFile; -import android.util.Log; -import android.util.LongArray; -import android.util.Slog; -import android.util.Xml; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.modules.utils.TypedXmlPullParser; -import com.android.modules.utils.TypedXmlSerializer; - -import org.xmlpull.v1.XmlPullParserException; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.channels.FileChannel; -import java.nio.channels.FileLock; -import java.nio.charset.StandardCharsets; -import java.nio.file.StandardOpenOption; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Properties; -import java.util.TreeMap; -import java.util.concurrent.locks.ReentrantLock; - -/** - * A storage mechanism for BatteryUsageStats snapshots. - */ -public class BatteryUsageStatsStore { -    private static final String TAG = "BatteryUsageStatsStore"; - -    private static final List<BatteryUsageStatsQuery> BATTERY_USAGE_STATS_QUERY = List.of( -            new BatteryUsageStatsQuery.Builder() -                    .setMaxStatsAgeMs(0) -                    .includePowerModels() -                    .includeProcessStateData() -                    .build()); -    private static final String BATTERY_USAGE_STATS_DIR = "battery-usage-stats"; -    private static final String SNAPSHOT_FILE_EXTENSION = ".bus"; -    private static final String DIR_LOCK_FILENAME = ".lock"; -    private static final String CONFIG_FILENAME = "config"; -    private static final String BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY = -            "BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP"; -    private static final long MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES = 100 * 1024; - -    private final Context mContext; -    private final BatteryStatsImpl mBatteryStats; -    private boolean mSystemReady; -    private final File mStoreDir; -    private final File mLockFile; -    private final ReentrantLock mFileLock = new ReentrantLock(); -    private FileLock mJvmLock; -    private final AtomicFile mConfigFile; -    private final long mMaxStorageBytes; -    private final Handler mHandler; -    private final BatteryUsageStatsProvider mBatteryUsageStatsProvider; - -    public BatteryUsageStatsStore(Context context, BatteryStatsImpl stats, File systemDir, -            Handler handler) { -        this(context, stats, systemDir, handler, MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES); -    } - -    @VisibleForTesting -    public BatteryUsageStatsStore(Context context, BatteryStatsImpl batteryStats, File systemDir, -            Handler handler, long maxStorageBytes) { -        mContext = context; -        mBatteryStats = batteryStats; -        mStoreDir = new File(systemDir, BATTERY_USAGE_STATS_DIR); -        mLockFile = new File(mStoreDir, DIR_LOCK_FILENAME); -        mConfigFile = new AtomicFile(new File(mStoreDir, CONFIG_FILENAME)); -        mHandler = handler; -        mMaxStorageBytes = maxStorageBytes; -        mBatteryStats.setBatteryResetListener(this::prepareForBatteryStatsReset); -        mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(mContext, mBatteryStats); -    } - -    /** -     * Notifies BatteryUsageStatsStore that the system server is ready. -     */ -    public void onSystemReady() { -        mSystemReady = true; -    } - -    private void prepareForBatteryStatsReset(int resetReason) { -        if (resetReason == BatteryStatsImpl.RESET_REASON_CORRUPT_FILE || !mSystemReady) { -            return; -        } - -        final List<BatteryUsageStats> stats = -                mBatteryUsageStatsProvider.getBatteryUsageStats(BATTERY_USAGE_STATS_QUERY); -        if (stats.isEmpty()) { -            Slog.wtf(TAG, "No battery usage stats generated"); -            return; -        } - -        mHandler.post(() -> storeBatteryUsageStats(stats.get(0))); -    } - -    private void storeBatteryUsageStats(BatteryUsageStats stats) { -        lockSnapshotDirectory(); -        try { -            if (!mStoreDir.exists()) { -                if (!mStoreDir.mkdirs()) { -                    Slog.e(TAG, "Could not create a directory for battery usage stats snapshots"); -                    return; -                } -            } -            File file = makeSnapshotFilename(stats.getStatsEndTimestamp()); -            try { -                writeXmlFileLocked(stats, file); -            } catch (Exception e) { -                Slog.e(TAG, "Cannot save battery usage stats", e); -            } - -            removeOldSnapshotsLocked(); -        } finally { -            unlockSnapshotDirectory(); -        } -    } - -    /** -     * Returns the timestamps of the stored BatteryUsageStats snapshots. The timestamp corresponds -     * to the time the snapshot was taken {@link BatteryUsageStats#getStatsEndTimestamp()}. -     */ -    public long[] listBatteryUsageStatsTimestamps() { -        LongArray timestamps = new LongArray(100); -        lockSnapshotDirectory(); -        try { -            for (File file : mStoreDir.listFiles()) { -                String fileName = file.getName(); -                if (fileName.endsWith(SNAPSHOT_FILE_EXTENSION)) { -                    try { -                        String fileNameWithoutExtension = fileName.substring(0, -                                fileName.length() - SNAPSHOT_FILE_EXTENSION.length()); -                        timestamps.add(Long.parseLong(fileNameWithoutExtension)); -                    } catch (NumberFormatException e) { -                        Slog.wtf(TAG, "Invalid format of BatteryUsageStats snapshot file name: " -                                + fileName); -                    } -                } -            } -        } finally { -            unlockSnapshotDirectory(); -        } -        return timestamps.toArray(); -    } - -    /** -     * Reads the specified snapshot of BatteryUsageStats.  Returns null if the snapshot -     * does not exist. -     */ -    @Nullable -    public BatteryUsageStats loadBatteryUsageStats(long timestamp) { -        lockSnapshotDirectory(); -        try { -            File file = makeSnapshotFilename(timestamp); -            try { -                return readXmlFileLocked(file); -            } catch (Exception e) { -                Slog.e(TAG, "Cannot read battery usage stats", e); -            } -        } finally { -            unlockSnapshotDirectory(); -        } -        return null; -    } - -    /** -     * Saves the supplied timestamp of the BATTERY_USAGE_STATS_BEFORE_RESET statsd atom pull -     * in persistent file. -     */ -    public void setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(long timestamp) { -        Properties props = new Properties(); -        lockSnapshotDirectory(); -        try { -            try (InputStream in = mConfigFile.openRead()) { -                props.load(in); -            } catch (IOException e) { -                Slog.e(TAG, "Cannot load config file " + mConfigFile, e); -            } -            props.put(BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY, -                    String.valueOf(timestamp)); -            FileOutputStream out = null; -            try { -                out = mConfigFile.startWrite(); -                props.store(out, "Statsd atom pull timestamps"); -                mConfigFile.finishWrite(out); -            } catch (IOException e) { -                mConfigFile.failWrite(out); -                Slog.e(TAG, "Cannot save config file " + mConfigFile, e); -            } -        } finally { -            unlockSnapshotDirectory(); -        } -    } - -    /** -     * Retrieves the previously saved timestamp of the last BATTERY_USAGE_STATS_BEFORE_RESET -     * statsd atom pull. -     */ -    public long getLastBatteryUsageStatsBeforeResetAtomPullTimestamp() { -        Properties props = new Properties(); -        lockSnapshotDirectory(); -        try { -            try (InputStream in = mConfigFile.openRead()) { -                props.load(in); -            } catch (IOException e) { -                Slog.e(TAG, "Cannot load config file " + mConfigFile, e); -            } -        } finally { -            unlockSnapshotDirectory(); -        } -        return Long.parseLong( -                props.getProperty(BATTERY_USAGE_STATS_BEFORE_RESET_TIMESTAMP_PROPERTY, "0")); -    } - -    private void lockSnapshotDirectory() { -        mFileLock.lock(); - -        // Lock the directory from access by other JVMs -        try { -            mLockFile.getParentFile().mkdirs(); -            mLockFile.createNewFile(); -            mJvmLock = FileChannel.open(mLockFile.toPath(), StandardOpenOption.WRITE).lock(); -        } catch (IOException e) { -            Log.e(TAG, "Cannot lock snapshot directory", e); -        } -    } - -    private void unlockSnapshotDirectory() { -        try { -            mJvmLock.close(); -        } catch (IOException e) { -            Log.e(TAG, "Cannot unlock snapshot directory", e); -        } finally { -            mFileLock.unlock(); -        } -    } - -    /** -     * Creates a file name by formatting the timestamp as 19-digit zero-padded number. -     * This ensures that sorted directory list follows the chronological order. -     */ -    private File makeSnapshotFilename(long statsEndTimestamp) { -        return new File(mStoreDir, String.format(Locale.ENGLISH, "%019d", statsEndTimestamp) -                + SNAPSHOT_FILE_EXTENSION); -    } - -    private void writeXmlFileLocked(BatteryUsageStats stats, File file) throws IOException { -        try (OutputStream out = new FileOutputStream(file)) { -            TypedXmlSerializer serializer = Xml.newBinarySerializer(); -            serializer.setOutput(out, StandardCharsets.UTF_8.name()); -            serializer.startDocument(null, true); -            stats.writeXml(serializer); -            serializer.endDocument(); -        } -    } - -    private BatteryUsageStats readXmlFileLocked(File file) -            throws IOException, XmlPullParserException { -        try (InputStream in = new FileInputStream(file)) { -            TypedXmlPullParser parser = Xml.newBinaryPullParser(); -            parser.setInput(in, StandardCharsets.UTF_8.name()); -            return BatteryUsageStats.createFromXml(parser); -        } -    } - -    private void removeOldSnapshotsLocked() { -        // Read the directory list into a _sorted_ map.  The alphanumeric ordering -        // corresponds to the historical order of snapshots because the file names -        // are timestamps zero-padded to the same length. -        long totalSize = 0; -        TreeMap<File, Long> mFileSizes = new TreeMap<>(); -        for (File file : mStoreDir.listFiles()) { -            final long fileSize = file.length(); -            totalSize += fileSize; -            if (file.getName().endsWith(SNAPSHOT_FILE_EXTENSION)) { -                mFileSizes.put(file, fileSize); -            } -        } - -        while (totalSize > mMaxStorageBytes) { -            final Map.Entry<File, Long> entry = mFileSizes.firstEntry(); -            if (entry == null) { -                break; -            } - -            File file = entry.getKey(); -            if (!file.delete()) { -                Slog.e(TAG, "Cannot delete battery usage stats " + file); -            } -            totalSize -= entry.getValue(); -            mFileSizes.remove(file); -        } -    } - -    public void removeAllSnapshots() { -        lockSnapshotDirectory(); -        try { -            for (File file : mStoreDir.listFiles()) { -                if (file.getName().endsWith(SNAPSHOT_FILE_EXTENSION)) { -                    if (!file.delete()) { -                        Slog.e(TAG, "Cannot delete battery usage stats " + file); -                    } -                } -            } -        } finally { -            unlockSnapshotDirectory(); -        } -    } -} diff --git a/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStats.java index 5b3fe064d79a..fbf692800e52 100644 --- a/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStats.java +++ b/services/core/java/com/android/server/power/stats/CpuAggregatedPowerStats.java @@ -16,14 +16,8 @@  package com.android.server.power.stats; -import android.os.BatteryConsumer; - -import com.android.internal.os.MultiStateStats; -  class CpuAggregatedPowerStats extends PowerComponentAggregatedPowerStats { - -    CpuAggregatedPowerStats(MultiStateStats.States[] deviceStates, -            MultiStateStats.States[] uidStates) { -        super(BatteryConsumer.POWER_COMPONENT_CPU, deviceStates, uidStates); +    CpuAggregatedPowerStats(AggregatedPowerStatsConfig.PowerComponent config) { +        super(config);      }  } diff --git a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java index 686268fea22b..05c0a13642b4 100644 --- a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java +++ b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java @@ -21,7 +21,13 @@ import android.util.SparseArray;  import com.android.internal.os.MultiStateStats;  import com.android.internal.os.PowerStats; +import com.android.modules.utils.TypedXmlPullParser; +import com.android.modules.utils.TypedXmlSerializer; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException;  import java.util.Collection;  /** @@ -31,6 +37,12 @@ import java.util.Collection;   * as part of the {@link PowerStats.Descriptor}.   */  class PowerComponentAggregatedPowerStats { +    static final String XML_TAG_POWER_COMPONENT = "power_component"; +    static final String XML_ATTR_ID = "id"; +    private static final String XML_TAG_DEVICE_STATS = "device-stats"; +    private static final String XML_TAG_UID_STATS = "uid-stats"; +    private static final String XML_ATTR_UID = "uid"; +      public final int powerComponentId;      private final MultiStateStats.States[] mDeviceStateConfig;      private final MultiStateStats.States[] mUidStateConfig; @@ -49,22 +61,24 @@ class PowerComponentAggregatedPowerStats {          public MultiStateStats stats;      } -    PowerComponentAggregatedPowerStats(int powerComponentId, -            MultiStateStats.States[] deviceStates, -            MultiStateStats.States[] uidStates) { -        this.powerComponentId = powerComponentId; -        mDeviceStateConfig = deviceStates; -        mUidStateConfig = uidStates; +    PowerComponentAggregatedPowerStats(AggregatedPowerStatsConfig.PowerComponent config) { +        this.powerComponentId = config.getPowerComponentId(); +        mDeviceStateConfig = config.getDeviceStateConfig(); +        mUidStateConfig = config.getUidStateConfig();          mDeviceStates = new int[mDeviceStateConfig.length];          mDeviceStateTimestamps = new long[mDeviceStateConfig.length];      } -    void setState(@PowerStatsAggregator.TrackedState int stateId, int state, long time) { +    public PowerStats.Descriptor getPowerStatsDescriptor() { +        return mPowerStatsDescriptor; +    } + +    void setState(@AggregatedPowerStatsConfig.TrackedState int stateId, int state, long time) {          mDeviceStates[stateId] = state;          mDeviceStateTimestamps[stateId] = time;          if (mDeviceStateConfig[stateId].isTracked()) { -            if (mDeviceStats != null || createDeviceStats()) { +            if (mDeviceStats != null) {                  mDeviceStats.setState(stateId, state, time);              }          } @@ -72,14 +86,14 @@ class PowerComponentAggregatedPowerStats {          if (mUidStateConfig[stateId].isTracked()) {              for (int i = mUidStats.size() - 1; i >= 0; i--) {                  PowerComponentAggregatedPowerStats.UidStats uidStats = mUidStats.valueAt(i); -                if (uidStats.stats != null || createUidStats(uidStats)) { +                if (uidStats.stats != null) {                      uidStats.stats.setState(stateId, state, time);                  }              }          }      } -    void setUidState(int uid, @PowerStatsAggregator.TrackedState int stateId, int state, +    void setUidState(int uid, @AggregatedPowerStatsConfig.TrackedState int stateId, int state,              long time) {          if (!mUidStateConfig[stateId].isTracked()) {              return; @@ -89,7 +103,7 @@ class PowerComponentAggregatedPowerStats {          uidStats.states[stateId] = state;          uidStats.stateTimestampMs[stateId] = time; -        if (uidStats.stats != null || createUidStats(uidStats)) { +        if (uidStats.stats != null) {              uidStats.stats.setState(stateId, state, time);          }      } @@ -102,13 +116,6 @@ class PowerComponentAggregatedPowerStats {          mPowerStatsDescriptor = powerStats.descriptor;          if (mDeviceStats == null) { -            if (mStatsFactory == null) { -                mStatsFactory = new MultiStateStats.Factory( -                        mPowerStatsDescriptor.statsArrayLength, mDeviceStateConfig); -                mUidStatsFactory = new MultiStateStats.Factory( -                        mPowerStatsDescriptor.uidStatsArrayLength, mUidStateConfig); -            } -              createDeviceStats();          } @@ -183,7 +190,11 @@ class PowerComponentAggregatedPowerStats {      private boolean createDeviceStats() {          if (mStatsFactory == null) { -            return false; +            if (mPowerStatsDescriptor == null) { +                return false; +            } +            mStatsFactory = new MultiStateStats.Factory( +                    mPowerStatsDescriptor.statsArrayLength, mDeviceStateConfig);          }          mDeviceStats = mStatsFactory.create(); @@ -196,7 +207,11 @@ class PowerComponentAggregatedPowerStats {      private boolean createUidStats(UidStats uidStats) {          if (mUidStatsFactory == null) { -            return false; +            if (mPowerStatsDescriptor == null) { +                return false; +            } +            mUidStatsFactory = new MultiStateStats.Factory( +                    mPowerStatsDescriptor.uidStatsArrayLength, mUidStateConfig);          }          uidStats.stats = mUidStatsFactory.create(); @@ -211,6 +226,74 @@ class PowerComponentAggregatedPowerStats {          return true;      } +    public void writeXml(TypedXmlSerializer serializer) throws IOException { +        // No stats aggregated - can skip writing XML altogether +        if (mPowerStatsDescriptor == null) { +            return; +        } + +        serializer.startTag(null, XML_TAG_POWER_COMPONENT); +        serializer.attributeInt(null, XML_ATTR_ID, powerComponentId); +        mPowerStatsDescriptor.writeXml(serializer); + +        if (mDeviceStats != null) { +            serializer.startTag(null, XML_TAG_DEVICE_STATS); +            mDeviceStats.writeXml(serializer); +            serializer.endTag(null, XML_TAG_DEVICE_STATS); +        } + +        for (int i = mUidStats.size() - 1; i >= 0; i--) { +            int uid = mUidStats.keyAt(i); +            UidStats uidStats = mUidStats.valueAt(i); +            if (uidStats.stats != null) { +                serializer.startTag(null, XML_TAG_UID_STATS); +                serializer.attributeInt(null, XML_ATTR_UID, uid); +                uidStats.stats.writeXml(serializer); +                serializer.endTag(null, XML_TAG_UID_STATS); +            } +        } + +        serializer.endTag(null, XML_TAG_POWER_COMPONENT); +        serializer.flush(); +    } + +    public boolean readFromXml(TypedXmlPullParser parser) throws XmlPullParserException, +            IOException { +        int eventType = parser.getEventType(); +        while (eventType != XmlPullParser.END_DOCUMENT) { +            if (eventType == XmlPullParser.START_TAG) { +                switch (parser.getName()) { +                    case PowerStats.Descriptor.XML_TAG_DESCRIPTOR: +                        mPowerStatsDescriptor = PowerStats.Descriptor.createFromXml(parser); +                        if (mPowerStatsDescriptor == null) { +                            return false; +                        } +                        break; +                    case XML_TAG_DEVICE_STATS: +                        if (mDeviceStats == null) { +                            createDeviceStats(); +                        } +                        if (!mDeviceStats.readFromXml(parser)) { +                            return false; +                        } +                        break; +                    case XML_TAG_UID_STATS: +                        int uid = parser.getAttributeInt(null, XML_ATTR_UID); +                        UidStats uidStats = getUidStats(uid); +                        if (uidStats.stats == null) { +                            createUidStats(uidStats); +                        } +                        if (!uidStats.stats.readFromXml(parser)) { +                            return false; +                        } +                        break; +                } +            } +            eventType = parser.next(); +        } +        return true; +    } +      void dumpDevice(IndentingPrintWriter ipw) {          if (mDeviceStats != null) {              ipw.println(mPowerStatsDescriptor.name); diff --git a/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java b/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java index 6a1c1da93163..f374fb77cae8 100644 --- a/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java +++ b/services/core/java/com/android/server/power/stats/PowerStatsAggregator.java @@ -15,59 +15,26 @@   */  package com.android.server.power.stats; -import android.annotation.IntDef; -import android.os.BatteryConsumer;  import android.os.BatteryStats;  import com.android.internal.os.BatteryStatsHistory;  import com.android.internal.os.BatteryStatsHistoryIterator; -import com.android.internal.os.MultiStateStats; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; -import java.util.List;  import java.util.function.Consumer; -class PowerStatsAggregator { -    public static final int STATE_POWER = 0; -    public static final int STATE_SCREEN = 1; -    public static final int STATE_PROCESS_STATE = 2; - -    @IntDef({ -            STATE_POWER, -            STATE_SCREEN, -            STATE_PROCESS_STATE, -    }) -    @Retention(RetentionPolicy.SOURCE) -    public @interface TrackedState { -    } - -    static final int POWER_STATE_BATTERY = 0; -    static final int POWER_STATE_OTHER = 1;   // Plugged in, or on wireless charger, etc. -    static final String[] STATE_LABELS_POWER = {"pwr-battery", "pwr-other"}; - -    static final int SCREEN_STATE_ON = 0; -    static final int SCREEN_STATE_OTHER = 1;  // Off, doze etc -    static final String[] STATE_LABELS_SCREEN = {"scr-on", "scr-other"}; - -    static final String[] STATE_LABELS_PROCESS_STATE; - -    static { -        String[] procStateLabels = new String[BatteryConsumer.PROCESS_STATE_COUNT]; -        for (int i = 0; i < BatteryConsumer.PROCESS_STATE_COUNT; i++) { -            procStateLabels[i] = BatteryConsumer.processStateToString(i); -        } -        STATE_LABELS_PROCESS_STATE = procStateLabels; -    } - -    private final BatteryStatsHistory mHistory; +/** + * Power stats aggregator. It reads through portions of battery stats history, finds + * relevant items (state changes, power stats etc) and produces one or more + * {@link AggregatedPowerStats} that adds up power stats from the samples found in battery history. + */ +public class PowerStatsAggregator {      private final AggregatedPowerStats mStats; +    private final BatteryStatsHistory mHistory; -    private PowerStatsAggregator(BatteryStatsHistory history, -            AggregatedPowerStats aggregatedPowerStats) { +    public PowerStatsAggregator(AggregatedPowerStatsConfig aggregatedPowerStatsConfig, +            BatteryStatsHistory history) { +        mStats = new AggregatedPowerStats(aggregatedPowerStatsConfig);          mHistory = history; -        mStats = aggregatedPowerStats;      }      /** @@ -82,12 +49,10 @@ class PowerStatsAggregator {       * Note: the AggregatedPowerStats object is reused, so the consumer should fully consume       * the stats in the <code>accept</code> method and never cache it.       */ -    void aggregateBatteryStats(long startTimeMs, long endTimeMs, +    public void aggregatePowerStats(long startTimeMs, long endTimeMs,              Consumer<AggregatedPowerStats> consumer) { -        mStats.reset(); - -        int currentBatteryState = POWER_STATE_BATTERY; -        int currentScreenState = SCREEN_STATE_OTHER; +        int currentBatteryState = AggregatedPowerStatsConfig.POWER_STATE_BATTERY; +        int currentScreenState = AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;          long baseTime = -1;          long lastTime = 0;          try (BatteryStatsHistoryIterator iterator = @@ -96,131 +61,60 @@ class PowerStatsAggregator {                  BatteryStats.HistoryItem item = iterator.next();                  if (baseTime < 0) { -                    mStats.setStartTime(item.currentTime); +                    mStats.addClockUpdate(item.time, item.currentTime);                      baseTime = item.time; +                } else if (item.cmd == BatteryStats.HistoryItem.CMD_CURRENT_TIME +                           || item.cmd == BatteryStats.HistoryItem.CMD_RESET) { +                    mStats.addClockUpdate(item.time, item.currentTime);                  }                  lastTime = item.time;                  int batteryState =                          (item.states & BatteryStats.HistoryItem.STATE_BATTERY_PLUGGED_FLAG) != 0 -                                ? POWER_STATE_OTHER : POWER_STATE_BATTERY; +                                ? AggregatedPowerStatsConfig.POWER_STATE_OTHER +                                : AggregatedPowerStatsConfig.POWER_STATE_BATTERY;                  if (batteryState != currentBatteryState) { -                    mStats.setDeviceState(STATE_POWER, batteryState, item.time); +                    mStats.setDeviceState(AggregatedPowerStatsConfig.STATE_POWER, batteryState, +                            item.time);                      currentBatteryState = batteryState;                  }                  int screenState =                          (item.states & BatteryStats.HistoryItem.STATE_SCREEN_ON_FLAG) != 0 -                                ? SCREEN_STATE_ON : SCREEN_STATE_OTHER; +                                ? AggregatedPowerStatsConfig.SCREEN_STATE_ON +                                : AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;                  if (screenState != currentScreenState) { -                    mStats.setDeviceState(STATE_SCREEN, screenState, item.time); +                    mStats.setDeviceState(AggregatedPowerStatsConfig.STATE_SCREEN, screenState, +                            item.time);                      currentScreenState = screenState;                  }                  if (item.processStateChange != null) { -                    mStats.setUidState(item.processStateChange.uid, STATE_PROCESS_STATE, +                    mStats.setUidState(item.processStateChange.uid, +                            AggregatedPowerStatsConfig.STATE_PROCESS_STATE,                              item.processStateChange.processState, item.time);                  }                  if (item.powerStats != null) {                      if (!mStats.isCompatible(item.powerStats)) { -                        mStats.setDuration(lastTime - baseTime); -                        consumer.accept(mStats); +                        if (lastTime > baseTime) { +                            mStats.setDuration(lastTime - baseTime); +                            consumer.accept(mStats); +                        }                          mStats.reset(); -                        mStats.setStartTime(item.currentTime); +                        mStats.addClockUpdate(item.time, item.currentTime);                          baseTime = lastTime = item.time;                      }                      mStats.addPowerStats(item.powerStats, item.time);                  }              }          } -        mStats.setDuration(lastTime - baseTime); -        consumer.accept(mStats); -    } - -    static class Builder { -        static class PowerComponentAggregateStatsBuilder { -            private final int mPowerComponentId; -            private @TrackedState int[] mTrackedDeviceStates; -            private @TrackedState int[] mTrackedUidStates; - -            PowerComponentAggregateStatsBuilder(int powerComponentId) { -                this.mPowerComponentId = powerComponentId; -            } - -            public PowerComponentAggregateStatsBuilder trackDeviceStates( -                    @TrackedState int... states) { -                mTrackedDeviceStates = states; -                return this; -            } - -            public PowerComponentAggregateStatsBuilder trackUidStates(@TrackedState int... states) { -                mTrackedUidStates = states; -                return this; -            } - -            private PowerComponentAggregatedPowerStats build() { -                MultiStateStats.States[] deviceStates = new MultiStateStats.States[]{ -                        new MultiStateStats.States(isTracked(mTrackedDeviceStates, STATE_POWER), -                                PowerStatsAggregator.STATE_LABELS_POWER), -                        new MultiStateStats.States(isTracked(mTrackedDeviceStates, STATE_SCREEN), -                                PowerStatsAggregator.STATE_LABELS_SCREEN), -                }; - -                MultiStateStats.States[] uidStates = new MultiStateStats.States[]{ -                        new MultiStateStats.States(isTracked(mTrackedUidStates, STATE_POWER), -                                PowerStatsAggregator.STATE_LABELS_POWER), -                        new MultiStateStats.States(isTracked(mTrackedUidStates, STATE_SCREEN), -                                PowerStatsAggregator.STATE_LABELS_SCREEN), -                        new MultiStateStats.States( -                                isTracked(mTrackedUidStates, STATE_PROCESS_STATE), -                                PowerStatsAggregator.STATE_LABELS_PROCESS_STATE), -                }; - -                switch (mPowerComponentId) { -                    case BatteryConsumer.POWER_COMPONENT_CPU: -                        return new CpuAggregatedPowerStats(deviceStates, uidStates); -                    default: -                        return new PowerComponentAggregatedPowerStats(mPowerComponentId, -                                deviceStates, uidStates); -                } -            } - -            private boolean isTracked(int[] trackedStates, int state) { -                if (trackedStates == null) { -                    return false; -                } - -                for (int trackedState : trackedStates) { -                    if (trackedState == state) { -                        return true; -                    } -                } -                return false; -            } -        } - -        private final BatteryStatsHistory mHistory; -        private final List<PowerComponentAggregateStatsBuilder> mPowerComponents = -                new ArrayList<>(); - -        Builder(BatteryStatsHistory history) { -            mHistory = history; -        } - -        PowerComponentAggregateStatsBuilder trackPowerComponent(int powerComponentId) { -            PowerComponentAggregateStatsBuilder builder = new PowerComponentAggregateStatsBuilder( -                    powerComponentId); -            mPowerComponents.add(builder); -            return builder; +        if (lastTime > baseTime) { +            mStats.setDuration(lastTime - baseTime); +            consumer.accept(mStats);          } -        PowerStatsAggregator build() { -            return new PowerStatsAggregator(mHistory, new AggregatedPowerStats( -                    mPowerComponents.stream() -                            .map(PowerComponentAggregateStatsBuilder::build) -                            .toArray(PowerComponentAggregatedPowerStats[]::new))); -        } +        mStats.reset();     // to free up memory      }  } diff --git a/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java b/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java new file mode 100644 index 000000000000..58619c70e0af --- /dev/null +++ b/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power.stats; + +import android.annotation.DurationMillisLong; +import android.app.AlarmManager; +import android.content.Context; +import android.os.BatteryUsageStats; +import android.os.BatteryUsageStatsQuery; +import android.os.ConditionVariable; +import android.os.Handler; +import android.util.IndentingPrintWriter; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.os.Clock; +import com.android.internal.os.MonotonicClock; + +import java.io.PrintWriter; +import java.util.Calendar; +import java.util.concurrent.TimeUnit; + +/** + * Controls the frequency at which {@link PowerStatsSpan}'s are generated and stored in + * {@link PowerStatsStore}. + */ +public class PowerStatsScheduler { +    private static final long MINUTE_IN_MILLIS = TimeUnit.MINUTES.toMillis(1); +    private static final long HOUR_IN_MILLIS = TimeUnit.HOURS.toMillis(1); + +    private final Context mContext; +    private boolean mEnablePeriodicPowerStatsCollection; +    @DurationMillisLong +    private final long mAggregatedPowerStatsSpanDuration; +    @DurationMillisLong +    private final long mPowerStatsAggregationPeriod; +    private final PowerStatsStore mPowerStatsStore; +    private final Clock mClock; +    private final MonotonicClock mMonotonicClock; +    private final Handler mHandler; +    private final BatteryStatsImpl mBatteryStats; +    private final BatteryUsageStatsProvider mBatteryUsageStatsProvider; +    private final PowerStatsAggregator mPowerStatsAggregator; +    private long mLastSavedSpanEndMonotonicTime; + +    public PowerStatsScheduler(Context context, PowerStatsAggregator powerStatsAggregator, +            @DurationMillisLong long aggregatedPowerStatsSpanDuration, +            @DurationMillisLong long powerStatsAggregationPeriod, PowerStatsStore powerStatsStore, +            Clock clock, MonotonicClock monotonicClock, Handler handler, +            BatteryStatsImpl batteryStats, BatteryUsageStatsProvider batteryUsageStatsProvider) { +        mContext = context; +        mPowerStatsAggregator = powerStatsAggregator; +        mAggregatedPowerStatsSpanDuration = aggregatedPowerStatsSpanDuration; +        mPowerStatsAggregationPeriod = powerStatsAggregationPeriod; +        mPowerStatsStore = powerStatsStore; +        mClock = clock; +        mMonotonicClock = monotonicClock; +        mHandler = handler; +        mBatteryStats = batteryStats; +        mBatteryUsageStatsProvider = batteryUsageStatsProvider; +    } + +    /** +     * Kicks off the scheduling of power stats aggregation spans. +     */ +    public void start(boolean enablePeriodicPowerStatsCollection) { +        mBatteryStats.setBatteryResetListener(this::storeBatteryUsageStatsOnReset); +        mEnablePeriodicPowerStatsCollection = enablePeriodicPowerStatsCollection; +        if (mEnablePeriodicPowerStatsCollection) { +            scheduleNextPowerStatsAggregation(); +        } +    } + +    private void scheduleNextPowerStatsAggregation() { +        AlarmManager alarmManager = mContext.getSystemService(AlarmManager.class); +        alarmManager.set(AlarmManager.ELAPSED_REALTIME, +                mClock.elapsedRealtime() + mPowerStatsAggregationPeriod, "PowerStats", +                () -> { +                    schedulePowerStatsAggregation(); +                    mHandler.post(this::scheduleNextPowerStatsAggregation); +                }, mHandler); +    } + +    /** +     * Initiate an asynchronous process of aggregation of power stats. +     */ +    @VisibleForTesting +    public void schedulePowerStatsAggregation() { +        // Catch up the power stats collectors +        mBatteryStats.schedulePowerStatsSampleCollection(); +        mHandler.post(this::aggregateAndStorePowerStats); +    } + +    private void aggregateAndStorePowerStats() { +        long currentTimeMillis = mClock.currentTimeMillis(); +        long currentMonotonicTime = mMonotonicClock.monotonicTime(); +        long startTime = getLastSavedSpanEndMonotonicTime(); +        long endTimeMs = alignToWallClock(startTime + mAggregatedPowerStatsSpanDuration, +                mAggregatedPowerStatsSpanDuration, currentMonotonicTime, currentTimeMillis); +        while (endTimeMs <= currentMonotonicTime) { +            mPowerStatsAggregator.aggregatePowerStats(startTime, endTimeMs, +                    stats -> { +                        storeAggregatedPowerStats(stats); +                        mLastSavedSpanEndMonotonicTime = stats.getStartTime() + stats.getDuration(); +                    }); + +            startTime = endTimeMs; +            endTimeMs += mAggregatedPowerStatsSpanDuration; +        } +    } + +    /** +     * Performs a power stats aggregation pass and then dumps all stored aggregated power stats +     * spans followed by the remainder that has not been stored yet. +     */ +    public void aggregateAndDumpPowerStats(PrintWriter pw) { +        if (mHandler.getLooper().isCurrentThread()) { +            throw new IllegalStateException("Should not be executed on the bg handler thread."); +        } + +        schedulePowerStatsAggregation(); + +        // Wait for the aggregation process to finish storing aggregated stats spans in the store. +        awaitCompletion(); + +        IndentingPrintWriter ipw = new IndentingPrintWriter(pw); +        mHandler.post(() -> { +            mPowerStatsStore.dump(ipw); +            // Aggregate the remainder of power stats and dump the results without storing them yet. +            long powerStoreEndMonotonicTime = getLastSavedSpanEndMonotonicTime(); +            mPowerStatsAggregator.aggregatePowerStats(powerStoreEndMonotonicTime, 0, +                    stats -> { +                        // Create a PowerStatsSpan for consistency of the textual output +                        PowerStatsSpan span = PowerStatsStore.createPowerStatsSpan(stats); +                        if (span != null) { +                            span.dump(ipw); +                        } +                    }); +        }); + +        awaitCompletion(); +    } + +    /** +     * Align the supplied time to the wall clock, for aesthetic purposes. For example, if +     * the schedule is configured with a 15-min interval, the captured aggregated stats will +     * be for spans XX:00-XX:15, XX:15-XX:30, XX:30-XX:45 and XX:45-XX:60. Only the current +     * time is used for the alignment, so if the wall clock changed during an aggregation span, +     * or if the device was off (which stops the monotonic clock), the alignment may be +     * temporarily broken. +     */ +    @VisibleForTesting +    public static long alignToWallClock(long targetMonotonicTime, long interval, +            long currentMonotonicTime, long currentTimeMillis) { + +        // Estimate the wall clock time for the requested targetMonotonicTime +        long targetWallClockTime = currentTimeMillis + (targetMonotonicTime - currentMonotonicTime); + +        if (interval >= MINUTE_IN_MILLIS && TimeUnit.HOURS.toMillis(1) % interval == 0) { +            // If the interval is a divisor of an hour, e.g. 10 minutes, 15 minutes, etc + +            // First, round up to the next whole minute +            Calendar cal = Calendar.getInstance(); +            cal.setTimeInMillis(targetWallClockTime + MINUTE_IN_MILLIS - 1); +            cal.set(Calendar.SECOND, 0); +            cal.set(Calendar.MILLISECOND, 0); + +            // Now set the minute to a multiple of the requested interval +            int intervalInMinutes = (int) (interval / MINUTE_IN_MILLIS); +            cal.set(Calendar.MINUTE, +                    ((cal.get(Calendar.MINUTE) + intervalInMinutes - 1) / intervalInMinutes) +                            * intervalInMinutes); + +            long adjustment = cal.getTimeInMillis() - targetWallClockTime; +            return targetMonotonicTime + adjustment; +        } else if (interval >= HOUR_IN_MILLIS && TimeUnit.DAYS.toMillis(1) % interval == 0) { +            // If the interval is a divisor of a day, e.g. 2h, 3h, etc + +            // First, round up to the next whole hour +            Calendar cal = Calendar.getInstance(); +            cal.setTimeInMillis(targetWallClockTime + HOUR_IN_MILLIS - 1); +            cal.set(Calendar.MINUTE, 0); +            cal.set(Calendar.SECOND, 0); +            cal.set(Calendar.MILLISECOND, 0); + +            // Now set the hour of day to a multiple of the requested interval +            int intervalInHours = (int) (interval / HOUR_IN_MILLIS); +            cal.set(Calendar.HOUR_OF_DAY, +                    ((cal.get(Calendar.HOUR_OF_DAY) + intervalInHours - 1) / intervalInHours) +                    * intervalInHours); + +            long adjustment = cal.getTimeInMillis() - targetWallClockTime; +            return targetMonotonicTime + adjustment; +        } + +        return targetMonotonicTime; +    } + +    private long getLastSavedSpanEndMonotonicTime() { +        if (mLastSavedSpanEndMonotonicTime != 0) { +            return mLastSavedSpanEndMonotonicTime; +        } + +        for (PowerStatsSpan.Metadata metadata : mPowerStatsStore.getTableOfContents()) { +            if (metadata.getSections().contains(AggregatedPowerStatsSection.TYPE)) { +                for (PowerStatsSpan.TimeFrame timeFrame : metadata.getTimeFrames()) { +                    long endMonotonicTime = timeFrame.startMonotonicTime + timeFrame.duration; +                    if (endMonotonicTime > mLastSavedSpanEndMonotonicTime) { +                        mLastSavedSpanEndMonotonicTime = endMonotonicTime; +                    } +                } +            } +        } +        return mLastSavedSpanEndMonotonicTime; +    } + +    private void storeAggregatedPowerStats(AggregatedPowerStats stats) { +        mPowerStatsStore.storeAggregatedPowerStats(stats); +    } + +    private void storeBatteryUsageStatsOnReset(int resetReason) { +        if (resetReason == BatteryStatsImpl.RESET_REASON_CORRUPT_FILE) { +            return; +        } + +        final BatteryUsageStats batteryUsageStats = +                mBatteryUsageStatsProvider.getBatteryUsageStats( +                        new BatteryUsageStatsQuery.Builder() +                                .setMaxStatsAgeMs(0) +                                .includePowerModels() +                                .includeProcessStateData() +                                .build()); + +        // TODO(b/188068523): BatteryUsageStats should use monotonic time for start and end +        // Once that change is made, we will be able to use the BatteryUsageStats' monotonic +        // start time +        long monotonicStartTime = +                mMonotonicClock.monotonicTime() - batteryUsageStats.getStatsDuration(); +        mHandler.post(() -> +                mPowerStatsStore.storeBatteryUsageStats(monotonicStartTime, batteryUsageStats)); +    } + +    private void awaitCompletion() { +        ConditionVariable done = new ConditionVariable(); +        mHandler.post(done::open); +        done.block(); +    } +} diff --git a/services/core/java/com/android/server/power/stats/PowerStatsSpan.java b/services/core/java/com/android/server/power/stats/PowerStatsSpan.java new file mode 100644 index 000000000000..3b260ca0c3b9 --- /dev/null +++ b/services/core/java/com/android/server/power/stats/PowerStatsSpan.java @@ -0,0 +1,433 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power.stats; + +import android.annotation.CurrentTimeMillisLong; +import android.annotation.DurationMillisLong; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.util.IndentingPrintWriter; +import android.util.Slog; +import android.util.TimeUtils; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.modules.utils.TypedXmlPullParser; +import com.android.modules.utils.TypedXmlSerializer; + +import com.google.android.collect.Sets; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Set; + +/** + * Contains power stats of various kinds, aggregated over a time span. + */ +public class PowerStatsSpan { +    private static final String TAG = "PowerStatsStore"; + +    /** +     * Increment VERSION when the XML format of the store changes. Also, update +     * {@link #isCompatibleXmlFormat} to return true for all legacy versions +     * that are compatible with the new one. +     */ +    private static final int VERSION = 1; + +    private static final String XML_TAG_METADATA = "metadata"; +    private static final String XML_ATTR_ID = "id"; +    private static final String XML_ATTR_VERSION = "version"; +    private static final String XML_TAG_TIMEFRAME = "timeframe"; +    private static final String XML_ATTR_MONOTONIC = "monotonic"; +    private static final String XML_ATTR_START_TIME = "start"; +    private static final String XML_ATTR_DURATION = "duration"; +    private static final String XML_TAG_SECTION = "section"; +    private static final String XML_ATTR_SECTION_TYPE = "type"; + +    private static final DateTimeFormatter DATE_FORMAT = +            DateTimeFormatter.ofPattern("MM-dd HH:mm:ss.SSS").withZone(ZoneId.systemDefault()); + +    static class TimeFrame { +        public final long startMonotonicTime; +        @CurrentTimeMillisLong +        public final long startTime; +        @DurationMillisLong +        public final long duration; + +        TimeFrame(long startMonotonicTime, @CurrentTimeMillisLong long startTime, +                @DurationMillisLong long duration) { +            this.startMonotonicTime = startMonotonicTime; +            this.startTime = startTime; +            this.duration = duration; +        } + +        void write(TypedXmlSerializer serializer) throws IOException { +            serializer.startTag(null, XML_TAG_TIMEFRAME); +            serializer.attributeLong(null, XML_ATTR_START_TIME, startTime); +            serializer.attributeLong(null, XML_ATTR_MONOTONIC, startMonotonicTime); +            serializer.attributeLong(null, XML_ATTR_DURATION, duration); +            serializer.endTag(null, XML_TAG_TIMEFRAME); +        } + +        static TimeFrame read(TypedXmlPullParser parser) throws XmlPullParserException { +            return new TimeFrame( +                    parser.getAttributeLong(null, XML_ATTR_MONOTONIC), +                    parser.getAttributeLong(null, XML_ATTR_START_TIME), +                    parser.getAttributeLong(null, XML_ATTR_DURATION)); +        } + +        /** +         * Prints the contents of this TimeFrame. +         */ +        public void dump(IndentingPrintWriter pw) { +            StringBuilder sb = new StringBuilder(); +            sb.append(DATE_FORMAT.format(Instant.ofEpochMilli(startTime))) +                    .append(" (monotonic=").append(startMonotonicTime).append(") ") +                    .append(" duration="); +            String durationString = TimeUtils.formatDuration(duration); +            if (durationString.startsWith("+")) { +                sb.append(durationString.substring(1)); +            } else { +                sb.append(durationString); +            } +            pw.print(sb); +        } +    } + +    static class Metadata { +        static final Comparator<Metadata> COMPARATOR = Comparator.comparing(Metadata::getId); + +        private final long mId; +        private final List<TimeFrame> mTimeFrames = new ArrayList<>(); +        private final List<String> mSections = new ArrayList<>(); + +        Metadata(long id) { +            mId = id; +        } + +        public long getId() { +            return mId; +        } + +        public List<TimeFrame> getTimeFrames() { +            return mTimeFrames; +        } + +        public List<String> getSections() { +            return mSections; +        } + +        void addTimeFrame(TimeFrame timeFrame) { +            mTimeFrames.add(timeFrame); +        } + +        void addSection(String sectionType) { +            // The number of sections per span is small, so there is no need to use a Set +            if (!mSections.contains(sectionType)) { +                mSections.add(sectionType); +            } +        } + +        void write(TypedXmlSerializer serializer) throws IOException { +            serializer.startTag(null, XML_TAG_METADATA); +            serializer.attributeLong(null, XML_ATTR_ID, mId); +            serializer.attributeInt(null, XML_ATTR_VERSION, VERSION); +            for (TimeFrame timeFrame : mTimeFrames) { +                timeFrame.write(serializer); +            } +            for (String section : mSections) { +                serializer.startTag(null, XML_TAG_SECTION); +                serializer.attribute(null, XML_ATTR_SECTION_TYPE, section); +                serializer.endTag(null, XML_TAG_SECTION); +            } +            serializer.endTag(null, XML_TAG_METADATA); +        } + +        /** +         * Reads just the header of the XML file containing metadata. +         * Returns null if the file does not contain a compatible <metadata> element. +         */ +        @Nullable +        public static Metadata read(TypedXmlPullParser parser) +                throws IOException, XmlPullParserException { +            Metadata metadata = null; +            int eventType = parser.getEventType(); +            while (eventType != XmlPullParser.END_DOCUMENT +                   && !(eventType == XmlPullParser.END_TAG +                        && parser.getName().equals(XML_TAG_METADATA))) { +                if (eventType == XmlPullParser.START_TAG) { +                    String tagName = parser.getName(); +                    if (tagName.equals(XML_TAG_METADATA)) { +                        int version = parser.getAttributeInt(null, XML_ATTR_VERSION); +                        if (!isCompatibleXmlFormat(version)) { +                            Slog.e(TAG, +                                    "Incompatible version " + version + "; expected " + VERSION); +                            return null; +                        } + +                        long id = parser.getAttributeLong(null, XML_ATTR_ID); +                        metadata = new Metadata(id); +                    } else if (metadata != null && tagName.equals(XML_TAG_TIMEFRAME)) { +                        metadata.addTimeFrame(TimeFrame.read(parser)); +                    } else if (metadata != null && tagName.equals(XML_TAG_SECTION)) { +                        metadata.addSection(parser.getAttributeValue(null, XML_ATTR_SECTION_TYPE)); +                    } +                } +                eventType = parser.next(); +            } +            return metadata; +        } + +        /** +         * Prints the metadata. +         */ +        public void dump(IndentingPrintWriter pw) { +            dump(pw, true); +        } + +        void dump(IndentingPrintWriter pw, boolean includeSections) { +            pw.print("Span "); +            if (mTimeFrames.size() > 0) { +                mTimeFrames.get(0).dump(pw); +                pw.println(); +            } + +            // Sometimes, when the wall clock is adjusted in the middle of a stats session, +            // we will have more than one time frame. +            for (int i = 1; i < mTimeFrames.size(); i++) { +                TimeFrame timeFrame = mTimeFrames.get(i); +                pw.print("     ");      // Aligned below "Span " +                timeFrame.dump(pw); +                pw.println(); +            } + +            if (includeSections) { +                pw.increaseIndent(); +                for (String section : mSections) { +                    pw.print("section", section); +                    pw.println(); +                } +                pw.decreaseIndent(); +            } +        } + +        @Override +        public String toString() { +            StringWriter sw = new StringWriter(); +            IndentingPrintWriter ipw = new IndentingPrintWriter(sw); +            ipw.print("id", mId); +            for (int i = 0; i < mTimeFrames.size(); i++) { +                TimeFrame timeFrame = mTimeFrames.get(i); +                ipw.print("timeframe=["); +                timeFrame.dump(ipw); +                ipw.print("] "); +            } +            for (String section : mSections) { +                ipw.print("section", section); +            } +            ipw.flush(); +            return sw.toString().trim(); +        } +    } + +    /** +     * Contains a specific type of aggregate power stats.  The contents type is determined by +     * the section type. +     */ +    public abstract static class Section { +        private final String mType; + +        Section(String type) { +            mType = type; +        } + +        /** +         * Returns the section type, which determines the type of data stored in the corresponding +         * section of {@link PowerStatsSpan} +         */ +        public String getType() { +            return mType; +        } + +        abstract void write(TypedXmlSerializer serializer) throws IOException; + +        /** +         * Prints the section type. +         */ +        public void dump(IndentingPrintWriter ipw) { +            ipw.println(mType); +        } +    } + +    /** +     * A universal XML parser for {@link PowerStatsSpan.Section}'s.  It is aware of all +     * supported section types as well as their corresponding XML formats. +     */ +    public interface SectionReader { +        /** +         * Reads the contents of the section using the parser. The type of the object +         * read and the corresponding XML format are determined by the section type. +         */ +        Section read(String sectionType, TypedXmlPullParser parser) +                throws IOException, XmlPullParserException; +    } + +    private final Metadata mMetadata; +    private final List<Section> mSections = new ArrayList<>(); + +    public PowerStatsSpan(long id) { +        this(new Metadata(id)); +    } + +    private PowerStatsSpan(Metadata metadata) { +        mMetadata = metadata; +    } + +    public Metadata getMetadata() { +        return mMetadata; +    } + +    public long getId() { +        return mMetadata.mId; +    } + +    void addTimeFrame(long monotonicTime, @CurrentTimeMillisLong long wallClockTime, +            @DurationMillisLong long duration) { +        mMetadata.mTimeFrames.add(new TimeFrame(monotonicTime, wallClockTime, duration)); +    } + +    void addSection(Section section) { +        mMetadata.addSection(section.getType()); +        mSections.add(section); +    } + +    @NonNull +    public List<Section> getSections() { +        return mSections; +    } + +    private static boolean isCompatibleXmlFormat(int version) { +        return version == VERSION; +    } + +    /** +     * Creates an XML file containing the persistent state of the power stats span. +     */ +    @VisibleForTesting +    public void writeXml(OutputStream out, TypedXmlSerializer serializer) throws IOException { +        serializer.setOutput(out, StandardCharsets.UTF_8.name()); +        serializer.startDocument(null, true); +        mMetadata.write(serializer); +        for (Section section : mSections) { +            serializer.startTag(null, XML_TAG_SECTION); +            serializer.attribute(null, XML_ATTR_SECTION_TYPE, section.mType); +            section.write(serializer); +            serializer.endTag(null, XML_TAG_SECTION); +        } +        serializer.endDocument(); +    } + +    @Nullable +    static PowerStatsSpan read(InputStream in, TypedXmlPullParser parser, +            SectionReader sectionReader, String... sectionTypes) +            throws IOException, XmlPullParserException { +        Set<String> neededSections = Sets.newArraySet(sectionTypes); +        boolean selectSections = !neededSections.isEmpty(); +        parser.setInput(in, StandardCharsets.UTF_8.name()); + +        Metadata metadata = Metadata.read(parser); +        if (metadata == null) { +            return null; +        } + +        PowerStatsSpan span = new PowerStatsSpan(metadata); +        boolean skipSection = false; +        int nestingLevel = 0; +        int eventType = parser.getEventType(); +        while (eventType != XmlPullParser.END_DOCUMENT) { +            if (skipSection) { +                if (eventType == XmlPullParser.END_TAG +                        && parser.getName().equals(XML_TAG_SECTION)) { +                    nestingLevel--; +                    if (nestingLevel == 0) { +                        skipSection = false; +                    } +                } else if (eventType == XmlPullParser.START_TAG +                           && parser.getName().equals(XML_TAG_SECTION)) { +                    nestingLevel++; +                } +            } else if (eventType == XmlPullParser.START_TAG) { +                String tag = parser.getName(); +                if (tag.equals(XML_TAG_SECTION)) { +                    String sectionType = parser.getAttributeValue(null, XML_ATTR_SECTION_TYPE); +                    if (!selectSections || neededSections.contains(sectionType)) { +                        Section section = sectionReader.read(sectionType, parser); +                        if (section == null) { +                            if (selectSections) { +                                throw new XmlPullParserException( +                                        "Unsupported PowerStatsStore section type: " + sectionType); +                            } else { +                                section = new Section(sectionType) { +                                    @Override +                                    public void dump(IndentingPrintWriter ipw) { +                                        ipw.println("Unsupported PowerStatsStore section type: " +                                                    + sectionType); +                                    } + +                                    @Override +                                    void write(TypedXmlSerializer serializer) { +                                    } +                                }; +                            } +                        } +                        span.addSection(section); +                    } else { +                        skipSection = true; +                    } +                } else if (tag.equals(XML_TAG_METADATA)) { +                    Metadata.read(parser); +                } +            } +            eventType = parser.next(); +        } +        return span; +    } + +    /** +     * Prints the contents of this power stats span. +     */ +    public void dump(IndentingPrintWriter ipw) { +        mMetadata.dump(ipw, /* includeSections */ false); +        for (Section section : mSections) { +            ipw.increaseIndent(); +            ipw.println(section.mType); +            section.dump(ipw); +            ipw.decreaseIndent(); +        } +    } +} diff --git a/services/core/java/com/android/server/power/stats/PowerStatsStore.java b/services/core/java/com/android/server/power/stats/PowerStatsStore.java new file mode 100644 index 000000000000..7123bcb2d095 --- /dev/null +++ b/services/core/java/com/android/server/power/stats/PowerStatsStore.java @@ -0,0 +1,371 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power.stats; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.BatteryUsageStats; +import android.os.FileUtils; +import android.os.Handler; +import android.util.AtomicFile; +import android.util.IndentingPrintWriter; +import android.util.Slog; +import android.util.Xml; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.modules.utils.TypedXmlPullParser; + +import org.xmlpull.v1.XmlPullParserException; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.charset.StandardCharsets; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.locks.ReentrantLock; + +/** + * A storage mechanism for aggregated power/battery stats. + */ +public class PowerStatsStore { +    private static final String TAG = "PowerStatsStore"; + +    private static final String POWER_STATS_DIR = "power-stats"; +    private static final String POWER_STATS_SPAN_FILE_EXTENSION = ".pss"; +    private static final String DIR_LOCK_FILENAME = ".lock"; +    private static final long MAX_POWER_STATS_SPAN_STORAGE_BYTES = 100 * 1024; + +    private final File mSystemDir; +    private final File mStoreDir; +    private final File mLockFile; +    private final ReentrantLock mFileLock = new ReentrantLock(); +    private FileLock mJvmLock; +    private final long mMaxStorageBytes; +    private final Handler mHandler; +    private final PowerStatsSpan.SectionReader mSectionReader; +    private volatile List<PowerStatsSpan.Metadata> mTableOfContents; + +    public PowerStatsStore(@NonNull File systemDir, Handler handler, +            AggregatedPowerStatsConfig aggregatedPowerStatsConfig) { +        this(systemDir, MAX_POWER_STATS_SPAN_STORAGE_BYTES, handler, +                new DefaultSectionReader(aggregatedPowerStatsConfig)); +    } + +    @VisibleForTesting +    public PowerStatsStore(@NonNull File systemDir, long maxStorageBytes, Handler handler, +            @NonNull PowerStatsSpan.SectionReader sectionReader) { +        mSystemDir = systemDir; +        mStoreDir = new File(systemDir, POWER_STATS_DIR); +        mLockFile = new File(mStoreDir, DIR_LOCK_FILENAME); +        mHandler = handler; +        mMaxStorageBytes = maxStorageBytes; +        mSectionReader = sectionReader; +        mHandler.post(this::maybeClearLegacyStore); +    } + +    /** +     * Returns the metadata for all {@link PowerStatsSpan}'s contained in the store. +     */ +    @NonNull +    public List<PowerStatsSpan.Metadata> getTableOfContents() { +        List<PowerStatsSpan.Metadata> toc = mTableOfContents; +        if (toc != null) { +            return toc; +        } + +        TypedXmlPullParser parser = Xml.newBinaryPullParser(); +        lockStoreDirectory(); +        try { +            toc = new ArrayList<>(); +            for (File file : mStoreDir.listFiles()) { +                String fileName = file.getName(); +                if (!fileName.endsWith(POWER_STATS_SPAN_FILE_EXTENSION)) { +                    continue; +                } +                try (InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) { +                    parser.setInput(inputStream, StandardCharsets.UTF_8.name()); +                    PowerStatsSpan.Metadata metadata = PowerStatsSpan.Metadata.read(parser); +                    if (metadata != null) { +                        toc.add(metadata); +                    } else { +                        Slog.e(TAG, "Removing incompatible PowerStatsSpan file: " + fileName); +                        file.delete(); +                    } +                } catch (IOException | XmlPullParserException e) { +                    Slog.wtf(TAG, "Cannot read PowerStatsSpan file: " + fileName); +                } +            } +            toc.sort(PowerStatsSpan.Metadata.COMPARATOR); +            mTableOfContents = Collections.unmodifiableList(toc); +        } finally { +            unlockStoreDirectory(); +        } + +        return toc; +    } + +    /** +     * Saves the specified span in the store. +     */ +    public void storePowerStatsSpan(PowerStatsSpan span) { +        maybeClearLegacyStore(); +        lockStoreDirectory(); +        try { +            if (!mStoreDir.exists()) { +                if (!mStoreDir.mkdirs()) { +                    Slog.e(TAG, "Could not create a directory for power stats store"); +                    return; +                } +            } + +            AtomicFile file = new AtomicFile(makePowerStatsSpanFilename(span.getId())); +            file.write(out-> { +                try { +                    span.writeXml(out, Xml.newBinarySerializer()); +                } catch (Exception e) { +                    // AtomicFile will log the exception and delete the file. +                    throw new RuntimeException(e); +                } +            }); +            mTableOfContents = null; +            removeOldSpansLocked(); +        } finally { +            unlockStoreDirectory(); +        } +    } + +    /** +     * Loads the PowerStatsSpan identified by its ID. Only loads the sections with +     * the specified types.  Loads all sections if no sectionTypes is empty. +     */ +    @Nullable +    public PowerStatsSpan loadPowerStatsSpan(long id, String... sectionTypes) { +        TypedXmlPullParser parser = Xml.newBinaryPullParser(); +        lockStoreDirectory(); +        try { +            File file = makePowerStatsSpanFilename(id); +            try (InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) { +                return PowerStatsSpan.read(inputStream, parser, mSectionReader, sectionTypes); +            } catch (IOException | XmlPullParserException e) { +                Slog.wtf(TAG, "Cannot read PowerStatsSpan file: " + file); +            } +        } finally { +            unlockStoreDirectory(); +        } +        return null; +    } + +    void storeAggregatedPowerStats(AggregatedPowerStats stats) { +        PowerStatsSpan span = createPowerStatsSpan(stats); +        if (span == null) { +            return; +        } +        storePowerStatsSpan(span); +    } + +    static PowerStatsSpan createPowerStatsSpan(AggregatedPowerStats stats) { +        List<AggregatedPowerStats.ClockUpdate> clockUpdates = stats.getClockUpdates(); +        if (clockUpdates.isEmpty()) { +            Slog.w(TAG, "No clock updates in aggregated power stats " + stats); +            return null; +        } + +        long monotonicTime = clockUpdates.get(0).monotonicTime; +        long durationSum = 0; +        PowerStatsSpan span = new PowerStatsSpan(monotonicTime); +        for (int i = 0; i < clockUpdates.size(); i++) { +            AggregatedPowerStats.ClockUpdate clockUpdate = clockUpdates.get(i); +            long duration; +            if (i == clockUpdates.size() - 1) { +                duration = stats.getDuration() - durationSum; +            } else { +                duration = clockUpdate.monotonicTime - monotonicTime; +            } +            span.addTimeFrame(clockUpdate.monotonicTime, clockUpdate.currentTime, duration); +            monotonicTime = clockUpdate.monotonicTime; +            durationSum += duration; +        } + +        span.addSection(new AggregatedPowerStatsSection(stats)); +        return span; +    } + +    /** +     * Stores a {@link PowerStatsSpan} containing a single section for the supplied +     * battery usage stats. +     */ +    public void storeBatteryUsageStats(long monotonicStartTime, +            BatteryUsageStats batteryUsageStats) { +        PowerStatsSpan span = new PowerStatsSpan(monotonicStartTime); +        span.addTimeFrame(monotonicStartTime, batteryUsageStats.getStatsStartTimestamp(), +                batteryUsageStats.getStatsDuration()); +        span.addSection(new BatteryUsageStatsSection(batteryUsageStats)); +        storePowerStatsSpan(span); +    } + +    /** +     * Creates a file name by formatting the span ID as a 19-digit zero-padded number. +     * This ensures that the lexicographically sorted directory follows the chronological order. +     */ +    private File makePowerStatsSpanFilename(long id) { +        return new File(mStoreDir, String.format(Locale.ENGLISH, "%019d", id) +                                   + POWER_STATS_SPAN_FILE_EXTENSION); +    } + +    private void maybeClearLegacyStore() { +        File legacyStoreDir = new File(mSystemDir, "battery-usage-stats"); +        if (legacyStoreDir.exists()) { +            FileUtils.deleteContentsAndDir(legacyStoreDir); +        } +    } + +    private void lockStoreDirectory() { +        mFileLock.lock(); + +        // Lock the directory from access by other JVMs +        try { +            mLockFile.getParentFile().mkdirs(); +            mLockFile.createNewFile(); +            mJvmLock = FileChannel.open(mLockFile.toPath(), StandardOpenOption.WRITE).lock(); +        } catch (IOException e) { +            Slog.e(TAG, "Cannot lock snapshot directory", e); +        } +    } + +    private void unlockStoreDirectory() { +        try { +            mJvmLock.close(); +        } catch (IOException e) { +            Slog.e(TAG, "Cannot unlock snapshot directory", e); +        } finally { +            mFileLock.unlock(); +        } +    } + +    private void removeOldSpansLocked() { +        // Read the directory list into a _sorted_ map.  The alphanumeric ordering +        // corresponds to the historical order of snapshots because the file names +        // are timestamps zero-padded to the same length. +        long totalSize = 0; +        TreeMap<File, Long> mFileSizes = new TreeMap<>(); +        for (File file : mStoreDir.listFiles()) { +            final long fileSize = file.length(); +            totalSize += fileSize; +            if (file.getName().endsWith(POWER_STATS_SPAN_FILE_EXTENSION)) { +                mFileSizes.put(file, fileSize); +            } +        } + +        while (totalSize > mMaxStorageBytes) { +            final Map.Entry<File, Long> entry = mFileSizes.firstEntry(); +            if (entry == null) { +                break; +            } + +            File file = entry.getKey(); +            if (!file.delete()) { +                Slog.e(TAG, "Cannot delete power stats span " + file); +            } +            totalSize -= entry.getValue(); +            mFileSizes.remove(file); +            mTableOfContents = null; +        } +    } + +    /** +     * Deletes all contents from the store. +     */ +    public void reset() { +        lockStoreDirectory(); +        try { +            for (File file : mStoreDir.listFiles()) { +                if (file.getName().endsWith(POWER_STATS_SPAN_FILE_EXTENSION)) { +                    if (!file.delete()) { +                        Slog.e(TAG, "Cannot delete power stats span " + file); +                    } +                } +            } +            mTableOfContents = List.of(); +        } finally { +            unlockStoreDirectory(); +        } +    } + +    /** +     * Prints the summary of contents of the store: only metadata, but not the actual stored +     * objects. +     */ +    public void dumpTableOfContents(IndentingPrintWriter ipw) { +        ipw.println("Power stats store TOC"); +        ipw.increaseIndent(); +        List<PowerStatsSpan.Metadata> contents = getTableOfContents(); +        for (PowerStatsSpan.Metadata metadata : contents) { +            metadata.dump(ipw); +        } +        ipw.decreaseIndent(); +    } + +    /** +     * Prints the contents of the store. +     */ +    public void dump(IndentingPrintWriter ipw) { +        ipw.println("Power stats store"); +        ipw.increaseIndent(); +        List<PowerStatsSpan.Metadata> contents = getTableOfContents(); +        for (PowerStatsSpan.Metadata metadata : contents) { +            PowerStatsSpan span = loadPowerStatsSpan(metadata.getId()); +            if (span != null) { +                span.dump(ipw); +            } +        } +        ipw.decreaseIndent(); +    } + +    private static class DefaultSectionReader implements PowerStatsSpan.SectionReader { +        private final AggregatedPowerStatsConfig mAggregatedPowerStatsConfig; + +        DefaultSectionReader(AggregatedPowerStatsConfig aggregatedPowerStatsConfig) { +            mAggregatedPowerStatsConfig = aggregatedPowerStatsConfig; +        } + +        @Override +        public PowerStatsSpan.Section read(String sectionType, TypedXmlPullParser parser) +                throws IOException, XmlPullParserException { +            switch (sectionType) { +                case AggregatedPowerStatsSection.TYPE: +                    return new AggregatedPowerStatsSection( +                            AggregatedPowerStats.createFromXml(parser, +                                    mAggregatedPowerStatsConfig)); +                case BatteryUsageStatsSection.TYPE: +                    return new BatteryUsageStatsSection( +                            BatteryUsageStats.createFromXml(parser)); +                default: +                    return null; +            } +        } +    } +} diff --git a/services/core/java/com/android/server/power/stats/flags.aconfig b/services/core/java/com/android/server/power/stats/flags.aconfig index d61bebc0c82c..0f135715ebc3 100644 --- a/services/core/java/com/android/server/power/stats/flags.aconfig +++ b/services/core/java/com/android/server/power/stats/flags.aconfig @@ -1,8 +1,15 @@  package: "com.android.server.power.optimization"  flag { +    name: "power_monitor_api" +    namespace: "backstage_power" +    description: "Feature flag for ODPM API" +    bug: "295027807" +} + +flag {      name: "streamlined_battery_stats" -    namespace: "power_optimization" +    namespace: "backstage_power"      description: "Feature flag for streamlined battery stats"      bug: "285646152"  } diff --git a/services/core/java/com/android/server/powerstats/PowerStatsService.java b/services/core/java/com/android/server/powerstats/PowerStatsService.java index 5609f6975bc4..77290fd944eb 100644 --- a/services/core/java/com/android/server/powerstats/PowerStatsService.java +++ b/services/core/java/com/android/server/powerstats/PowerStatsService.java @@ -694,7 +694,7 @@ public class PowerStatsService extends SystemService {                      Log.d(TAG, String.format(Locale.ENGLISH,                              "Monitor=%s timestamp=%d energy=%d"                                      + " uid=%d noise=%.1f%% returned=%d", -                            state.powerMonitor.name, +                            state.powerMonitor.getName(),                              state.timestampMs,                              state.energyUws,                              callingUid, @@ -728,7 +728,7 @@ public class PowerStatsService extends SystemService {          }          for (PowerMonitorState powerMonitorState : powerMonitorStates) { -            if (powerMonitorState.powerMonitor.type +            if (powerMonitorState.powerMonitor.getType()                      == PowerMonitor.POWER_MONITOR_TYPE_CONSUMER) {                  for (EnergyConsumerResult energyConsumerResult : energyConsumerResults) {                      if (energyConsumerResult.id == powerMonitorState.id) { @@ -754,7 +754,7 @@ public class PowerStatsService extends SystemService {          }          for (PowerMonitorState powerMonitorState : powerMonitorStates) { -            if (powerMonitorState.powerMonitor.type +            if (powerMonitorState.powerMonitor.getType()                      == PowerMonitor.POWER_MONITOR_TYPE_MEASUREMENT) {                  for (EnergyMeasurement energyMeasurement : energyMeasurements) {                      if (energyMeasurement.id == powerMonitorState.id) { @@ -773,7 +773,7 @@ public class PowerStatsService extends SystemService {              @PowerMonitor.PowerMonitorType int type) {          int count = 0;          for (PowerMonitorState monitorState : powerMonitorStates) { -            if (monitorState.powerMonitor.type == type) { +            if (monitorState.powerMonitor.getType() == type) {                  count++;              }          } @@ -785,7 +785,7 @@ public class PowerStatsService extends SystemService {          int[] ids = new int[count];          int index = 0;          for (PowerMonitorState monitorState : powerMonitorStates) { -            if (monitorState.powerMonitor.type == type) { +            if (monitorState.powerMonitor.getType() == type) {                  ids[index++] = monitorState.id;              }          } diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java index c9db343697df..0656a6a7d3e9 100644 --- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java +++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java @@ -174,8 +174,6 @@ public final class SensorPrivacyService extends SystemService {      private CallStateHelper mCallStateHelper;      private KeyguardManager mKeyguardManager; -    private SafetyCenterManager mSafetyCenterManager; -      private int mCurrentUser = USER_NULL;      public SensorPrivacyService(Context context) { @@ -191,7 +189,6 @@ public final class SensorPrivacyService extends SystemService {          mTelephonyManager = context.getSystemService(TelephonyManager.class);          mPackageManagerInternal = getLocalService(PackageManagerInternal.class);          mSensorPrivacyServiceImpl = new SensorPrivacyServiceImpl(); -        mSafetyCenterManager = mContext.getSystemService(SafetyCenterManager.class);      }      @Override @@ -656,7 +653,9 @@ public final class SensorPrivacyService extends SystemService {              String contentTitle = getUiContext().getString(messageRes);              Spanned contentText = Html.fromHtml(getUiContext().getString(                      R.string.sensor_privacy_start_use_notification_content_text, packageLabel), 0); -            String action = mSafetyCenterManager.isSafetyCenterEnabled() +            SafetyCenterManager safetyCenterManager = +                    mContext.getSystemService(SafetyCenterManager.class); +            String action = safetyCenterManager.isSafetyCenterEnabled()                      ? Settings.ACTION_PRIVACY_CONTROLS : Settings.ACTION_PRIVACY_SETTINGS;              PendingIntent contentIntent = PendingIntent.getActivity(mContext, sensor, diff --git a/services/core/java/com/android/server/stats/OWNERS b/services/core/java/com/android/server/stats/OWNERS index 174ad3ad2e25..c33f3d9fa264 100644 --- a/services/core/java/com/android/server/stats/OWNERS +++ b/services/core/java/com/android/server/stats/OWNERS @@ -1,11 +1,10 @@  jeffreyhuang@google.com  joeo@google.com -jtnguyen@google.com +monicamwang@google.com  muhammadq@google.com +rayhdez@google.com  rslawik@google.com -ruchirr@google.com  sharaienko@google.com  singhtejinder@google.com  tsaichristine@google.com  yaochen@google.com -yro@google.com diff --git a/services/core/java/com/android/server/testharness/TestHarnessModeService.java b/services/core/java/com/android/server/testharness/TestHarnessModeService.java index bfe34049e1e5..9a9b83602832 100644 --- a/services/core/java/com/android/server/testharness/TestHarnessModeService.java +++ b/services/core/java/com/android/server/testharness/TestHarnessModeService.java @@ -41,8 +41,8 @@ import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;  import com.android.internal.notification.SystemNotificationChannels;  import com.android.internal.widget.LockPatternUtils;  import com.android.server.LocalServices; -import com.android.server.PersistentDataBlockManagerInternal;  import com.android.server.SystemService; +import com.android.server.pdb.PersistentDataBlockManagerInternal;  import com.android.server.pm.UserManagerInternal;  import java.io.ByteArrayInputStream; diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 01ea33f1aecd..4200fbf39ade 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -2843,7 +2843,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub          WallpaperData systemWallpaper = mWallpaperMap.get(mCurrentUserId);          WallpaperData lockWallpaper = mLockWallpaperMap.get(mCurrentUserId);          boolean systemValid = systemWallpaper != null; -        boolean lockValid = lockWallpaper != null && !isLockscreenLiveWallpaperEnabled(); +        boolean lockValid = lockWallpaper != null && isLockscreenLiveWallpaperEnabled();          return systemValid && lockValid ? new WallpaperData[]{systemWallpaper, lockWallpaper}                  : systemValid ? new WallpaperData[]{systemWallpaper}                  : lockValid ? new WallpaperData[]{lockWallpaper} @@ -3982,7 +3982,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub          if (wallpaper == null) {              // common case, this is the first lookup post-boot of the system or              // unified lock, so we bring up the saved state lazily now and recheck. -            int whichLoad = (which == FLAG_LOCK) ? FLAG_LOCK : FLAG_SYSTEM; +            // if we're loading the system wallpaper for the first time, also load the lock +            // wallpaper to determine if the system wallpaper is system+lock or system only. +            int whichLoad = (which == FLAG_LOCK) ? FLAG_LOCK : FLAG_SYSTEM | FLAG_LOCK;              loadSettingsLocked(userId, false, whichLoad);              wallpaper = whichSet.get(userId);              if (wallpaper == null) { diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index c866dd013af0..a01113b26a1e 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1316,6 +1316,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A          if (mLaunchIntoPipHostActivity != null) {              pw.println(prefix + "launchIntoPipHostActivity=" + mLaunchIntoPipHostActivity);          } +        if (mWaitForEnteringPinnedMode) { +            pw.print(prefix); pw.println("mWaitForEnteringPinnedMode=true"); +        }          mLetterboxUiController.dump(pw, prefix); @@ -3132,9 +3135,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A      }      boolean canReceiveKeys() { -        // TODO(156521483): Propagate the state down the hierarchy instead of checking the parent -        return getWindowConfiguration().canReceiveKeys() -                && (task == null || task.getWindowConfiguration().canReceiveKeys()); +        return getWindowConfiguration().canReceiveKeys() && !mWaitForEnteringPinnedMode;      }      boolean isResizeable() { @@ -8264,7 +8265,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A      private void clearSizeCompatModeAttributes() {          mInSizeCompatModeForBounds = false; +        final float lastSizeCompatScale = mSizeCompatScale;          mSizeCompatScale = 1f; +        if (mSizeCompatScale != lastSizeCompatScale) { +            forAllWindows(WindowState::updateGlobalScale, false /* traverseTopToBottom */); +        }          mSizeCompatBounds = null;          mCompatDisplayInsets = null;          mLetterboxUiController.clearInheritedCompatDisplayInsets(); @@ -8272,11 +8277,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A      @VisibleForTesting      void clearSizeCompatMode() { -        final float lastSizeCompatScale = mSizeCompatScale;          clearSizeCompatModeAttributes(); -        if (mSizeCompatScale != lastSizeCompatScale) { -            forAllWindows(WindowState::updateGlobalScale, false /* traverseTopToBottom */); -        }          // Clear config override in #updateCompatDisplayInsets().          final int activityType = getActivityType();          final Configuration overrideConfig = getRequestedOverrideConfiguration(); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 0b673211a1c9..de335d3d013e 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -68,6 +68,7 @@ import static android.view.WindowManager.TRANSIT_CHANGE;  import static android.view.WindowManager.TRANSIT_PIP;  import static android.view.WindowManager.TRANSIT_TO_FRONT;  import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT; +import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;  import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;  import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_DREAM;  import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS; @@ -3686,6 +3687,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {                          getTransitionController(), mWindowManager.mSyncEngine)                  : null; +        if (r.getTaskFragment() != null && r.getTaskFragment().isEmbeddedWithBoundsOverride() +                && transition != null) { +            transition.addFlag(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY); +        } +          final Runnable enterPipRunnable = () -> {              synchronized (mGlobalLock) {                  if (r.getParent() == null) { diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index 12e1e2cd1e41..777b5cd4337b 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -2832,17 +2832,22 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {      static class OpaqueActivityHelper implements Predicate<ActivityRecord> {          private ActivityRecord mStarting;          private boolean mIncludeInvisibleAndFinishing; +        private boolean mIgnoringKeyguard; -        ActivityRecord getOpaqueActivity(@NonNull WindowContainer<?> container) { +        ActivityRecord getOpaqueActivity( +                @NonNull WindowContainer<?> container, boolean ignoringKeyguard) {              mIncludeInvisibleAndFinishing = true; +            mIgnoringKeyguard = ignoringKeyguard;              return container.getActivity(this,                      true /* traverseTopToBottom */, null /* boundary */);          } -        ActivityRecord getVisibleOpaqueActivity(@NonNull WindowContainer<?> container, -                @Nullable ActivityRecord starting) { +        ActivityRecord getVisibleOpaqueActivity( +                @NonNull WindowContainer<?> container, @Nullable ActivityRecord starting, +                boolean ignoringKeyguard) {              mStarting = starting;              mIncludeInvisibleAndFinishing = false; +            mIgnoringKeyguard = ignoringKeyguard;              final ActivityRecord opaque = container.getActivity(this,                      true /* traverseTopToBottom */, null /* boundary */);              mStarting = null; @@ -2851,7 +2856,9 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {          @Override          public boolean test(ActivityRecord r) { -            if (!mIncludeInvisibleAndFinishing && !r.visibleIgnoringKeyguard && r != mStarting) { +            if (!mIncludeInvisibleAndFinishing && r != mStarting +                    && ((mIgnoringKeyguard && !r.visibleIgnoringKeyguard) +                    || (!mIgnoringKeyguard && !r.isVisible()))) {                  // Ignore invisible activities that are not the currently starting activity                  // (about to be visible).                  return false; diff --git a/services/core/java/com/android/server/wm/AnimationAdapter.java b/services/core/java/com/android/server/wm/AnimationAdapter.java index b039646c1697..3dc377dbc14c 100644 --- a/services/core/java/com/android/server/wm/AnimationAdapter.java +++ b/services/core/java/com/android/server/wm/AnimationAdapter.java @@ -22,6 +22,7 @@ import android.view.SurfaceControl;  import android.view.SurfaceControl.Transaction;  import android.view.animation.Animation; +import com.android.internal.annotations.VisibleForTesting;  import com.android.server.wm.SurfaceAnimator.AnimationType;  import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; @@ -31,7 +32,8 @@ import java.io.PrintWriter;   * Interface that describes an animation and bridges the animation start to the component   * responsible for running the animation.   */ -interface AnimationAdapter { +@VisibleForTesting +public interface AnimationAdapter {      long STATUS_BAR_TRANSITION_DURATION = 120L; diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index 42376685498a..6d59b297de48 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -276,6 +276,10 @@ class BackNavigationController {                  // activity, we won't close the activity.                  backType = BackNavigationInfo.TYPE_DIALOG_CLOSE;                  removedWindowContainer = window; +            } else if (!currentActivity.occludesParent() || currentActivity.showWallpaper()) { +                // skip if current activity is translucent +                backType = BackNavigationInfo.TYPE_CALLBACK; +                removedWindowContainer = window;              } else if (prevActivity != null) {                  if (!isOccluded || prevActivity.canShowWhenLocked()) {                      // We have another Activity in the same currentTask to go to diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java index ae29afa9fc49..64a230effb38 100644 --- a/services/core/java/com/android/server/wm/Dimmer.java +++ b/services/core/java/com/android/server/wm/Dimmer.java @@ -1,5 +1,5 @@  /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2023 The Android Open Source Project   *   * Licensed under the Apache License, Version 2.0 (the "License");   * you may not use this file except in compliance with the License. @@ -11,172 +11,36 @@   * distributed under the License is distributed on an "AS IS" BASIS,   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   * See the License for the specific language governing permissions and - * limitations under the License + * limitations under the License.   */  package com.android.server.wm; -import static com.android.server.wm.AlphaAnimationSpecProto.DURATION_MS; -import static com.android.server.wm.AlphaAnimationSpecProto.FROM; -import static com.android.server.wm.AlphaAnimationSpecProto.TO; -import static com.android.server.wm.AnimationSpecProto.ALPHA; -import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER; -  import android.annotation.NonNull;  import android.graphics.Rect; -import android.util.Log; -import android.util.proto.ProtoOutputStream; -import android.view.Surface;  import android.view.SurfaceControl;  import com.android.internal.annotations.VisibleForTesting; -import com.android.server.wm.SurfaceAnimator.AnimationType; - -import java.io.PrintWriter; +import com.android.window.flags.Flags;  /**   * Utility class for use by a WindowContainer implementation to add "DimLayer" support, that is   * black layers of varying opacity at various Z-levels which create the effect of a Dim.   */ -class Dimmer { -    private static final String TAG = "WindowManager"; -    // This is in milliseconds. -    private static final int DEFAULT_DIM_ANIM_DURATION = 200; - -    private class DimAnimatable implements SurfaceAnimator.Animatable { -        private SurfaceControl mDimLayer; - -        private DimAnimatable(SurfaceControl dimLayer) { -            mDimLayer = dimLayer; -        } - -        @Override -        public SurfaceControl.Transaction getSyncTransaction() { -            return mHost.getSyncTransaction(); -        } - -        @Override -        public SurfaceControl.Transaction getPendingTransaction() { -            return mHost.getPendingTransaction(); -        } - -        @Override -        public void commitPendingTransaction() { -            mHost.commitPendingTransaction(); -        } - -        @Override -        public void onAnimationLeashCreated(SurfaceControl.Transaction t, SurfaceControl leash) { -        } - -        @Override -        public void onAnimationLeashLost(SurfaceControl.Transaction t) { -        } - -        @Override -        public SurfaceControl.Builder makeAnimationLeash() { -            return mHost.makeAnimationLeash(); -        } - -        @Override -        public SurfaceControl getAnimationLeashParent() { -            return mHost.getSurfaceControl(); -        } - -        @Override -        public SurfaceControl getSurfaceControl() { -            return mDimLayer; -        } - -        @Override -        public SurfaceControl getParentSurfaceControl() { -            return mHost.getSurfaceControl(); -        } - -        @Override -        public int getSurfaceWidth() { -            // This will determine the size of the leash created. This should be the size of the -            // host and not the dim layer since the dim layer may get bigger during animation. If -            // that occurs, the leash size cannot change so we need to ensure the leash is big -            // enough that the dim layer can grow. -            // This works because the mHost will be a Task which has the display bounds. -            return mHost.getSurfaceWidth(); -        } - -        @Override -        public int getSurfaceHeight() { -            // See getSurfaceWidth() above for explanation. -            return mHost.getSurfaceHeight(); -        } - -        void removeSurface() { -            if (mDimLayer != null && mDimLayer.isValid()) { -                getSyncTransaction().remove(mDimLayer); -            } -            mDimLayer = null; -        } -    } - -    @VisibleForTesting -    class DimState { -        /** -         * The layer where property changes should be invoked on. -         */ -        SurfaceControl mDimLayer; -        boolean mDimming; -        boolean isVisible; -        SurfaceAnimator mSurfaceAnimator; - -        // TODO(b/64816140): Remove after confirming dimmer layer always matches its container. -        final Rect mDimBounds = new Rect(); - -        /** -         * Determines whether the dim layer should animate before destroying. -         */ -        boolean mAnimateExit = true; - -        /** -         * Used for Dims not associated with a WindowContainer. See {@link Dimmer#dimAbove} for -         * details on Dim lifecycle. -         */ -        boolean mDontReset; - -        DimState(SurfaceControl dimLayer) { -            mDimLayer = dimLayer; -            mDimming = true; -            final DimAnimatable dimAnimatable = new DimAnimatable(dimLayer); -            mSurfaceAnimator = new SurfaceAnimator(dimAnimatable, (type, anim) -> { -                if (!mDimming) { -                    dimAnimatable.removeSurface(); -                } -            }, mHost.mWmService); -        } -    } - +public abstract class Dimmer {      /** -     * The {@link WindowContainer} that our Dim's are bounded to. We may be dimming on behalf of the +     * The {@link WindowContainer} that our Dims are bounded to. We may be dimming on behalf of the       * host, some controller of it, or one of the hosts children.       */ -    private WindowContainer mHost; -    private WindowContainer mLastRequestedDimContainer; -    @VisibleForTesting -    DimState mDimState; - -    private final SurfaceAnimatorStarter mSurfaceAnimatorStarter; - -    @VisibleForTesting -    interface SurfaceAnimatorStarter { -        void startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t, -                AnimationAdapter anim, boolean hidden, @AnimationType int type); -    } +    protected final WindowContainer mHost; -    Dimmer(WindowContainer host) { -        this(host, SurfaceAnimator::startAnimation); +    protected Dimmer(WindowContainer host) { +        mHost = host;      } -    Dimmer(WindowContainer host, SurfaceAnimatorStarter surfaceAnimatorStarter) { -        mHost = host; -        mSurfaceAnimatorStarter = surfaceAnimatorStarter; +    // Constructs the correct type of dimmer +    static Dimmer create(WindowContainer host) { +        return Flags.dimmerRefactor() ? new SmoothDimmer(host) : new LegacyDimmer(host);      }      @NonNull @@ -184,49 +48,8 @@ class Dimmer {          return mHost;      } -    private SurfaceControl makeDimLayer() { -        return mHost.makeChildSurface(null) -                .setParent(mHost.getSurfaceControl()) -                .setColorLayer() -                .setName("Dim Layer for - " + mHost.getName()) -                .setCallsite("Dimmer.makeDimLayer") -                .build(); -    } - -    /** -     * Retrieve the DimState, creating one if it doesn't exist. -     */ -    private DimState getDimState(WindowContainer container) { -        if (mDimState == null) { -            try { -                final SurfaceControl ctl = makeDimLayer(); -                mDimState = new DimState(ctl); -            } catch (Surface.OutOfResourcesException e) { -                Log.w(TAG, "OutOfResourcesException creating dim surface"); -            } -        } - -        mLastRequestedDimContainer = container; -        return mDimState; -    } - -    private void dim(WindowContainer container, int relativeLayer, float alpha, int blurRadius) { -        final DimState d = getDimState(container); - -        if (d == null) { -            return; -        } - -        // The dim method is called from WindowState.prepareSurfaces(), which is always called -        // in the correct Z from lowest Z to highest. This ensures that the dim layer is always -        // relative to the highest Z layer with a dim. -        SurfaceControl.Transaction t = mHost.getPendingTransaction(); -        t.setRelativeLayer(d.mDimLayer, container.getSurfaceControl(), relativeLayer); -        t.setAlpha(d.mDimLayer, alpha); -        t.setBackgroundBlurRadius(d.mDimLayer, blurRadius); - -        d.mDimming = true; -    } +    protected abstract void dim( +            WindowContainer container, int relativeLayer, float alpha, int blurRadius);      /**       * Place a dim above the given container, which should be a child of the host container. @@ -260,25 +83,15 @@ class Dimmer {       * chain {@link WindowContainer#prepareSurfaces} down to it's children to give them       * a chance to request dims to continue.       */ -    void resetDimStates() { -        if (mDimState == null) { -            return; -        } -        if (!mDimState.mDontReset) { -            mDimState.mDimming = false; -        } -    } +    abstract void resetDimStates();      /** Returns non-null bounds if the dimmer is showing. */ -    Rect getDimBounds() { -        return mDimState != null ? mDimState.mDimBounds : null; -    } +    abstract Rect getDimBounds(); -    void dontAnimateExit() { -        if (mDimState != null) { -            mDimState.mAnimateExit = false; -        } -    } +    abstract void dontAnimateExit(); + +    @VisibleForTesting +    abstract SurfaceControl getDimLayer();      /**       * Call after invoking {@link WindowContainer#prepareSurfaces} on children as @@ -288,109 +101,5 @@ class Dimmer {       * @param t      A transaction in which to update the dims.       * @return true if any Dims were updated.       */ -    boolean updateDims(SurfaceControl.Transaction t) { -        if (mDimState == null) { -            return false; -        } - -        if (!mDimState.mDimming) { -            if (!mDimState.mAnimateExit) { -                if (mDimState.mDimLayer.isValid()) { -                    t.remove(mDimState.mDimLayer); -                } -            } else { -                startDimExit(mLastRequestedDimContainer, mDimState.mSurfaceAnimator, t); -            } -            mDimState = null; -            return false; -        } else { -            final Rect bounds = mDimState.mDimBounds; -            // TODO: Once we use geometry from hierarchy this falls away. -            t.setPosition(mDimState.mDimLayer, bounds.left, bounds.top); -            t.setWindowCrop(mDimState.mDimLayer, bounds.width(), bounds.height()); -            if (!mDimState.isVisible) { -                mDimState.isVisible = true; -                t.show(mDimState.mDimLayer); -                // Skip enter animation while starting window is on top of its activity -                final WindowState ws = mLastRequestedDimContainer.asWindowState(); -                if (ws == null || ws.mActivityRecord == null -                        || ws.mActivityRecord.mStartingData == null) { -                    startDimEnter(mLastRequestedDimContainer, mDimState.mSurfaceAnimator, t); -                } -            } -            return true; -        } -    } - -    private void startDimEnter(WindowContainer container, SurfaceAnimator animator, -            SurfaceControl.Transaction t) { -        startAnim(container, animator, t, 0 /* startAlpha */, 1 /* endAlpha */); -    } - -    private void startDimExit(WindowContainer container, SurfaceAnimator animator, -            SurfaceControl.Transaction t) { -        startAnim(container, animator, t, 1 /* startAlpha */, 0 /* endAlpha */); -    } - -    private void startAnim(WindowContainer container, SurfaceAnimator animator, -            SurfaceControl.Transaction t, float startAlpha, float endAlpha) { -        mSurfaceAnimatorStarter.startAnimation(animator, t, new LocalAnimationAdapter( -                new AlphaAnimationSpec(startAlpha, endAlpha, getDimDuration(container)), -                mHost.mWmService.mSurfaceAnimationRunner), false /* hidden */, -                ANIMATION_TYPE_DIMMER); -    } - -    private long getDimDuration(WindowContainer container) { -        // If there's no container, then there isn't an animation occurring while dimming. Set the -        // duration to 0 so it immediately dims to the set alpha. -        if (container == null) { -            return 0; -        } - -        // Otherwise use the same duration as the animation on the WindowContainer -        AnimationAdapter animationAdapter = container.mSurfaceAnimator.getAnimation(); -        final float durationScale = container.mWmService.getTransitionAnimationScaleLocked(); -        return animationAdapter == null ? (long) (DEFAULT_DIM_ANIM_DURATION * durationScale) -                : animationAdapter.getDurationHint(); -    } - -    private static class AlphaAnimationSpec implements LocalAnimationAdapter.AnimationSpec { -        private final long mDuration; -        private final float mFromAlpha; -        private final float mToAlpha; - -        AlphaAnimationSpec(float fromAlpha, float toAlpha, long duration) { -            mFromAlpha = fromAlpha; -            mToAlpha = toAlpha; -            mDuration = duration; -        } - -        @Override -        public long getDuration() { -            return mDuration; -        } - -        @Override -        public void apply(SurfaceControl.Transaction t, SurfaceControl sc, long currentPlayTime) { -            final float fraction = getFraction(currentPlayTime); -            final float alpha = fraction * (mToAlpha - mFromAlpha) + mFromAlpha; -            t.setAlpha(sc, alpha); -        } - -        @Override -        public void dump(PrintWriter pw, String prefix) { -            pw.print(prefix); pw.print("from="); pw.print(mFromAlpha); -            pw.print(" to="); pw.print(mToAlpha); -            pw.print(" duration="); pw.println(mDuration); -        } - -        @Override -        public void dumpDebugInner(ProtoOutputStream proto) { -            final long token = proto.start(ALPHA); -            proto.write(FROM, mFromAlpha); -            proto.write(TO, mToAlpha); -            proto.write(DURATION_MS, mDuration); -            proto.end(token); -        } -    } +    abstract boolean updateDims(SurfaceControl.Transaction t);  } diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java index df26b101a657..f51bf7fbf27a 100644 --- a/services/core/java/com/android/server/wm/DisplayArea.java +++ b/services/core/java/com/android/server/wm/DisplayArea.java @@ -54,7 +54,6 @@ import java.util.function.BiFunction;  import java.util.function.Consumer;  import java.util.function.Function;  import java.util.function.Predicate; -  /**   * Container for grouping WindowContainer below DisplayContent.   * @@ -786,7 +785,7 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> {       * DisplayArea that can be dimmed.       */      static class Dimmable extends DisplayArea<DisplayArea> { -        private final Dimmer mDimmer = new Dimmer(this); +        private final Dimmer mDimmer = Dimmer.create(this);          Dimmable(WindowManagerService wms, Type type, String name, int featureId) {              super(wms, type, name, featureId); diff --git a/services/core/java/com/android/server/wm/InputConsumerImpl.java b/services/core/java/com/android/server/wm/InputConsumerImpl.java index c21930dab5eb..1fa7d2a2aa13 100644 --- a/services/core/java/com/android/server/wm/InputConsumerImpl.java +++ b/services/core/java/com/android/server/wm/InputConsumerImpl.java @@ -51,7 +51,8 @@ class InputConsumerImpl implements IBinder.DeathRecipient {      private final Rect mOldWindowCrop = new Rect();      InputConsumerImpl(WindowManagerService service, IBinder token, String name, -            InputChannel inputChannel, int clientPid, UserHandle clientUser, int displayId) { +            InputChannel inputChannel, int clientPid, UserHandle clientUser, int displayId, +            SurfaceControl.Transaction t) {          mService = service;          mToken = token;          mName = name; @@ -82,6 +83,7 @@ class InputConsumerImpl implements IBinder.DeathRecipient {                  .setName("Input Consumer " + name)                  .setCallsite("InputConsumerImpl")                  .build(); +        mWindowHandle.setTrustedOverlay(t, mInputSurface, true);      }      void linkToDeathRecipient() { @@ -129,14 +131,12 @@ class InputConsumerImpl implements IBinder.DeathRecipient {      void show(SurfaceControl.Transaction t, WindowContainer w) {          t.show(mInputSurface); -        mWindowHandle.setTrustedOverlay(t, mInputSurface, true);          t.setInputWindowInfo(mInputSurface, mWindowHandle);          t.setRelativeLayer(mInputSurface, w.getSurfaceControl(), 1);      }      void show(SurfaceControl.Transaction t, int layer) {          t.show(mInputSurface); -        mWindowHandle.setTrustedOverlay(t, mInputSurface, true);          t.setInputWindowInfo(mInputSurface, mWindowHandle);          t.setLayer(mInputSurface, layer);      } diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index af307ec3c2a9..5c0bc28779a8 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -224,7 +224,7 @@ final class InputMonitor {          }          final InputConsumerImpl consumer = new InputConsumerImpl(mService, token, name, -                inputChannel, clientPid, clientUser, mDisplayId); +                inputChannel, clientPid, clientUser, mDisplayId, mInputTransaction);          switch (name) {              case INPUT_CONSUMER_WALLPAPER:                  consumer.mWindowHandle.inputConfig |= InputConfig.DUPLICATE_TOUCH_TO_WALLPAPER; @@ -675,11 +675,6 @@ final class InputMonitor {                      w.getKeyInterceptionInfo());              if (w.mWinAnimator.hasSurface()) { -                // Update trusted overlay changes here because they are tied to input info. Input -                // changes can be updated even if surfaces aren't. -                inputWindowHandle.setTrustedOverlay(mInputTransaction, -                        w.mWinAnimator.mSurfaceController.mSurfaceControl, -                        w.isWindowTrustedOverlay());                  populateInputWindowHandle(inputWindowHandle, w);                  setInputWindowInfoIfNeeded(mInputTransaction,                          w.mWinAnimator.mSurfaceController.mSurfaceControl, inputWindowHandle); diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index 02f5c217e5d8..cd114fcf9e21 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -68,6 +68,7 @@ class InsetsSourceProvider {      private final Rect mTmpRect = new Rect();      private final InsetsStateController mStateController;      private final InsetsSourceControl mFakeControl; +    private final Consumer<Transaction> mSetLeashPositionConsumer;      private @Nullable InsetsSourceControl mControl;      private @Nullable InsetsControlTarget mControlTarget;      private @Nullable InsetsControlTarget mPendingControlTarget; @@ -85,16 +86,7 @@ class InsetsSourceProvider {      private boolean mInsetsHintStale = true;      private @Flags int mFlagsFromFrameProvider;      private @Flags int mFlagsFromServer; - -    private final Consumer<Transaction> mSetLeashPositionConsumer = t -> { -        if (mControl != null) { -            final SurfaceControl leash = mControl.getLeash(); -            if (leash != null) { -                final Point position = mControl.getSurfacePosition(); -                t.setPosition(leash, position.x, position.y); -            } -        } -    }; +    private boolean mHasPendingPosition;      /** The visibility override from the current controlling window. */      private boolean mClientVisible; @@ -129,6 +121,21 @@ class InsetsSourceProvider {                  source.getId(), source.getType(), null /* leash */, false /* initialVisible */,                  new Point(), Insets.NONE);          mControllable = (InsetsPolicy.CONTROLLABLE_TYPES & source.getType()) != 0; +        mSetLeashPositionConsumer = t -> { +            if (mControl != null) { +                final SurfaceControl leash = mControl.getLeash(); +                if (leash != null) { +                    final Point position = mControl.getSurfacePosition(); +                    t.setPosition(leash, position.x, position.y); +                } +            } +            if (mHasPendingPosition) { +                mHasPendingPosition = false; +                if (mPendingControlTarget != mControlTarget) { +                    mStateController.notifyControlTargetChanged(mPendingControlTarget, this); +                } +            } +        };      }      InsetsSource getSource() { @@ -185,9 +192,8 @@ class InsetsSourceProvider {              mWindowContainer.getInsetsSourceProviders().put(mSource.getId(), this);              if (mControllable) {                  mWindowContainer.setControllableInsetProvider(this); -                if (mPendingControlTarget != null) { +                if (mPendingControlTarget != mControlTarget) {                      updateControlForTarget(mPendingControlTarget, true /* force */); -                    mPendingControlTarget = null;                  }              }          } @@ -344,6 +350,7 @@ class InsetsSourceProvider {                  changed = true;                  if (windowState != null && windowState.getWindowFrames().didFrameSizeChange()                          && windowState.mWinAnimator.getShown() && mWindowContainer.okToDisplay()) { +                    mHasPendingPosition = true;                      windowState.applyWithNextDraw(mSetLeashPositionConsumer);                  } else {                      Transaction t = mWindowContainer.getSyncTransaction(); @@ -465,18 +472,23 @@ class InsetsSourceProvider {              // to control the window for now.              return;          } +        mPendingControlTarget = target;          if (mWindowContainer != null && mWindowContainer.getSurfaceControl() == null) {              // if window doesn't have a surface, set it null and return.              setWindowContainer(null, null, null);          }          if (mWindowContainer == null) { -            mPendingControlTarget = target;              return;          }          if (target == mControlTarget && !force) {              return;          } +        if (mHasPendingPosition) { +            // Don't create a new leash while having a pending position. Otherwise, the position +            // will be changed earlier than expected, which can cause flicker. +            return; +        }          if (target == null) {              // Cancelling the animation will invoke onAnimationCancelled, resetting all the fields.              mWindowContainer.cancelAnimation(); @@ -618,6 +630,7 @@ class InsetsSourceProvider {          }          pw.print(prefix);          pw.print("mIsLeashReadyForDispatching="); pw.print(mIsLeashReadyForDispatching); +        pw.print("mHasPendingPosition="); pw.print(mHasPendingPosition);          pw.println();          if (mWindowContainer != null) {              pw.print(prefix + "mWindowContainer="); @@ -631,7 +644,7 @@ class InsetsSourceProvider {              pw.print(prefix + "mControlTarget=");              pw.println(mControlTarget);          } -        if (mPendingControlTarget != null) { +        if (mPendingControlTarget != mControlTarget) {              pw.print(prefix + "mPendingControlTarget=");              pw.println(mPendingControlTarget);          } @@ -652,7 +665,8 @@ class InsetsSourceProvider {          if (mControlTarget != null && mControlTarget.getWindow() != null) {              mControlTarget.getWindow().dumpDebug(proto, CONTROL_TARGET, logLevel);          } -        if (mPendingControlTarget != null && mPendingControlTarget.getWindow() != null) { +        if (mPendingControlTarget != null && mPendingControlTarget != mControlTarget +                && mPendingControlTarget.getWindow() != null) {              mPendingControlTarget.getWindow().dumpDebug(proto, PENDING_CONTROL_TARGET, logLevel);          }          if (mFakeControlTarget != null && mFakeControlTarget.getWindow() != null) { diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java index 081ebe0e7cbd..c4d01291f558 100644 --- a/services/core/java/com/android/server/wm/InsetsStateController.java +++ b/services/core/java/com/android/server/wm/InsetsStateController.java @@ -278,6 +278,12 @@ class InsetsStateController {          notifyPendingInsetsControlChanged();      } +    void notifyControlTargetChanged(@Nullable InsetsControlTarget target, +            InsetsSourceProvider provider) { +        onControlTargetChanged(provider, target, false /* fake */); +        notifyPendingInsetsControlChanged(); +    } +      void notifyControlRevoked(@NonNull InsetsControlTarget previousControlTarget,              InsetsSourceProvider provider) {          removeFromControlMaps(previousControlTarget, provider, false /* fake */); diff --git a/services/core/java/com/android/server/wm/LegacyDimmer.java b/services/core/java/com/android/server/wm/LegacyDimmer.java new file mode 100644 index 000000000000..ccf956ecef1e --- /dev/null +++ b/services/core/java/com/android/server/wm/LegacyDimmer.java @@ -0,0 +1,341 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static com.android.server.wm.AlphaAnimationSpecProto.DURATION_MS; +import static com.android.server.wm.AlphaAnimationSpecProto.FROM; +import static com.android.server.wm.AlphaAnimationSpecProto.TO; +import static com.android.server.wm.AnimationSpecProto.ALPHA; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER; +import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; + +import android.graphics.Rect; +import android.util.Log; +import android.util.proto.ProtoOutputStream; +import android.view.Surface; +import android.view.SurfaceControl; + +import com.android.internal.annotations.VisibleForTesting; + +import java.io.PrintWriter; + +public class LegacyDimmer extends Dimmer { +    private static final String TAG = TAG_WITH_CLASS_NAME ? "Dimmer" : TAG_WM; +    // This is in milliseconds. +    private static final int DEFAULT_DIM_ANIM_DURATION = 200; +    DimState mDimState; +    private WindowContainer mLastRequestedDimContainer; +    private final SurfaceAnimatorStarter mSurfaceAnimatorStarter; + +    private class DimAnimatable implements SurfaceAnimator.Animatable { +        private SurfaceControl mDimLayer; + +        private DimAnimatable(SurfaceControl dimLayer) { +            mDimLayer = dimLayer; +        } + +        @Override +        public SurfaceControl.Transaction getSyncTransaction() { +            return mHost.getSyncTransaction(); +        } + +        @Override +        public SurfaceControl.Transaction getPendingTransaction() { +            return mHost.getPendingTransaction(); +        } + +        @Override +        public void commitPendingTransaction() { +            mHost.commitPendingTransaction(); +        } + +        @Override +        public void onAnimationLeashCreated(SurfaceControl.Transaction t, SurfaceControl leash) { +        } + +        @Override +        public void onAnimationLeashLost(SurfaceControl.Transaction t) { +        } + +        @Override +        public SurfaceControl.Builder makeAnimationLeash() { +            return mHost.makeAnimationLeash(); +        } + +        @Override +        public SurfaceControl getAnimationLeashParent() { +            return mHost.getSurfaceControl(); +        } + +        @Override +        public SurfaceControl getSurfaceControl() { +            return mDimLayer; +        } + +        @Override +        public SurfaceControl getParentSurfaceControl() { +            return mHost.getSurfaceControl(); +        } + +        @Override +        public int getSurfaceWidth() { +            // This will determine the size of the leash created. This should be the size of the +            // host and not the dim layer since the dim layer may get bigger during animation. If +            // that occurs, the leash size cannot change so we need to ensure the leash is big +            // enough that the dim layer can grow. +            // This works because the mHost will be a Task which has the display bounds. +            return mHost.getSurfaceWidth(); +        } + +        @Override +        public int getSurfaceHeight() { +            // See getSurfaceWidth() above for explanation. +            return mHost.getSurfaceHeight(); +        } + +        void removeSurface() { +            if (mDimLayer != null && mDimLayer.isValid()) { +                getSyncTransaction().remove(mDimLayer); +            } +            mDimLayer = null; +        } +    } + +    @VisibleForTesting +    class DimState { +        /** +         * The layer where property changes should be invoked on. +         */ +        SurfaceControl mDimLayer; +        boolean mDimming; +        boolean mIsVisible; + +        // TODO(b/64816140): Remove after confirming dimmer layer always matches its container. +        final Rect mDimBounds = new Rect(); + +        /** +         * Determines whether the dim layer should animate before destroying. +         */ +        boolean mAnimateExit = true; + +        /** +         * Used for Dims not associated with a WindowContainer. See {@link Dimmer#dimAbove} for +         * details on Dim lifecycle. +         */ +        boolean mDontReset; +        SurfaceAnimator mSurfaceAnimator; + +        DimState(SurfaceControl dimLayer) { +            mDimLayer = dimLayer; +            mDimming = true; +            final DimAnimatable dimAnimatable = new DimAnimatable(dimLayer); +            mSurfaceAnimator = new SurfaceAnimator(dimAnimatable, (type, anim) -> { +                if (!mDimming) { +                    dimAnimatable.removeSurface(); +                } +            }, mHost.mWmService); +        } +    } + +    @VisibleForTesting +    interface SurfaceAnimatorStarter { +        void startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t, +                AnimationAdapter anim, boolean hidden, @SurfaceAnimator.AnimationType int type); +    } + +    protected LegacyDimmer(WindowContainer host) { +        this(host, SurfaceAnimator::startAnimation); +    } + +    LegacyDimmer(WindowContainer host, SurfaceAnimatorStarter surfaceAnimatorStarter) { +        super(host); +        mSurfaceAnimatorStarter = surfaceAnimatorStarter; +    } + +    private DimState obtainDimState(WindowContainer container) { +        if (mDimState == null) { +            try { +                final SurfaceControl ctl = makeDimLayer(); +                mDimState = new DimState(ctl); +            } catch (Surface.OutOfResourcesException e) { +                Log.w(TAG, "OutOfResourcesException creating dim surface"); +            } +        } + +        mLastRequestedDimContainer = container; +        return mDimState; +    } + +    private SurfaceControl makeDimLayer() { +        return mHost.makeChildSurface(null) +                .setParent(mHost.getSurfaceControl()) +                .setColorLayer() +                .setName("Dim Layer for - " + mHost.getName()) +                .setCallsite("Dimmer.makeDimLayer") +                .build(); +    } + +    @Override +    SurfaceControl getDimLayer() { +        return mDimState != null ? mDimState.mDimLayer : null; +    } + +    @Override +    void resetDimStates() { +        if (mDimState == null) { +            return; +        } +        if (!mDimState.mDontReset) { +            mDimState.mDimming = false; +        } +    } + +    @Override +    Rect getDimBounds() { +        return mDimState != null ? mDimState.mDimBounds : null; +    } + +    @Override +    void dontAnimateExit() { +        if (mDimState != null) { +            mDimState.mAnimateExit = false; +        } +    } + +    @Override +    protected void dim(WindowContainer container, int relativeLayer, float alpha, int blurRadius) { +        final DimState d = obtainDimState(container); + +        if (d == null) { +            return; +        } + +        // The dim method is called from WindowState.prepareSurfaces(), which is always called +        // in the correct Z from lowest Z to highest. This ensures that the dim layer is always +        // relative to the highest Z layer with a dim. +        SurfaceControl.Transaction t = mHost.getPendingTransaction(); +        t.setRelativeLayer(d.mDimLayer, container.getSurfaceControl(), relativeLayer); +        t.setAlpha(d.mDimLayer, alpha); +        t.setBackgroundBlurRadius(d.mDimLayer, blurRadius); + +        d.mDimming = true; +    } + +    @Override +    boolean updateDims(SurfaceControl.Transaction t) { +        if (mDimState == null) { +            return false; +        } + +        if (!mDimState.mDimming) { +            if (!mDimState.mAnimateExit) { +                if (mDimState.mDimLayer.isValid()) { +                    t.remove(mDimState.mDimLayer); +                } +            } else { +                startDimExit(mLastRequestedDimContainer, +                        mDimState.mSurfaceAnimator, t); +            } +            mDimState = null; +            return false; +        } else { +            final Rect bounds = mDimState.mDimBounds; +            // TODO: Once we use geometry from hierarchy this falls away. +            t.setPosition(mDimState.mDimLayer, bounds.left, bounds.top); +            t.setWindowCrop(mDimState.mDimLayer, bounds.width(), bounds.height()); +            if (!mDimState.mIsVisible) { +                mDimState.mIsVisible = true; +                t.show(mDimState.mDimLayer); +                // Skip enter animation while starting window is on top of its activity +                final WindowState ws = mLastRequestedDimContainer.asWindowState(); +                if (ws == null || ws.mActivityRecord == null +                        || ws.mActivityRecord.mStartingData == null) { +                    startDimEnter(mLastRequestedDimContainer, +                            mDimState.mSurfaceAnimator, t); +                } +            } +            return true; +        } +    } + +    private long getDimDuration(WindowContainer container) { +        // Use the same duration as the animation on the WindowContainer +        AnimationAdapter animationAdapter = container.mSurfaceAnimator.getAnimation(); +        final float durationScale = container.mWmService.getTransitionAnimationScaleLocked(); +        return animationAdapter == null ? (long) (DEFAULT_DIM_ANIM_DURATION * durationScale) +                : animationAdapter.getDurationHint(); +    } + +    private void startDimEnter(WindowContainer container, SurfaceAnimator animator, +            SurfaceControl.Transaction t) { +        startAnim(container, animator, t, 0 /* startAlpha */, 1 /* endAlpha */); +    } + +    private void startDimExit(WindowContainer container, SurfaceAnimator animator, +            SurfaceControl.Transaction t) { +        startAnim(container, animator, t, 1 /* startAlpha */, 0 /* endAlpha */); +    } + +    private void startAnim(WindowContainer container, SurfaceAnimator animator, +            SurfaceControl.Transaction t, float startAlpha, float endAlpha) { +        mSurfaceAnimatorStarter.startAnimation(animator, t, new LocalAnimationAdapter( +                        new AlphaAnimationSpec(startAlpha, endAlpha, getDimDuration(container)), +                        mHost.mWmService.mSurfaceAnimationRunner), false /* hidden */, +                ANIMATION_TYPE_DIMMER); +    } + +    private static class AlphaAnimationSpec implements LocalAnimationAdapter.AnimationSpec { +        private final long mDuration; +        private final float mFromAlpha; +        private final float mToAlpha; + +        AlphaAnimationSpec(float fromAlpha, float toAlpha, long duration) { +            mFromAlpha = fromAlpha; +            mToAlpha = toAlpha; +            mDuration = duration; +        } + +        @Override +        public long getDuration() { +            return mDuration; +        } + +        @Override +        public void apply(SurfaceControl.Transaction t, SurfaceControl sc, long currentPlayTime) { +            final float fraction = getFraction(currentPlayTime); +            final float alpha = fraction * (mToAlpha - mFromAlpha) + mFromAlpha; +            t.setAlpha(sc, alpha); +        } + +        @Override +        public void dump(PrintWriter pw, String prefix) { +            pw.print(prefix); pw.print("from="); pw.print(mFromAlpha); +            pw.print(" to="); pw.print(mToAlpha); +            pw.print(" duration="); pw.println(mDuration); +        } + +        @Override +        public void dumpDebugInner(ProtoOutputStream proto) { +            final long token = proto.start(ALPHA); +            proto.write(FROM, mFromAlpha); +            proto.write(TO, mToAlpha); +            proto.write(DURATION_MS, mDuration); +            proto.end(token); +        } +    } +} diff --git a/services/core/java/com/android/server/wm/OWNERS b/services/core/java/com/android/server/wm/OWNERS index 458786ffdbc0..f6c364008c62 100644 --- a/services/core/java/com/android/server/wm/OWNERS +++ b/services/core/java/com/android/server/wm/OWNERS @@ -18,4 +18,4 @@ rgl@google.com  yunfanc@google.com  per-file BackgroundActivityStartController.java = set noparent -per-file BackgroundActivityStartController.java = brufino@google.com, ogunwale@google.com, louischang@google.com, lus@google.com
\ No newline at end of file +per-file BackgroundActivityStartController.java = brufino@google.com, topjohnwu@google.com, achim@google.com, ogunwale@google.com, louischang@google.com, lus@google.com diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index cf6a1feef5ee..7a442e708130 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -2211,6 +2211,10 @@ class RootWindowContainer extends WindowContainer<DisplayContent>                  mService.mTaskFragmentOrganizerController.dispatchPendingInfoChangedEvent(                          organizedTf);              } + +            if (taskDisplayArea.getFocusedRootTask() == rootTask) { +                taskDisplayArea.clearPreferredTopFocusableRootTask(); +            }          } finally {              mService.continueWindowLayout();              try { diff --git a/services/core/java/com/android/server/wm/SmoothDimmer.java b/services/core/java/com/android/server/wm/SmoothDimmer.java new file mode 100644 index 000000000000..6ddbd2c8eb67 --- /dev/null +++ b/services/core/java/com/android/server/wm/SmoothDimmer.java @@ -0,0 +1,395 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_DIMMER; +import static com.android.server.wm.AlphaAnimationSpecProto.DURATION_MS; +import static com.android.server.wm.AlphaAnimationSpecProto.FROM; +import static com.android.server.wm.AlphaAnimationSpecProto.TO; +import static com.android.server.wm.AnimationSpecProto.ALPHA; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER; +import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; + +import android.graphics.Rect; +import android.util.Log; +import android.util.proto.ProtoOutputStream; +import android.view.Surface; +import android.view.SurfaceControl; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.protolog.common.ProtoLog; + +import java.io.PrintWriter; + +class SmoothDimmer extends Dimmer { +    private static final String TAG = TAG_WITH_CLASS_NAME ? "Dimmer" : TAG_WM; +    private static final float EPSILON = 0.0001f; +    // This is in milliseconds. +    private static final int DEFAULT_DIM_ANIM_DURATION = 200; +    DimState mDimState; +    private WindowContainer mLastRequestedDimContainer; +    private final AnimationAdapterFactory mAnimationAdapterFactory; + +    @VisibleForTesting +    class DimState { +        /** +         * The layer where property changes should be invoked on. +         */ +        SurfaceControl mDimLayer; +        boolean mDimming; +        boolean mIsVisible; + +        // TODO(b/64816140): Remove after confirming dimmer layer always matches its container. +        final Rect mDimBounds = new Rect(); + +        /** +         * Determines whether the dim layer should animate before destroying. +         */ +        boolean mAnimateExit = true; + +        /** +         * Used for Dims not associated with a WindowContainer. See {@link Dimmer#dimAbove} for +         * details on Dim lifecycle. +         */ +        boolean mDontReset; + +        Change mCurrentProperties; +        Change mRequestedProperties; +        private AnimationSpec mAlphaAnimationSpec; +        private AnimationAdapter mLocalAnimationAdapter; + +        static class Change { +            private float mAlpha = -1f; +            private int mBlurRadius = -1; +            private WindowContainer mDimmingContainer = null; +            private int mRelativeLayer = -1; +            private boolean mSkipAnimation = false; + +            Change() {} + +            Change(Change other) { +                mAlpha = other.mAlpha; +                mBlurRadius = other.mBlurRadius; +                mDimmingContainer = other.mDimmingContainer; +                mRelativeLayer = other.mRelativeLayer; +            } + +            @Override +            public String toString() { +                return "Dim state: alpha=" + mAlpha + ", blur=" + mBlurRadius + ", container=" +                        + mDimmingContainer + ", relativePosition=" + mRelativeLayer +                        + ", skipAnimation=" + mSkipAnimation; +            } +        } + +        DimState(SurfaceControl dimLayer) { +            mDimLayer = dimLayer; +            mDimming = true; +            mCurrentProperties = new Change(); +            mRequestedProperties = new Change(); +        } + +        void setExitParameters(WindowContainer container) { +            setRequestedParameters(container, -1, 0, 0); +        } +        // Sets a requested change without applying it immediately +        void setRequestedParameters(WindowContainer container, int relativeLayer, float alpha, +                int blurRadius) { +            mRequestedProperties.mDimmingContainer = container; +            mRequestedProperties.mRelativeLayer = relativeLayer; +            mRequestedProperties.mAlpha = alpha; +            mRequestedProperties.mBlurRadius = blurRadius; +        } + +        /** +         * Commit the last changes we received. Called after +         * {@link Change#setRequestedParameters(WindowContainer, int, float, int)} +         */ +        void applyChanges(SurfaceControl.Transaction t) { +            if (mRequestedProperties.mDimmingContainer.mSurfaceControl == null) { +                Log.w(TAG, "container " + mRequestedProperties.mDimmingContainer +                        + "does not have a surface"); +                return; +            } +            if (!mDimState.mIsVisible) { +                mDimState.mIsVisible = true; +                t.show(mDimState.mDimLayer); +            } +            t.setRelativeLayer(mDimLayer, +                    mRequestedProperties.mDimmingContainer.getSurfaceControl(), +                    mRequestedProperties.mRelativeLayer); + +            if (aspectChanged()) { +                if (isAnimating()) { +                    mLocalAnimationAdapter.onAnimationCancelled(mDimLayer); +                } +                if (mRequestedProperties.mSkipAnimation +                        || (!dimmingContainerChanged() && mDimming)) { +                    // If the dimming container has not changed, then it is running its own +                    // animation, thus we can directly set the values we get requested, unless it's +                    // the exiting animation +                    ProtoLog.d(WM_DEBUG_DIMMER, +                            "Dim %s skipping animation and directly setting alpha=%f, blur=%d", +                            mDimLayer, mRequestedProperties.mAlpha, +                            mRequestedProperties.mBlurRadius); +                    t.setAlpha(mDimLayer, mRequestedProperties.mAlpha); +                    t.setBackgroundBlurRadius(mDimLayer, mRequestedProperties.mBlurRadius); +                    mRequestedProperties.mSkipAnimation = false; +                } else { +                    startAnimation(t); +                } +            } +            mCurrentProperties = new Change(mRequestedProperties); +        } + +        private void startAnimation(SurfaceControl.Transaction t) { +            mAlphaAnimationSpec = getRequestedAnimationSpec(mRequestedProperties.mAlpha, +                    mRequestedProperties.mBlurRadius); +            mLocalAnimationAdapter = mAnimationAdapterFactory.get(mAlphaAnimationSpec, +                    mHost.mWmService.mSurfaceAnimationRunner); + +            mLocalAnimationAdapter.startAnimation(mDimLayer, t, +                    ANIMATION_TYPE_DIMMER, (type, animator) -> { +                        t.setAlpha(mDimLayer, mRequestedProperties.mAlpha); +                        t.setBackgroundBlurRadius(mDimLayer, mRequestedProperties.mBlurRadius); +                        if (mRequestedProperties.mAlpha == 0f && !mDimming) { +                            ProtoLog.d(WM_DEBUG_DIMMER, +                                    "Removing dim surface %s on transaction %s", mDimLayer, t); +                            t.remove(mDimLayer); +                        } +                        mLocalAnimationAdapter = null; +                        mAlphaAnimationSpec = null; +                    }); +        } + +        private boolean isAnimating() { +            return mAlphaAnimationSpec != null; +        } + +        private boolean aspectChanged() { +            return Math.abs(mRequestedProperties.mAlpha - mCurrentProperties.mAlpha) > EPSILON +                    || mRequestedProperties.mBlurRadius != mCurrentProperties.mBlurRadius; +        } + +        private boolean dimmingContainerChanged() { +            return mRequestedProperties.mDimmingContainer != mCurrentProperties.mDimmingContainer; +        } + +        private AnimationSpec getRequestedAnimationSpec(float targetAlpha, int targetBlur) { +            final float startAlpha; +            final int startBlur; +            if (mAlphaAnimationSpec != null) { +                startAlpha = mAlphaAnimationSpec.mCurrentAlpha; +                startBlur = mAlphaAnimationSpec.mCurrentBlur; +            } else { +                startAlpha = Math.max(mCurrentProperties.mAlpha, 0f); +                startBlur = Math.max(mCurrentProperties.mBlurRadius, 0); +            } +            long duration = (long) (getDimDuration(mRequestedProperties.mDimmingContainer) +                    * Math.abs(targetAlpha - startAlpha)); + +            ProtoLog.v(WM_DEBUG_DIMMER, "Starting animation on dim layer %s, requested by %s, " +                            + "alpha: %f -> %f, blur: %d -> %d", +                    mDimLayer, mRequestedProperties.mDimmingContainer, startAlpha, targetAlpha, +                    startBlur, targetBlur); +            return new AnimationSpec( +                    new AnimationExtremes<>(startAlpha, targetAlpha), +                    new AnimationExtremes<>(startBlur, targetBlur), +                    duration +            ); +        } +    } + +    protected SmoothDimmer(WindowContainer host) { +        this(host, new AnimationAdapterFactory()); +    } + +    @VisibleForTesting +    SmoothDimmer(WindowContainer host, AnimationAdapterFactory animationFactory) { +        super(host); +        mAnimationAdapterFactory = animationFactory; +    } + +    private DimState obtainDimState(WindowContainer container) { +        if (mDimState == null) { +            try { +                final SurfaceControl ctl = makeDimLayer(); +                mDimState = new DimState(ctl); +            } catch (Surface.OutOfResourcesException e) { +                Log.w(TAG, "OutOfResourcesException creating dim surface"); +            } +        } + +        mLastRequestedDimContainer = container; +        return mDimState; +    } + +    private SurfaceControl makeDimLayer() { +        return mHost.makeChildSurface(null) +                .setParent(mHost.getSurfaceControl()) +                .setColorLayer() +                .setName("Dim Layer for - " + mHost.getName()) +                .setCallsite("Dimmer.makeDimLayer") +                .build(); +    } + +    @Override +    SurfaceControl getDimLayer() { +        return mDimState != null ? mDimState.mDimLayer : null; +    } + +    @Override +    void resetDimStates() { +        if (mDimState == null) { +            return; +        } +        if (!mDimState.mDontReset) { +            mDimState.mDimming = false; +        } +    } + +    @Override +    Rect getDimBounds() { +        return mDimState != null ? mDimState.mDimBounds : null; +    } + +    @Override +    void dontAnimateExit() { +        if (mDimState != null) { +            mDimState.mAnimateExit = false; +        } +    } + +    @Override +    protected void dim(WindowContainer container, int relativeLayer, float alpha, int blurRadius) { +        final DimState d = obtainDimState(container); + +        mDimState.mRequestedProperties.mDimmingContainer = container; +        mDimState.setRequestedParameters(container, relativeLayer, alpha, blurRadius); +        d.mDimming = true; +    } + +    boolean updateDims(SurfaceControl.Transaction t) { +        if (mDimState == null) { +            return false; +        } + +        if (!mDimState.mDimming) { +            // No one is dimming anymore, fade out dim and remove +            if (!mDimState.mAnimateExit) { +                if (mDimState.mDimLayer.isValid()) { +                    t.remove(mDimState.mDimLayer); +                } +            } else { +                mDimState.setExitParameters( +                        mDimState.mRequestedProperties.mDimmingContainer); +                mDimState.applyChanges(t); +            } +            mDimState = null; +            return false; +        } +        final Rect bounds = mDimState.mDimBounds; +        // TODO: Once we use geometry from hierarchy this falls away. +        t.setPosition(mDimState.mDimLayer, bounds.left, bounds.top); +        t.setWindowCrop(mDimState.mDimLayer, bounds.width(), bounds.height()); +        // Skip enter animation while starting window is on top of its activity +        final WindowState ws = mLastRequestedDimContainer.asWindowState(); +        if (!mDimState.mIsVisible && ws != null && ws.mActivityRecord != null +                && ws.mActivityRecord.mStartingData != null) { +            mDimState.mRequestedProperties.mSkipAnimation = true; +        } +        mDimState.applyChanges(t); +        return true; +    } + +    private long getDimDuration(WindowContainer container) { +        // Use the same duration as the animation on the WindowContainer +        AnimationAdapter animationAdapter = container.mSurfaceAnimator.getAnimation(); +        final float durationScale = container.mWmService.getTransitionAnimationScaleLocked(); +        return animationAdapter == null ? (long) (DEFAULT_DIM_ANIM_DURATION * durationScale) +                : animationAdapter.getDurationHint(); +    } + +    private static class AnimationExtremes<T> { +        final T mStartValue; +        final T mFinishValue; + +        AnimationExtremes(T fromValue, T toValue) { +            mStartValue = fromValue; +            mFinishValue = toValue; +        } +    } + +    private static class AnimationSpec implements LocalAnimationAdapter.AnimationSpec { +        private final long mDuration; +        private final AnimationExtremes<Float> mAlpha; +        private final AnimationExtremes<Integer> mBlur; + +        float mCurrentAlpha = 0; +        int mCurrentBlur = 0; + +        AnimationSpec(AnimationExtremes<Float> alpha, +                AnimationExtremes<Integer> blur, long duration) { +            mAlpha = alpha; +            mBlur = blur; +            mDuration = duration; +        } + +        @Override +        public long getDuration() { +            return mDuration; +        } + +        @Override +        public void apply(SurfaceControl.Transaction t, SurfaceControl sc, long currentPlayTime) { +            final float fraction = getFraction(currentPlayTime); +            mCurrentAlpha = +                    fraction * (mAlpha.mFinishValue - mAlpha.mStartValue) + mAlpha.mStartValue; +            mCurrentBlur = +                    (int) fraction * (mBlur.mFinishValue - mBlur.mStartValue) + mBlur.mStartValue; +            t.setAlpha(sc, mCurrentAlpha); +            t.setBackgroundBlurRadius(sc, mCurrentBlur); +        } + +        @Override +        public void dump(PrintWriter pw, String prefix) { +            pw.print(prefix); pw.print("from_alpha="); pw.print(mAlpha.mStartValue); +            pw.print(" to_alpha="); pw.print(mAlpha.mFinishValue); +            pw.print(prefix); pw.print("from_blur="); pw.print(mBlur.mStartValue); +            pw.print(" to_blur="); pw.print(mBlur.mFinishValue); +            pw.print(" duration="); pw.println(mDuration); +        } + +        @Override +        public void dumpDebugInner(ProtoOutputStream proto) { +            final long token = proto.start(ALPHA); +            proto.write(FROM, mAlpha.mStartValue); +            proto.write(TO, mAlpha.mFinishValue); +            proto.write(DURATION_MS, mDuration); +            proto.end(token); +        } +    } + +    static class AnimationAdapterFactory { + +        public AnimationAdapter get(LocalAnimationAdapter.AnimationSpec alphaAnimationSpec, +                SurfaceAnimationRunner runner) { +            return new LocalAnimationAdapter(alphaAnimationSpec, runner); +        } +    } +} diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java index 408ea6eb8c1a..c3de4d5acc21 100644 --- a/services/core/java/com/android/server/wm/SurfaceAnimator.java +++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java @@ -49,7 +49,8 @@ import java.util.function.Supplier;   * {@link AnimationAdapter}. When the animation is done animating, our callback to finish the   * animation will be invoked, at which we reparent the children back to the original parent.   */ -class SurfaceAnimator { +@VisibleForTesting +public class SurfaceAnimator {      private static final String TAG = TAG_WITH_CLASS_NAME ? "SurfaceAnimator" : TAG_WM; @@ -617,7 +618,8 @@ class SurfaceAnimator {       * Callback to be passed into {@link AnimationAdapter#startAnimation} to be invoked by the       * component that is running the animation when the animation is finished.       */ -    interface OnAnimationFinishedCallback { +    @VisibleForTesting +    public interface OnAnimationFinishedCallback {          void onAnimationFinished(@AnimationType int type, AnimationAdapter anim);      } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 83856153a709..4922e9028ed9 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -199,6 +199,7 @@ import com.android.server.Watchdog;  import com.android.server.am.ActivityManagerService;  import com.android.server.am.AppTimeTracker;  import com.android.server.uri.NeededUriGrants; +import com.android.window.flags.Flags;  import org.xmlpull.v1.XmlPullParser;  import org.xmlpull.v1.XmlPullParserException; @@ -488,10 +489,6 @@ class Task extends TaskFragment {      private boolean mForceShowForAllUsers; -    /** When set, will force the task to report as invisible. */ -    static final int FLAG_FORCE_HIDDEN_FOR_PINNED_TASK = 1; -    static final int FLAG_FORCE_HIDDEN_FOR_TASK_ORG = 1 << 1; -    private int mForceHiddenFlags = 0;      private boolean mForceTranslucent = false;      // The display category name for this task. @@ -2858,7 +2855,8 @@ class Task extends TaskFragment {      }      /** Bounds of the task to be used for dimming, as well as touch related tests. */ -    void getDimBounds(Rect out) { +    @Override +    void getDimBounds(@NonNull Rect out) {          if (isRootTask()) {              getBounds(out);              return; @@ -3306,7 +3304,8 @@ class Task extends TaskFragment {          // Once at the root task level, we want to check {@link #isTranslucent(ActivityRecord)}.          // If true, we want to get the Dimmer from the level above since we don't want to animate          // the dim with the Task. -        if (!isRootTask() || isTranslucent(null)) { +        if (!isRootTask() || (Flags.dimmerRefactor() && isTranslucentAndVisible()) +                || isTranslucent(null)) {              return super.getDimmer();          } @@ -4492,20 +4491,13 @@ class Task extends TaskFragment {       * Sets/unsets the forced-hidden state flag for this task depending on {@param set}.       * @return Whether the force hidden state changed       */ -    boolean setForceHidden(int flags, boolean set) { -        int newFlags = mForceHiddenFlags; -        if (set) { -            newFlags |= flags; -        } else { -            newFlags &= ~flags; -        } -        if (mForceHiddenFlags == newFlags) { -            return false; -        } - +    @Override +    boolean setForceHidden(@FlagForceHidden int flags, boolean set) {          final boolean wasHidden = isForceHidden();          final boolean wasVisible = isVisible(); -        mForceHiddenFlags = newFlags; +        if (!super.setForceHidden(flags, set)) { +            return false; +        }          final boolean nowHidden = isForceHidden();          if (wasHidden != nowHidden) {              final String reason = "setForceHidden"; @@ -4536,11 +4528,6 @@ class Task extends TaskFragment {          return super.isAlwaysOnTop();      } -    @Override -    protected boolean isForceHidden() { -        return mForceHiddenFlags != 0; -    } -      boolean isForceHiddenForPinnedTask() {          return (mForceHiddenFlags & FLAG_FORCE_HIDDEN_FOR_PINNED_TASK) != 0;      } @@ -5648,6 +5635,8 @@ class Task extends TaskFragment {              if (noAnimation) {                  mDisplayContent.prepareAppTransition(TRANSIT_NONE);                  mTaskSupervisor.mNoAnimActivities.add(top); +                mTransitionController.collect(top); +                mTransitionController.setNoAnimation(top);                  ActivityOptions.abort(options);              } else {                  updateTransitLocked(TRANSIT_TO_FRONT, options); diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 50bc825d8bea..906b3b55e015 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -103,6 +103,7 @@ import com.android.internal.annotations.VisibleForTesting;  import com.android.internal.protolog.common.ProtoLog;  import com.android.server.am.HostingRecord;  import com.android.server.pm.pkg.AndroidPackage; +import com.android.window.flags.Flags;  import java.io.FileDescriptor;  import java.io.PrintWriter; @@ -209,7 +210,26 @@ class TaskFragment extends WindowContainer<WindowContainer> {       */      int mMinHeight; -    Dimmer mDimmer = new Dimmer(this); +    Dimmer mDimmer = Flags.dimmerRefactor() +            ? new SmoothDimmer(this) : new LegacyDimmer(this); + +    /** Apply the dim layer on the embedded TaskFragment. */ +    static final int EMBEDDED_DIM_AREA_TASK_FRAGMENT = 0; + +    /** Apply the dim layer on the parent Task for an embedded TaskFragment. */ +    static final int EMBEDDED_DIM_AREA_PARENT_TASK = 1; + +    /** +     * The type of dim layer area for an embedded TaskFragment. +     */ +    @IntDef(prefix = {"EMBEDDED_DIM_AREA_"}, value = { +            EMBEDDED_DIM_AREA_TASK_FRAGMENT, +            EMBEDDED_DIM_AREA_PARENT_TASK, +    }) +    @interface EmbeddedDimArea {} + +    @EmbeddedDimArea +    private int mEmbeddedDimArea = EMBEDDED_DIM_AREA_TASK_FRAGMENT;      /** This task fragment will be removed when the cleanup of its children are done. */      private boolean mIsRemovalRequested; @@ -342,6 +362,19 @@ class TaskFragment extends WindowContainer<WindowContainer> {       */      private boolean mIsolatedNav; +    /** When set, will force the task to report as invisible. */ +    static final int FLAG_FORCE_HIDDEN_FOR_PINNED_TASK = 1; +    static final int FLAG_FORCE_HIDDEN_FOR_TASK_ORG = 1 << 1; +    static final int FLAG_FORCE_HIDDEN_FOR_TASK_FRAGMENT_ORG = 1 << 2; + +    @IntDef(prefix = {"FLAG_FORCE_HIDDEN_"}, value = { +            FLAG_FORCE_HIDDEN_FOR_PINNED_TASK, +            FLAG_FORCE_HIDDEN_FOR_TASK_ORG, +            FLAG_FORCE_HIDDEN_FOR_TASK_FRAGMENT_ORG, +    }, flag = true) +    @interface FlagForceHidden {} +    protected int mForceHiddenFlags = 0; +      final Point mLastSurfaceSize = new Point();      private final Rect mTmpBounds = new Rect(); @@ -825,7 +858,25 @@ class TaskFragment extends WindowContainer<WindowContainer> {       * Returns whether this TaskFragment is currently forced to be hidden for any reason.       */      protected boolean isForceHidden() { -        return false; +        return mForceHiddenFlags != 0; +    } + +    /** +     * Sets/unsets the forced-hidden state flag for this task depending on {@param set}. +     * @return Whether the force hidden state changed +     */ +    boolean setForceHidden(@FlagForceHidden int flags, boolean set) { +        int newFlags = mForceHiddenFlags; +        if (set) { +            newFlags |= flags; +        } else { +            newFlags &= ~flags; +        } +        if (mForceHiddenFlags == newFlags) { +            return false; +        } +        mForceHiddenFlags = newFlags; +        return true;      }      protected boolean isForceTranslucent() { @@ -969,7 +1020,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {          // A TaskFragment isn't translucent if it has at least one visible activity that occludes          // this TaskFragment.          return mTaskSupervisor.mOpaqueActivityHelper.getVisibleOpaqueActivity(this, -                starting) == null; +                starting, true /* ignoringKeyguard */) == null;      }      /** @@ -982,7 +1033,20 @@ class TaskFragment extends WindowContainer<WindowContainer> {              return true;          }          // Including finishing Activity if the TaskFragment is becoming invisible in the transition. -        return mTaskSupervisor.mOpaqueActivityHelper.getOpaqueActivity(this) == null; +        return mTaskSupervisor.mOpaqueActivityHelper.getOpaqueActivity(this, +                true /* ignoringKeyguard */) == null; +    } + +    /** +     * Like {@link  #isTranslucent(ActivityRecord)} but evaluating the actual visibility of the +     * windows rather than their visibility ignoring keyguard. +     */ +    boolean isTranslucentAndVisible() { +        if (!isAttached() || isForceHidden() || isForceTranslucent()) { +            return true; +        } +        return mTaskSupervisor.mOpaqueActivityHelper.getVisibleOpaqueActivity(this, null, +                false /* ignoringKeyguard */) == null;      }      ActivityRecord getTopNonFinishingActivity() { @@ -2929,14 +2993,27 @@ class TaskFragment extends WindowContainer<WindowContainer> {      @Override      Dimmer getDimmer() { -        // If the window is in an embedded TaskFragment, we want to dim at the TaskFragment. -        if (asTask() == null) { +        // If this is in an embedded TaskFragment and we want the dim applies on the TaskFragment. +        if (mIsEmbedded && mEmbeddedDimArea == EMBEDDED_DIM_AREA_TASK_FRAGMENT) {              return mDimmer;          }          return super.getDimmer();      } +    /** Bounds to be used for dimming, as well as touch related tests. */ +    void getDimBounds(@NonNull Rect out) { +        if (mIsEmbedded && mEmbeddedDimArea == EMBEDDED_DIM_AREA_PARENT_TASK) { +            out.set(getTask().getBounds()); +        } else { +            out.set(getBounds()); +        } +    } + +    void setEmbeddedDimArea(@EmbeddedDimArea int embeddedDimArea) { +        mEmbeddedDimArea = embeddedDimArea; +    } +      @Override      void prepareSurfaces() {          if (asTask() != null) { diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java index 04164c20a372..ff766beee337 100644 --- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java @@ -51,7 +51,6 @@ import android.window.ITaskFragmentOrganizer;  import android.window.ITaskFragmentOrganizerController;  import android.window.TaskFragmentInfo;  import android.window.TaskFragmentOperation; -import android.window.TaskFragmentOrganizerToken;  import android.window.TaskFragmentParentInfo;  import android.window.TaskFragmentTransaction;  import android.window.WindowContainerTransaction; @@ -745,9 +744,9 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr          }      } -    boolean isSystemOrganizer(@NonNull TaskFragmentOrganizerToken token) { +    boolean isSystemOrganizer(@NonNull IBinder organizerToken) {          final TaskFragmentOrganizerState state = -                mTaskFragmentOrganizerState.get(token.asBinder()); +                mTaskFragmentOrganizerState.get(organizerToken);          return state != null && state.mIsSystemOrganizer;      } diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index f3fb7c442b78..7d65c61193b5 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -418,8 +418,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {              if (transientRoot == null) continue;              final WindowContainer<?> rootParent = transientRoot.getParent();              if (rootParent == null || rootParent.getTopChild() == transientRoot) continue; -            final ActivityRecord topOpaque = mController.mAtm.mTaskSupervisor -                    .mOpaqueActivityHelper.getOpaqueActivity(rootParent); +            final ActivityRecord topOpaque = mController.mAtm.mTaskSupervisor.mOpaqueActivityHelper +                    .getOpaqueActivity(rootParent, true /* ignoringKeyguard */);              if (transientRoot.compareTo(topOpaque.getRootTask()) < 0) {                  occludedCount++;              } @@ -3321,8 +3321,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {              mFrozen.add(wc);              final ChangeInfo changeInfo = Objects.requireNonNull(mChanges.get(wc));              changeInfo.mSnapshot = snapshotSurface; -            if (isDisplayRotation) { -                // This isn't cheap, so only do it for display rotations. +            if (changeInfo.mRotation != wc.mDisplayContent.getRotation()) { +                // This isn't cheap, so only do it for rotation change.                  changeInfo.mSnapshotLuma = TransitionAnimation.getBorderLuma(                          buffer, screenshotBuffer.getColorSpace());              } diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index f509463c5409..8ac21e41f7f4 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -757,7 +757,7 @@ class TransitionController {              final TransitionRequestInfo request = new TransitionRequestInfo(transition.mType,                      startTaskInfo, pipTaskInfo, remoteTransition, displayChange, -                    transition.getFlags()); +                    transition.getFlags(), transition.getSyncId());              transition.mLogger.mRequestTimeNs = SystemClock.elapsedRealtimeNanos();              transition.mLogger.mRequest = request; @@ -1067,12 +1067,20 @@ class TransitionController {              // legacy sync              mSyncEngine.startSyncSet(queued.mLegacySync);          } -        // Post this so that the now-playing transition logic isn't interrupted. -        mAtm.mH.post(() -> { -            synchronized (mAtm.mGlobalLock) { -                queued.mOnStartCollect.onCollectStarted(true /* deferred */); -            } -        }); +        if (queued.mTransition != null +                && queued.mTransition.mType == WindowManager.TRANSIT_SLEEP) { +            // SLEEP transitions are special in that they don't collect anything (in fact if they +            // do collect things it can cause problems). So, we need to run it's onCollectStarted +            // immediately. +            queued.mOnStartCollect.onCollectStarted(true /* deferred */); +        } else { +            // Post this so that the now-playing transition logic isn't interrupted. +            mAtm.mH.post(() -> { +                synchronized (mAtm.mGlobalLock) { +                    queued.mOnStartCollect.onCollectStarted(true /* deferred */); +                } +            }); +        }      }      void moveToPlaying(Transition transition) { @@ -1584,8 +1592,8 @@ class TransitionController {          TransitionInfo mInfo;          private String buildOnSendLog() { -            StringBuilder sb = new StringBuilder("Sent Transition #").append(mSyncId) -                    .append(" createdAt=").append(TimeUtils.logTimeOfDay(mCreateWallTimeMs)); +            StringBuilder sb = new StringBuilder("Sent Transition (#").append(mSyncId) +                    .append(") createdAt=").append(TimeUtils.logTimeOfDay(mCreateWallTimeMs));              if (mRequest != null) {                  sb.append(" via request=").append(mRequest);              } @@ -1609,7 +1617,8 @@ class TransitionController {          void logOnSend() {              ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "%s", buildOnSendLog());              ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "    startWCT=%s", mStartWCT); -            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "    info=%s", mInfo); +            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "    info=%s", +                    mInfo.toString("    " /* prefix */));          }          private static String toMsString(long nanos) { @@ -1617,8 +1626,8 @@ class TransitionController {          }          private String buildOnFinishLog() { -            StringBuilder sb = new StringBuilder("Finish Transition #").append(mSyncId) -                    .append(": created at ").append(TimeUtils.logTimeOfDay(mCreateWallTimeMs)); +            StringBuilder sb = new StringBuilder("Finish Transition (#").append(mSyncId) +                    .append("): created at ").append(TimeUtils.logTimeOfDay(mCreateWallTimeMs));              sb.append(" collect-started=").append(toMsString(mCollectTimeNs - mCreateTimeNs));              if (mRequestTimeNs != 0) {                  sb.append(" request-sent=").append(toMsString(mRequestTimeNs - mCreateTimeNs)); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index f339d249e272..88f72f9dbc90 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2261,6 +2261,7 @@ public class WindowManagerService extends IWindowManager.Stub                      }                  } +                final boolean wasTrustedOverlay = win.isWindowTrustedOverlay();                  flagChanges = win.mAttrs.flags ^ attrs.flags;                  privateFlagChanges = win.mAttrs.privateFlags ^ attrs.privateFlags;                  attrChanges = win.mAttrs.copyFrom(attrs); @@ -2273,6 +2274,9 @@ public class WindowManagerService extends IWindowManager.Stub                  if (layoutChanged && win.providesDisplayDecorInsets()) {                      configChanged = displayPolicy.updateDecorInsetsInfo();                  } +                if (wasTrustedOverlay != win.isWindowTrustedOverlay()) { +                    win.updateTrustedOverlay(); +                }                  if (win.mActivityRecord != null && ((flagChanges & FLAG_SHOW_WHEN_LOCKED) != 0                          || (flagChanges & FLAG_DISMISS_KEYGUARD) != 0)) {                      win.mActivityRecord.checkKeyguardFlagsChanged(); @@ -5299,7 +5303,11 @@ public class WindowManagerService extends IWindowManager.Stub      public void displayReady() {          synchronized (mGlobalLock) {              if (mMaxUiWidth > 0) { -                mRoot.forAllDisplays(displayContent -> displayContent.setMaxUiWidth(mMaxUiWidth)); +                mRoot.forAllDisplays(dc -> { +                    if (dc.mDisplay.getType() == Display.TYPE_INTERNAL) { +                        dc.setMaxUiWidth(mMaxUiWidth); +                    } +                });              }              applyForcedPropertiesForDefaultDisplay();              mAnimator.ready(); @@ -8453,16 +8461,18 @@ public class WindowManagerService extends IWindowManager.Stub                      return true;                  }                  // For a task session, find the activity identified by the launch cookie. -                final WindowContainerToken wct = getTaskWindowContainerTokenForLaunchCookie( +                final WindowContainerInfo wci = getTaskWindowContainerInfoForLaunchCookie(                          incomingSession.getTokenToRecord()); -                if (wct == null) { +                if (wci == null) {                      Slog.w(TAG, "Handling a new recording session; unable to find the "                              + "WindowContainerToken");                      return false;                  }                  // Replace the launch cookie in the session details with the task's                  // WindowContainerToken. -                incomingSession.setTokenToRecord(wct.asBinder()); +                incomingSession.setTokenToRecord(wci.getToken().asBinder()); +                // Also replace the UNKNOWN target UID with the actual UID. +                incomingSession.setTargetUid(wci.getUid());                  mContentRecordingController.setContentRecordingSessionLocked(incomingSession,                          WindowManagerService.this);                  return true; @@ -8790,21 +8800,41 @@ public class WindowManagerService extends IWindowManager.Stub          mAtmService.setFocusedTask(task.mTaskId, touchedActivity);      } +    @VisibleForTesting +    static class WindowContainerInfo { +        private final int mUid; +        @NonNull private final WindowContainerToken mToken; + +        private WindowContainerInfo(int uid, @NonNull WindowContainerToken token) { +            this.mUid = uid; +            this.mToken = token; +        } + +        public int getUid() { +            return mUid; +        } + +        @NonNull +        public WindowContainerToken getToken() { +            return mToken; +        } +    } +      /** -     * Retrieve the {@link WindowContainerToken} of the task that contains the activity started -     * with the given launch cookie. +     * Retrieve the {@link WindowContainerInfo} of the task that contains the activity started with +     * the given launch cookie.       *       * @param launchCookie the launch cookie set on the {@link ActivityOptions} when starting an -     *                     activity +     *     activity       * @return a token representing the task containing the activity started with the given launch -     * cookie, or {@code null} if the token couldn't be found. +     *     cookie, or {@code null} if the token couldn't be found.       */      @VisibleForTesting      @Nullable -    WindowContainerToken getTaskWindowContainerTokenForLaunchCookie(@NonNull IBinder launchCookie) { +    WindowContainerInfo getTaskWindowContainerInfoForLaunchCookie(@NonNull IBinder launchCookie) {          // Find the activity identified by the launch cookie. -        final ActivityRecord targetActivity = mRoot.getActivity( -                activity -> activity.mLaunchCookie == launchCookie); +        final ActivityRecord targetActivity = +                mRoot.getActivity(activity -> activity.mLaunchCookie == launchCookie);          if (targetActivity == null) {              Slog.w(TAG, "Unable to find the activity for this launch cookie");              return null; @@ -8819,7 +8849,7 @@ public class WindowManagerService extends IWindowManager.Stub              Slog.w(TAG, "Unable to find the WindowContainerToken for " + targetActivity.getName());              return null;          } -        return taskWindowContainerToken; +        return new WindowContainerInfo(targetActivity.getUid(), taskWindowContainerToken);      }      /** diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index dd9a88f72bde..5ed6caffe1fb 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -24,7 +24,9 @@ import static android.view.Display.DEFAULT_DISPLAY;  import static android.window.TaskFragmentOperation.OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS;  import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT;  import static android.window.TaskFragmentOperation.OP_TYPE_DELETE_TASK_FRAGMENT; +import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_BOTTOM_OF_TASK;  import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_FRONT; +import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_TOP_OF_TASK;  import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;  import static android.window.TaskFragmentOperation.OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT;  import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS; @@ -34,6 +36,8 @@ import static android.window.TaskFragmentOperation.OP_TYPE_SET_ISOLATED_NAVIGATI  import static android.window.TaskFragmentOperation.OP_TYPE_SET_RELATIVE_BOUNDS;  import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;  import static android.window.TaskFragmentOperation.OP_TYPE_UNKNOWN; +import static android.window.WindowContainerTransaction.Change.CHANGE_FOCUSABLE; +import static android.window.WindowContainerTransaction.Change.CHANGE_HIDDEN;  import static android.window.WindowContainerTransaction.Change.CHANGE_RELATIVE_BOUNDS;  import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER;  import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_ADD_TASK_FRAGMENT_OPERATION; @@ -61,6 +65,7 @@ import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;  import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_PINNED_TASK;  import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;  import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED; +import static com.android.server.wm.TaskFragment.FLAG_FORCE_HIDDEN_FOR_TASK_FRAGMENT_ORG;  import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;  import static com.android.server.wm.WindowContainer.POSITION_TOP; @@ -821,6 +826,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub              return TRANSACT_EFFECTS_NONE;          } +        int effects = TRANSACT_EFFECTS_NONE;          // When the TaskFragment is resized, we may want to create a change transition for it, for          // which we want to defer the surface update until we determine whether or not to start          // change transition. @@ -843,7 +849,14 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub              c.getConfiguration().windowConfiguration.setBounds(absBounds);              taskFragment.setRelativeEmbeddedBounds(relBounds);          } -        final int effects = applyChanges(taskFragment, c); +        if ((c.getChangeMask() & WindowContainerTransaction.Change.CHANGE_HIDDEN) != 0) { +            if (taskFragment.setForceHidden( +                    FLAG_FORCE_HIDDEN_FOR_TASK_FRAGMENT_ORG, c.getHidden())) { +                effects |= TRANSACT_EFFECTS_LIFECYCLE; +            } +        } +        effects |= applyChanges(taskFragment, c); +          if (taskFragment.shouldStartChangeTransition(mTmpBounds0, mTmpBounds1)) {              taskFragment.initializeChangeTransition(mTmpBounds0);          } @@ -1393,6 +1406,24 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub                  taskFragment.setIsolatedNav(isolatedNav);                  break;              } +            case OP_TYPE_REORDER_TO_BOTTOM_OF_TASK: { +                final Task task = taskFragment.getTask(); +                if (task != null) { +                    task.mChildren.remove(taskFragment); +                    task.mChildren.add(0, taskFragment); +                    effects |= TRANSACT_EFFECTS_LIFECYCLE; +                } +                break; +            } +            case OP_TYPE_REORDER_TO_TOP_OF_TASK: { +                final Task task = taskFragment.getTask(); +                if (task != null) { +                    task.mChildren.remove(taskFragment); +                    task.mChildren.add(taskFragment); +                    effects |= TRANSACT_EFFECTS_LIFECYCLE; +                } +                break; +            }          }          return effects;      } @@ -1420,6 +1451,18 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub              return false;          } +        if ((opType == OP_TYPE_REORDER_TO_BOTTOM_OF_TASK +                || opType == OP_TYPE_REORDER_TO_TOP_OF_TASK) +                && !mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) { +            final Throwable exception = new SecurityException( +                    "Only a system organizer can perform OP_TYPE_REORDER_TO_BOTTOM_OF_TASK or " +                            + "OP_TYPE_REORDER_TO_TOP_OF_TASK." +            ); +            sendTaskFragmentOperationFailure(organizer, errorCallbackToken, taskFragment, +                    opType, exception); +            return false; +        } +          final IBinder secondaryFragmentToken = operation.getSecondaryFragmentToken();          return secondaryFragmentToken == null                  || validateTaskFragment(mLaunchTaskFragments.get(secondaryFragmentToken), opType, @@ -1920,6 +1963,11 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub       * For config change on {@link TaskFragment}, we only support the following operations:       * {@link WindowContainerTransaction#setRelativeBounds(WindowContainerToken, Rect)},       * {@link WindowContainerTransaction#setWindowingMode(WindowContainerToken, int)}. +     * +     * For a system organizer, we additionally support +     * {@link WindowContainerTransaction#setHidden(WindowContainerToken, boolean)}, and +     * {@link WindowContainerTransaction#setFocusable(WindowContainerToken, boolean)}. See +     * {@link TaskFragmentOrganizerController#registerOrganizer(ITaskFragmentOrganizer, boolean)}       */      private void enforceTaskFragmentConfigChangeAllowed(@NonNull String func,              @Nullable WindowContainer wc, @NonNull WindowContainerTransaction.Change change, @@ -1938,31 +1986,49 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub              throw new SecurityException(msg);          } -        final int changeMask = change.getChangeMask(); -        final int configSetMask = change.getConfigSetMask(); -        final int windowSetMask = change.getWindowSetMask(); -        if (changeMask == 0 && configSetMask == 0 && windowSetMask == 0 -                && change.getWindowingMode() >= 0) { -            // The change contains only setWindowingMode, which is allowed. -            return; +        final int originalChangeMask = change.getChangeMask(); +        final int originalConfigSetMask = change.getConfigSetMask(); +        final int originalWindowSetMask = change.getWindowSetMask(); + +        int changeMaskToBeChecked = originalChangeMask; +        int configSetMaskToBeChecked = originalConfigSetMask; +        int windowSetMaskToBeChecked = originalWindowSetMask; + +        if (mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) { +            // System organizer is allowed to update the hidden and focusable state. +            // We unset the CHANGE_HIDDEN and CHANGE_FOCUSABLE bits because they are checked here. +            changeMaskToBeChecked &= ~CHANGE_HIDDEN; +            changeMaskToBeChecked &= ~CHANGE_FOCUSABLE;          } -        if (changeMask != CHANGE_RELATIVE_BOUNDS -                || configSetMask != ActivityInfo.CONFIG_WINDOW_CONFIGURATION -                || windowSetMask != WindowConfiguration.WINDOW_CONFIG_BOUNDS) { -            // None of the change should be requested from a TaskFragment organizer except -            // setRelativeBounds and setWindowingMode. -            // For setRelativeBounds, we don't need to check whether it is outside of the Task + +        // setRelativeBounds is allowed. +        if ((changeMaskToBeChecked & CHANGE_RELATIVE_BOUNDS) != 0 +                && (configSetMaskToBeChecked & ActivityInfo.CONFIG_WINDOW_CONFIGURATION) != 0 +                && (windowSetMaskToBeChecked & WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0) { +            // For setRelativeBounds, we don't need to check whether it is outside the Task              // bounds, because it is possible that the Task is also resizing, for which we don't              // want to throw an exception. The bounds will be adjusted in              // TaskFragment#translateRelativeBoundsToAbsoluteBounds. -            String msg = "Permission Denial: " + func + " from pid=" -                    + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() -                    + " trying to apply changes of changeMask=" + changeMask -                    + " configSetMask=" + configSetMask + " windowSetMask=" + windowSetMask -                    + " to TaskFragment=" + tf + " TaskFragmentOrganizer=" + organizer; -            Slog.w(TAG, msg); -            throw new SecurityException(msg); +            changeMaskToBeChecked &= ~CHANGE_RELATIVE_BOUNDS; +            configSetMaskToBeChecked &= ~ActivityInfo.CONFIG_WINDOW_CONFIGURATION; +            windowSetMaskToBeChecked &= ~WindowConfiguration.WINDOW_CONFIG_BOUNDS;          } + +        if (changeMaskToBeChecked == 0 && configSetMaskToBeChecked == 0 +                && windowSetMaskToBeChecked == 0) { +            // All the changes have been checked. +            // Note that setWindowingMode is always allowed, so we don't need to check the mask. +            return; +        } + +        final String msg = "Permission Denial: " + func + " from pid=" +                + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() +                + " trying to apply changes of changeMask=" + originalChangeMask +                + " configSetMask=" + originalConfigSetMask +                + " windowSetMask=" + originalWindowSetMask +                + " to TaskFragment=" + tf + " TaskFragmentOrganizer=" + organizer; +        Slog.w(TAG, msg); +        throw new SecurityException(msg);      }      private void createTaskFragment(@NonNull TaskFragmentCreationParams creationParams, @@ -2019,7 +2085,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub          TaskFragmentOrganizerToken organizerToken = creationParams.getOrganizer();          taskFragment.setTaskFragmentOrganizer(organizerToken,                  ownerActivity.getUid(), ownerActivity.info.processName, -                mTaskFragmentOrganizerController.isSystemOrganizer(organizerToken)); +                mTaskFragmentOrganizerController.isSystemOrganizer(organizerToken.asBinder()));          final int position;          if (creationParams.getPairedPrimaryFragmentToken() != null) {              // When there is a paired primary TaskFragment, we want to place the new TaskFragment diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 726d4d7c34e7..7f36aec69480 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -1189,7 +1189,20 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP          }      } -    public boolean isWindowTrustedOverlay() { +    @Override +    void setInitialSurfaceControlProperties(SurfaceControl.Builder b) { +        super.setInitialSurfaceControlProperties(b); +        if (surfaceTrustedOverlay() && isWindowTrustedOverlay()) { +            getPendingTransaction().setTrustedOverlay(mSurfaceControl, true); +        } +    } + +    void updateTrustedOverlay() { +        mInputWindowHandle.setTrustedOverlay(getPendingTransaction(), mSurfaceControl, +                isWindowTrustedOverlay()); +    } + +    boolean isWindowTrustedOverlay() {          return InputMonitor.isTrustedOverlay(mAttrs.type)                  || ((mAttrs.privateFlags & PRIVATE_FLAG_TRUSTED_OVERLAY) != 0                          && mSession.mCanAddInternalSystemWindow) @@ -2756,12 +2769,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP                  // bounds, as they would be used to display the dim layer.                  final TaskFragment taskFragment = getTaskFragment();                  if (taskFragment != null) { -                    final Task task = taskFragment.asTask(); -                    if (task != null) { -                        task.getDimBounds(mTmpRect); -                    } else { -                        mTmpRect.set(taskFragment.getBounds()); -                    } +                    taskFragment.getDimBounds(mTmpRect);                  } else if (getRootTask() != null) {                      getRootTask().getDimBounds(mTmpRect);                  } @@ -5205,9 +5213,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP              updateFrameRateSelectionPriorityIfNeeded();              updateScaleIfNeeded();              mWinAnimator.prepareSurfaceLocked(getSyncTransaction()); -            if (surfaceTrustedOverlay()) { -                getSyncTransaction().setTrustedOverlay(mSurfaceControl, isWindowTrustedOverlay()); -            }          }          super.prepareSurfaces();      } diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index e434f296bbb5..7d21dbf85a66 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -569,6 +569,7 @@ class WindowToken extends WindowContainer<WindowState> {                  && asActivityRecord() != null && isVisible()) {              // Trigger an activity level rotation transition.              mTransitionController.requestTransitionIfNeeded(WindowManager.TRANSIT_CHANGE, this); +            mTransitionController.collectVisibleChange(this);              mTransitionController.setReady(this);          }          final int originalRotation = getWindowConfiguration().getRotation(); diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index bc70658a06c4..24ee16389fd2 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -70,7 +70,7 @@ cc_library_static {          "com_android_server_UsbHostManager.cpp",          "com_android_server_vibrator_VibratorController.cpp",          "com_android_server_vibrator_VibratorManagerService.cpp", -        "com_android_server_PersistentDataBlockService.cpp", +        "com_android_server_pdb_PersistentDataBlockService.cpp",          "com_android_server_am_LowMemDetector.cpp",          "com_android_server_pm_PackageManagerShellCommandDataLoader.cpp",          "com_android_server_sensor_SensorService.cpp", @@ -189,7 +189,7 @@ cc_defaults {          "android.hardware.thermal@1.0",          "android.hardware.thermal-V1-ndk",          "android.hardware.tv.input@1.0", -        "android.hardware.tv.input-V1-ndk", +        "android.hardware.tv.input-V2-ndk",          "android.hardware.vibrator-V2-cpp",          "android.hardware.vibrator@1.0",          "android.hardware.vibrator@1.1", @@ -244,5 +244,5 @@ filegroup {  filegroup {      name: "lib_oomConnection_native", -    srcs: ["com_android_server_am_OomConnection.cpp",], +    srcs: ["com_android_server_am_OomConnection.cpp"],  } diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS index 7e8ce60b8a50..0e45f61cbfe1 100644 --- a/services/core/jni/OWNERS +++ b/services/core/jni/OWNERS @@ -20,6 +20,7 @@ per-file com_android_server_lights_* = file:/services/core/java/com/android/serv  per-file com_android_server_location_* = file:/location/java/android/location/OWNERS  per-file com_android_server_locksettings_* = file:/services/core/java/com/android/server/locksettings/OWNERS  per-file com_android_server_net_* = file:/services/core/java/com/android/server/net/OWNERS +per-file com_android_server_pdb_* = file:/services/core/java/com/android/server/pdb/OWNERS  per-file com_android_server_pm_* = file:/services/core/java/com/android/server/pm/OWNERS  per-file com_android_server_power_* = file:/services/core/java/com/android/server/power/OWNERS  per-file com_android_server_powerstats_* = file:/services/core/java/com/android/server/powerstats/OWNERS diff --git a/services/core/jni/com_android_server_PersistentDataBlockService.cpp b/services/core/jni/com_android_server_PersistentDataBlockService.cpp deleted file mode 100644 index 97e69fb4bb40..000000000000 --- a/services/core/jni/com_android_server_PersistentDataBlockService.cpp +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (C) 2010 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. - */ - -#include <android_runtime/AndroidRuntime.h> -#include <nativehelper/JNIHelp.h> -#include <jni.h> -#include <nativehelper/ScopedUtfChars.h> - -#include <utils/misc.h> -#include <sys/ioctl.h> -#include <sys/mount.h> -#include <utils/Log.h> - - -#include <inttypes.h> -#include <fcntl.h> -#include <errno.h> -#include <string.h> - -namespace android { - -    uint64_t get_block_device_size(int fd) -    { -        uint64_t size = 0; -        int ret; - -        ret = ioctl(fd, BLKGETSIZE64, &size); - -        if (ret) -            return 0; - -        return size; -    } - -    int wipe_block_device(int fd) -    { -        uint64_t range[2]; -        int ret; -        uint64_t len = get_block_device_size(fd); - -        range[0] = 0; -        range[1] = len; - -        if (range[1] == 0) -            return 0; - -        ret = ioctl(fd, BLKSECDISCARD, &range); -        if (ret < 0) { -            ALOGE("Something went wrong secure discarding block: %s\n", strerror(errno)); -            range[0] = 0; -            range[1] = len; -            ret = ioctl(fd, BLKDISCARD, &range); -            if (ret < 0) { -                ALOGE("Discard failed: %s\n", strerror(errno)); -                return -1; -            } else { -                ALOGE("Wipe via secure discard failed, used non-secure discard instead\n"); -                return 0; -            } - -        } - -        return ret; -    } - -    static jlong com_android_server_PersistentDataBlockService_getBlockDeviceSize(JNIEnv *env, jclass, jstring jpath) -    { -        ScopedUtfChars path(env, jpath); -        int fd = open(path.c_str(), O_RDONLY); - -        if (fd < 0) -            return 0; - -        const uint64_t size = get_block_device_size(fd); - -        close(fd); - -        return size; -    } - -    static int com_android_server_PersistentDataBlockService_wipe(JNIEnv *env, jclass, jstring jpath) { -        ScopedUtfChars path(env, jpath); -        int fd = open(path.c_str(), O_WRONLY); - -        if (fd < 0) -            return 0; - -        const int ret = wipe_block_device(fd); - -        close(fd); - -        return ret; -    } - -    static const JNINativeMethod sMethods[] = { -         /* name, signature, funcPtr */ -        {"nativeGetBlockDeviceSize", "(Ljava/lang/String;)J", (void*)com_android_server_PersistentDataBlockService_getBlockDeviceSize}, -        {"nativeWipe", "(Ljava/lang/String;)I", (void*)com_android_server_PersistentDataBlockService_wipe}, -    }; - -    int register_android_server_PersistentDataBlockService(JNIEnv* env) -    { -        return jniRegisterNativeMethods(env, "com/android/server/PersistentDataBlockService", -                                        sMethods, NELEM(sMethods)); -    } - -} /* namespace android */
\ No newline at end of file diff --git a/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp b/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp index b256f168f2af..1844d3063cd8 100644 --- a/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp +++ b/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp @@ -24,33 +24,33 @@  #include "utils/Log.h"  namespace android { -static void nativeUpdateSmallAreaDetection(JNIEnv* env, jclass clazz, jintArray juids, +static void nativeUpdateSmallAreaDetection(JNIEnv* env, jclass clazz, jintArray jappIds,                                             jfloatArray jthresholds) { -    if (juids == nullptr || jthresholds == nullptr) return; +    if (jappIds == nullptr || jthresholds == nullptr) return; -    ScopedIntArrayRO uids(env, juids); +    ScopedIntArrayRO appIds(env, jappIds);      ScopedFloatArrayRO thresholds(env, jthresholds); -    if (uids.size() != thresholds.size()) { -        ALOGE("uids size exceeds thresholds size!"); +    if (appIds.size() != thresholds.size()) { +        ALOGE("appIds size exceeds thresholds size!");          return;      } -    std::vector<int32_t> uidVector; +    std::vector<int32_t> appIdVector;      std::vector<float> thresholdVector; -    size_t size = uids.size(); -    uidVector.reserve(size); +    size_t size = appIds.size(); +    appIdVector.reserve(size);      thresholdVector.reserve(size);      for (int i = 0; i < size; i++) { -        uidVector.push_back(static_cast<int32_t>(uids[i])); +        appIdVector.push_back(static_cast<int32_t>(appIds[i]));          thresholdVector.push_back(static_cast<float>(thresholds[i]));      } -    SurfaceComposerClient::updateSmallAreaDetection(uidVector, thresholdVector); +    SurfaceComposerClient::updateSmallAreaDetection(appIdVector, thresholdVector);  } -static void nativeSetSmallAreaDetectionThreshold(JNIEnv* env, jclass clazz, jint uid, +static void nativeSetSmallAreaDetectionThreshold(JNIEnv* env, jclass clazz, jint appId,                                                   jfloat threshold) { -    SurfaceComposerClient::setSmallAreaDetectionThreshold(uid, threshold); +    SurfaceComposerClient::setSmallAreaDetectionThreshold(appId, threshold);  }  static const JNINativeMethod gMethods[] = { diff --git a/services/core/jni/com_android_server_pdb_PersistentDataBlockService.cpp b/services/core/jni/com_android_server_pdb_PersistentDataBlockService.cpp new file mode 100644 index 000000000000..1e3cfd049e65 --- /dev/null +++ b/services/core/jni/com_android_server_pdb_PersistentDataBlockService.cpp @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2010 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. + */ + +#include <android_runtime/AndroidRuntime.h> +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <jni.h> +#include <nativehelper/JNIHelp.h> +#include <nativehelper/ScopedUtfChars.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/mount.h> +#include <utils/Log.h> +#include <utils/misc.h> + +namespace android { + +uint64_t get_block_device_size(int fd) { +    uint64_t size = 0; +    int ret; + +    ret = ioctl(fd, BLKGETSIZE64, &size); + +    if (ret) return 0; + +    return size; +} + +int wipe_block_device(int fd) { +    uint64_t range[2]; +    int ret; +    uint64_t len = get_block_device_size(fd); + +    range[0] = 0; +    range[1] = len; + +    if (range[1] == 0) return 0; + +    ret = ioctl(fd, BLKSECDISCARD, &range); +    if (ret < 0) { +        ALOGE("Something went wrong secure discarding block: %s\n", strerror(errno)); +        range[0] = 0; +        range[1] = len; +        ret = ioctl(fd, BLKDISCARD, &range); +        if (ret < 0) { +            ALOGE("Discard failed: %s\n", strerror(errno)); +            return -1; +        } else { +            ALOGE("Wipe via secure discard failed, used non-secure discard instead\n"); +            return 0; +        } +    } + +    return ret; +} + +static jlong com_android_server_pdb_PersistentDataBlockService_getBlockDeviceSize(JNIEnv *env, +                                                                                  jclass, +                                                                                  jstring jpath) { +    ScopedUtfChars path(env, jpath); +    int fd = open(path.c_str(), O_RDONLY); + +    if (fd < 0) return 0; + +    const uint64_t size = get_block_device_size(fd); + +    close(fd); + +    return size; +} + +static int com_android_server_pdb_PersistentDataBlockService_wipe(JNIEnv *env, jclass, +                                                                  jstring jpath) { +    ScopedUtfChars path(env, jpath); +    int fd = open(path.c_str(), O_WRONLY); + +    if (fd < 0) return 0; + +    const int ret = wipe_block_device(fd); + +    close(fd); + +    return ret; +} + +static const JNINativeMethod sMethods[] = { +        /* name, signature, funcPtr */ +        {"nativeGetBlockDeviceSize", "(Ljava/lang/String;)J", +         (void *)com_android_server_pdb_PersistentDataBlockService_getBlockDeviceSize}, +        {"nativeWipe", "(Ljava/lang/String;)I", +         (void *)com_android_server_pdb_PersistentDataBlockService_wipe}, +}; + +int register_android_server_pdb_PersistentDataBlockService(JNIEnv *env) { +    return jniRegisterNativeMethods(env, "com/android/server/pdb/PersistentDataBlockService", +                                    sMethods, NELEM(sMethods)); +} + +} /* namespace android */
\ No newline at end of file diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index df4489528be5..11734da5b1ac 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -47,7 +47,7 @@ int register_android_server_connectivity_Vpn(JNIEnv* env);  int register_android_server_devicepolicy_CryptoTestHelper(JNIEnv*);  int register_android_server_tv_TvUinputBridge(JNIEnv* env);  int register_android_server_tv_TvInputHal(JNIEnv* env); -int register_android_server_PersistentDataBlockService(JNIEnv* env); +int register_android_server_pdb_PersistentDataBlockService(JNIEnv* env);  int register_android_server_Watchdog(JNIEnv* env);  int register_android_server_HardwarePropertiesManagerService(JNIEnv* env);  int register_android_server_SyntheticPasswordManager(JNIEnv* env); @@ -108,7 +108,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)      register_android_server_BatteryStatsService(env);      register_android_server_tv_TvUinputBridge(env);      register_android_server_tv_TvInputHal(env); -    register_android_server_PersistentDataBlockService(env); +    register_android_server_pdb_PersistentDataBlockService(env);      register_android_server_HardwarePropertiesManagerService(env);      register_android_server_storage_AppFuse(env);      register_android_server_SyntheticPasswordManager(env); diff --git a/services/core/jni/tvinput/JTvInputHal.cpp b/services/core/jni/tvinput/JTvInputHal.cpp index c7366173ecda..dc05462c1a1c 100644 --- a/services/core/jni/tvinput/JTvInputHal.cpp +++ b/services/core/jni/tvinput/JTvInputHal.cpp @@ -368,12 +368,20 @@ JTvInputHal::TvInputEventWrapper JTvInputHal::TvInputEventWrapper::createEventWr  }  JTvInputHal::TvMessageEventWrapper JTvInputHal::TvMessageEventWrapper::createEventWrapper( -        const AidlTvMessageEvent& aidlTvMessageEvent) { +        const AidlTvMessageEvent& aidlTvMessageEvent, bool isLegacyMessage) { +    auto messageList = aidlTvMessageEvent.messages;      TvMessageEventWrapper event; -    event.messages.insert(event.messages.begin(), std::begin(aidlTvMessageEvent.messages) + 1, -                          std::end(aidlTvMessageEvent.messages)); +    // Handle backwards compatibility for V1 +    if (isLegacyMessage) { +        event.deviceId = messageList[0].groupId; +        event.messages.insert(event.messages.begin(), std::begin(messageList) + 1, +                              std::end(messageList)); +    } else { +        event.deviceId = aidlTvMessageEvent.deviceId; +        event.messages.insert(event.messages.begin(), std::begin(messageList), +                              std::end(messageList)); +    }      event.streamId = aidlTvMessageEvent.streamId; -    event.deviceId = aidlTvMessageEvent.messages[0].groupId;      event.type = aidlTvMessageEvent.type;      return event;  } @@ -449,15 +457,30 @@ JTvInputHal::TvInputCallback::TvInputCallback(JTvInputHal* hal) {  ::ndk::ScopedAStatus JTvInputHal::TvInputCallback::notifyTvMessageEvent(          const AidlTvMessageEvent& event) {      const std::string DEVICE_ID_SUBTYPE = "device_id"; -    if (event.messages.size() > 1 && event.messages[0].subType == DEVICE_ID_SUBTYPE) { -        mHal->mLooper -                ->sendMessage(new NotifyTvMessageHandler(mHal, -                                                         TvMessageEventWrapper::createEventWrapper( -                                                                 event)), -                              static_cast<int>(event.type)); +    ::ndk::ScopedAStatus status = ::ndk::ScopedAStatus::ok(); +    int32_t aidlVersion = 0; +    if (mHal->mTvInput->getAidlInterfaceVersion(&aidlVersion).isOk() && event.messages.size() > 0) { +        bool validLegacyMessage = aidlVersion == 1 && +                event.messages[0].subType == DEVICE_ID_SUBTYPE && event.messages.size() > 1; +        bool validTvMessage = aidlVersion > 1 && event.messages.size() > 0; +        if (validLegacyMessage || validTvMessage) { +            mHal->mLooper->sendMessage( +                    new NotifyTvMessageHandler(mHal, +                                               TvMessageEventWrapper:: +                                                       createEventWrapper(event, +                                                                          validLegacyMessage)), +                    static_cast<int>(event.type)); +        } else { +            status = ::ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); +            ALOGE("The TVMessage event was malformed for HAL version: %d", aidlVersion); +        } +    } else { +        status = ::ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); +        ALOGE("The TVMessage event was empty or the HAL version (version: %d) could not " +              "be inferred.", +              aidlVersion);      } - -    return ::ndk::ScopedAStatus::ok(); +    return status;  }  JTvInputHal::ITvInputWrapper::ITvInputWrapper(std::shared_ptr<AidlITvInput>& aidlTvInput) @@ -521,4 +544,12 @@ JTvInputHal::ITvInputWrapper::ITvInputWrapper(std::shared_ptr<AidlITvInput>& aid      }  } +::ndk::ScopedAStatus JTvInputHal::ITvInputWrapper::getAidlInterfaceVersion(int32_t* _aidl_return) { +    if (mIsHidl) { +        return ::ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); +    } else { +        return mAidlTvInput->getInterfaceVersion(_aidl_return); +    } +} +  } // namespace android diff --git a/services/core/jni/tvinput/JTvInputHal.h b/services/core/jni/tvinput/JTvInputHal.h index 1d8d1629f67d..6026a107c67f 100644 --- a/services/core/jni/tvinput/JTvInputHal.h +++ b/services/core/jni/tvinput/JTvInputHal.h @@ -138,7 +138,7 @@ private:          TvMessageEventWrapper() {}          static TvMessageEventWrapper createEventWrapper( -                const AidlTvMessageEvent& aidlTvMessageEvent); +                const AidlTvMessageEvent& aidlTvMessageEvent, bool isLegacyMessage);          int streamId;          int deviceId; @@ -195,6 +195,7 @@ private:          ::ndk::ScopedAStatus getTvMessageQueueDesc(                  MQDescriptor<int8_t, SynchronizedReadWrite>* out_queue, int32_t in_deviceId,                  int32_t in_streamId); +        ::ndk::ScopedAStatus getAidlInterfaceVersion(int32_t* _aidl_return);      private:          ::ndk::ScopedAStatus hidlSetCallback(const std::shared_ptr<TvInputCallback>& in_callback); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 49af89b5f02e..5a620a3b87f5 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -486,7 +486,6 @@ import com.android.server.AlarmManagerInternal;  import com.android.server.LocalManagerRegistry;  import com.android.server.LocalServices;  import com.android.server.LockGuard; -import com.android.server.PersistentDataBlockManagerInternal;  import com.android.server.SystemServerInitThreadPool;  import com.android.server.SystemService;  import com.android.server.SystemServiceManager; @@ -494,6 +493,7 @@ import com.android.server.devicepolicy.ActiveAdmin.TrustAgentInfo;  import com.android.server.devicepolicy.flags.FlagUtils;  import com.android.server.inputmethod.InputMethodManagerInternal;  import com.android.server.net.NetworkPolicyManagerInternal; +import com.android.server.pdb.PersistentDataBlockManagerInternal;  import com.android.server.pm.DefaultCrossProfileIntentFilter;  import com.android.server.pm.DefaultCrossProfileIntentFiltersUtils;  import com.android.server.pm.PackageManagerLocal; diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 59f1edcf309d..924e2f8ec654 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -161,6 +161,7 @@ import com.android.server.os.BugreportManagerService;  import com.android.server.os.DeviceIdentifiersPolicyService;  import com.android.server.os.NativeTombstoneManagerService;  import com.android.server.os.SchedulingPolicyService; +import com.android.server.pdb.PersistentDataBlockService;  import com.android.server.people.PeopleService;  import com.android.server.permission.access.AccessCheckingService;  import com.android.server.pm.ApexManager; @@ -337,6 +338,8 @@ public final class SystemServer implements Dumpable {              "com.android.clockwork.modes.ModeManagerService";      private static final String WEAR_DISPLAY_SERVICE_CLASS =              "com.android.clockwork.display.WearDisplayService"; +    private static final String WEAR_DEBUG_SERVICE_CLASS = +            "com.android.clockwork.debug.WearDebugService";      private static final String WEAR_TIME_SERVICE_CLASS =              "com.android.clockwork.time.WearTimeService";      private static final String WEAR_SETTINGS_SERVICE_CLASS = @@ -2635,6 +2638,12 @@ public final class SystemServer implements Dumpable {              mSystemServiceManager.startService(WEAR_DISPLAY_SERVICE_CLASS);              t.traceEnd(); +            if (Build.IS_DEBUGGABLE) { +                t.traceBegin("StartWearDebugService"); +                mSystemServiceManager.startService(WEAR_DEBUG_SERVICE_CLASS); +                t.traceEnd(); +            } +              t.traceBegin("StartWearTimeService");              mSystemServiceManager.startService(WEAR_TIME_SERVICE_CLASS);              t.traceEnd(); diff --git a/services/midi/Android.bp b/services/midi/Android.bp index 5adcfbaf432e..4b5f8a7bf0ac 100644 --- a/services/midi/Android.bp +++ b/services/midi/Android.bp @@ -19,4 +19,7 @@ java_library_static {      defaults: ["platform_service_defaults"],      srcs: [":services.midi-sources"],      libs: ["services.core"], +    static_libs: [ +        "aconfig_midi_flags_java_lib", +    ],  } diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java index a8902fcf77af..2f47cc7160b7 100644 --- a/services/midi/java/com/android/server/midi/MidiService.java +++ b/services/midi/java/com/android/server/midi/MidiService.java @@ -16,6 +16,8 @@  package com.android.server.midi; +import static com.android.media.midi.flags.Flags.virtualUmp; +  import android.Manifest;  import android.annotation.NonNull;  import android.annotation.RequiresPermission; @@ -1549,6 +1551,12 @@ public class MidiService extends IMidiManager.Stub {                  return;              } +            if (!virtualUmp()) { +                Log.w(TAG, "Skipping MIDI device service " + serviceInfo.packageName +                        + ": virtual UMP flag not enabled"); +                return; +            } +              Bundle properties = null;              int numPorts = 0;              boolean isPrivate = false; diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java index 2889c749f679..cbedcaf97358 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java @@ -24,7 +24,9 @@ import static android.content.pm.SuspendDialogInfo.BUTTON_ACTION_MORE_DETAILS;  import static android.content.pm.SuspendDialogInfo.BUTTON_ACTION_UNSUSPEND;  import static android.content.pm.parsing.FrameworkParsingPackageUtils.parsePublicKey;  import static android.content.res.Resources.ID_NULL; +  import static com.android.server.pm.PackageManagerService.WRITE_USER_PACKAGE_RESTRICTIONS; +  import static org.hamcrest.CoreMatchers.equalTo;  import static org.hamcrest.CoreMatchers.is;  import static org.hamcrest.CoreMatchers.not; @@ -41,6 +43,7 @@ import static org.mockito.Mockito.when;  import android.annotation.NonNull;  import android.app.PropertyInvalidatedCache; +import android.content.ComponentName;  import android.content.pm.ApplicationInfo;  import android.content.pm.PackageManager;  import android.content.pm.SuspendDialogInfo; @@ -871,12 +874,20 @@ public class PackageManagerSettingsTests {                  .setUid(packageSetting.getAppId())                  .hideAsFinal()); -        ArchiveState archiveState = new ArchiveState( -                List.of(new ArchiveState.ArchiveActivityInfo("title1", Path.of("/path1"), -                                Path.of("/monochromePath1")), -                        new ArchiveState.ArchiveActivityInfo("title2", Path.of("/path2"), -                                Path.of("/monochromePath2"))), -                "installerTitle"); +        ArchiveState archiveState = +                new ArchiveState( +                        List.of( +                                new ArchiveState.ArchiveActivityInfo( +                                        "title1", +                                        new ComponentName("pkg1", "class1"), +                                        Path.of("/path1"), +                                        Path.of("/monochromePath1")), +                                new ArchiveState.ArchiveActivityInfo( +                                        "title2", +                                        new ComponentName("pkg2", "class2"), +                                        Path.of("/path2"), +                                        Path.of("/monochromePath2"))), +                        "installerTitle");          packageSetting.modifyUserState(UserHandle.SYSTEM.getIdentifier()).setArchiveState(                  archiveState);          settings.mPackages.put(PACKAGE_NAME_1, packageSetting); @@ -1065,7 +1076,10 @@ public class PackageManagerSettingsTests {                  null /*usesStaticLibraries*/,                  null /*usesStaticLibrariesVersions*/,                  null /*mimeGroups*/, -                UUID.randomUUID()); +                UUID.randomUUID(), +                false /*isPersistent*/, +                34 /*targetSdkVersion*/, +                null /*restrictUpdateHash*/);          assertThat(testPkgSetting01.getPrimaryCpuAbi(), is("arm64-v8a"));          assertThat(testPkgSetting01.getPrimaryCpuAbiLegacy(), is("arm64-v8a"));          assertThat(testPkgSetting01.getSecondaryCpuAbi(), is("armeabi")); @@ -1103,7 +1117,10 @@ public class PackageManagerSettingsTests {                  null /*usesStaticLibraries*/,                  null /*usesStaticLibrariesVersions*/,                  null /*mimeGroups*/, -                UUID.randomUUID()); +                UUID.randomUUID(), +                false /*isPersistent*/, +                34 /*targetSdkVersion*/, +                null /*restrictUpdateHash*/);          assertThat(testPkgSetting01.getPrimaryCpuAbi(), is("arm64-v8a"));          assertThat(testPkgSetting01.getPrimaryCpuAbiLegacy(), is("arm64-v8a"));          assertThat(testPkgSetting01.getSecondaryCpuAbi(), is("armeabi")); @@ -1143,7 +1160,10 @@ public class PackageManagerSettingsTests {                      null /*usesStaticLibraries*/,                      null /*usesStaticLibrariesVersions*/,                      null /*mimeGroups*/, -                    UUID.randomUUID()); +                    UUID.randomUUID(), +                    false /*isPersistent*/, +                    34 /*targetSdkVersion*/, +                    null /*restrictUpdateHash*/);              fail("Expected a PackageManagerException");          } catch (PackageManagerException expected) {          } @@ -1179,7 +1199,10 @@ public class PackageManagerSettingsTests {                  null /*usesStaticLibraries*/,                  null /*usesStaticLibrariesVersions*/,                  null /*mimeGroups*/, -                UUID.randomUUID()); +                UUID.randomUUID(), +                false /*isPersistent*/, +                34 /*targetSdkVersion*/, +                null /*restrictUpdateHash*/);          assertThat(testPkgSetting01.getPath(), is(UPDATED_CODE_PATH));          assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME));          assertThat(testPkgSetting01.getFlags(), is(ApplicationInfo.FLAG_SYSTEM)); @@ -1224,7 +1247,10 @@ public class PackageManagerSettingsTests {                  null /*usesStaticLibraries*/,                  null /*usesStaticLibrariesVersions*/,                  null /*mimeGroups*/, -                UUID.randomUUID()); +                UUID.randomUUID(), +                false /*isPersistent*/, +                34 /*targetSdkVersion*/, +                null /*restrictUpdateHash*/);          assertThat(testPkgSetting01.getAppId(), is(0));          assertThat(testPkgSetting01.getPath(), is(INITIAL_CODE_PATH));          assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME)); @@ -1269,7 +1295,10 @@ public class PackageManagerSettingsTests {                  null /*usesStaticLibraries*/,                  null /*usesStaticLibrariesVersions*/,                  null /*mimeGroups*/, -                UUID.randomUUID()); +                UUID.randomUUID(), +                false /*isPersistent*/, +                34 /*targetSdkVersion*/, +                null /*restrictUpdateHash*/);          assertThat(testPkgSetting01.getAppId(), is(10064));          assertThat(testPkgSetting01.getPath(), is(INITIAL_CODE_PATH));          assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME)); @@ -1315,7 +1344,10 @@ public class PackageManagerSettingsTests {                  null /*usesStaticLibraries*/,                  null /*usesStaticLibrariesVersions*/,                  null /*mimeGroups*/, -                UUID.randomUUID()); +                UUID.randomUUID(), +                false /*isPersistent*/, +                34 /*targetSdkVersion*/, +                null /*restrictUpdateHash*/);          assertThat(testPkgSetting01.getAppId(), is(10064));          assertThat(testPkgSetting01.getPath(), is(UPDATED_CODE_PATH));          assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME)); @@ -1358,7 +1390,10 @@ public class PackageManagerSettingsTests {                  null /*usesStaticLibraries*/,                  null /*usesStaticLibrariesVersions*/,                  null /*mimeGroups*/, -                UUID.randomUUID()); +                UUID.randomUUID(), +                false /*isPersistent*/, +                34 /*targetSdkVersion*/, +                null /*restrictUpdateHash*/);          assertThat(testPkgSetting01.getAppId(), is(0));          assertThat(testPkgSetting01.getPath(), is(UPDATED_CODE_PATH));          assertThat(testPkgSetting01.getPackageName(), is(PACKAGE_NAME)); diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java index 58ae7406580e..87a297b0e86f 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java @@ -24,6 +24,7 @@ import static org.junit.Assert.assertFalse;  import static org.junit.Assert.assertThat;  import static org.junit.Assert.assertTrue; +import android.content.ComponentName;  import android.content.pm.PackageManager;  import android.content.pm.SuspendDialogInfo;  import android.content.pm.overlay.OverlayPaths; @@ -192,8 +193,8 @@ public class PackageUserStateTest {          return new SuspendParams(dialogInfo, appExtras, launcherExtras);      } -    private static PersistableBundle createPersistableBundle(String lKey, long lValue, String sKey, -            String sValue, String dKey, double dValue) { +    private static PersistableBundle createPersistableBundle( +            String lKey, long lValue, String sKey, String sValue, String dKey, double dValue) {          final PersistableBundle result = new PersistableBundle(3);          if (lKey != null) {              result.putLong("com.unit_test." + lKey, lValue); @@ -320,6 +321,7 @@ public class PackageUserStateTest {              assertEquals(0L, state.getLastPackageUsageTimeInMills()[i]);          }      } +      private static void assertLastPackageUsageSet(              PackageStateUnserialized state, int reason, long value) throws Exception {          for (int i = state.getLastPackageUsageTimeInMills().length - 1; i >= 0; --i) { @@ -330,6 +332,7 @@ public class PackageUserStateTest {              }          }      } +      @Test      public void testPackageUseReasons() throws Exception {          PackageSetting packageSetting = Mockito.mock(PackageSetting.class); @@ -377,6 +380,7 @@ public class PackageUserStateTest {          assertTrue(testState.setOverlayPaths(new OverlayPaths.Builder().build()));          assertFalse(testState.setOverlayPaths(null));      } +      @Test      public void testSharedLibOverlayPaths() {          final PackageUserStateImpl testState = new PackageUserStateImpl(); @@ -401,8 +405,12 @@ public class PackageUserStateTest {      @Test      public void archiveState() {          PackageUserStateImpl packageUserState = new PackageUserStateImpl(); -        ArchiveState.ArchiveActivityInfo archiveActivityInfo = new ArchiveState.ArchiveActivityInfo( -                "appTitle", Path.of("/path1"), Path.of("/path2")); +        ArchiveState.ArchiveActivityInfo archiveActivityInfo = +                new ArchiveState.ArchiveActivityInfo( +                        "appTitle", +                        new ComponentName("pkg", "class"), +                        Path.of("/path1"), +                        Path.of("/path2"));          ArchiveState archiveState = new ArchiveState(List.of(archiveActivityInfo),                  "installerTitle");          packageUserState.setArchiveState(archiveState); diff --git a/services/tests/displayservicetests/AndroidManifest.xml b/services/tests/displayservicetests/AndroidManifest.xml index 55fde00a05db..e71ea263983a 100644 --- a/services/tests/displayservicetests/AndroidManifest.xml +++ b/services/tests/displayservicetests/AndroidManifest.xml @@ -28,6 +28,7 @@      <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" />      <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />      <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> +    <uses-permission android:name="android.permission.MANAGE_USB" />      <!-- Permissions needed for DisplayTransformManagerTest -->      <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" /> diff --git a/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java b/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java index 4b124caca7d6..8cc3408ad79b 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java @@ -100,6 +100,14 @@ public class DeviceStateToLayoutMapTest {      }      @Test +    public void testPowerThrottlingMapId() { +        Layout configLayout = mDeviceStateToLayoutMap.get(5); + +        assertEquals("concurrent1", configLayout.getAt(0).getPowerThrottlingMapId()); +        assertEquals("concurrent2", configLayout.getAt(1).getPowerThrottlingMapId()); +    } + +    @Test      public void testRearDisplayLayout() {          Layout configLayout = mDeviceStateToLayoutMap.get(2); @@ -133,13 +141,15 @@ public class DeviceStateToLayoutMapTest {                  mDisplayIdProducerMock,  Layout.Display.POSITION_FRONT,                  /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ "brightness1",                  /* refreshRateZoneId= */ "zone1", -                /* refreshRateThermalThrottlingMapId= */ "rr1"); +                /* refreshRateThermalThrottlingMapId= */ "rr1", +                /* powerThrottlingMapId= */ "power1");          testLayout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(678L),                  /* isDefault= */ false, /* isEnabled= */ false, /* displayGroupName= */ "group1",                  mDisplayIdProducerMock, Layout.Display.POSITION_REAR,                  /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ "brightness2",                  /* refreshRateZoneId= */ "zone2", -                /* refreshRateThermalThrottlingMapId= */ "rr2"); +                /* refreshRateThermalThrottlingMapId= */ "rr2", +                /* powerThrottlingMapId= */ "power2");          testLayout.postProcessLocked();          assertEquals(testLayout, configLayout); @@ -200,7 +210,8 @@ public class DeviceStateToLayoutMapTest {                      mDisplayIdProducerMock,  Layout.Display.POSITION_FRONT,                      DisplayAddress.fromPhysicalDisplayId(123L),                      /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null, -                    /* refreshRateThermalThrottlingMapId= */ null)); +                    /* refreshRateThermalThrottlingMapId= */ null, +                    /* powerThrottlingMapId= */ null));      }      @Test @@ -215,7 +226,8 @@ public class DeviceStateToLayoutMapTest {                      mDisplayIdProducerMock,  Layout.Display.POSITION_FRONT,                      DisplayAddress.fromPhysicalDisplayId(987L),                      /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null, -                    /* refreshRateThermalThrottlingMapId= */ null)); +                    /* refreshRateThermalThrottlingMapId= */ null, +                    /* powerThrottlingMapId= */ null));      }      @Test @@ -271,7 +283,8 @@ public class DeviceStateToLayoutMapTest {                  enabled, group, mDisplayIdProducerMock, Layout.Display.POSITION_UNKNOWN,                  leadDisplayAddress, /* brightnessThrottlingMapId= */ null,                  /* refreshRateZoneId= */ null, -                /* refreshRateThermalThrottlingMapId= */ null); +                /* refreshRateThermalThrottlingMapId= */ null, +                /* powerThrottlingMapId= */ null);      }      private void setupDeviceStateToLayoutMap() throws IOException { @@ -327,7 +340,6 @@ public class DeviceStateToLayoutMapTest {                  +        "<brightnessThrottlingMapId>concurrent2</brightnessThrottlingMapId>\n"                  +      "</display>\n"                  +    "</layout>\n" -                  +    "<layout>\n"                  +      "<state>3</state> \n"                  +      "<display enabled=\"true\" defaultDisplay=\"true\" " @@ -338,7 +350,6 @@ public class DeviceStateToLayoutMapTest {                  +        "<address>678</address>\n"                  +      "</display>\n"                  +    "</layout>\n" -                  +    "<layout>\n"                  +      "<state>4</state> \n"                  +      "<display enabled=\"true\" defaultDisplay=\"true\" >\n" @@ -352,6 +363,20 @@ public class DeviceStateToLayoutMapTest {                  +      "</display>\n"                  +    "</layout>\n"                  +    "<layout>\n" +                +      "<state>5</state> \n" +                +      "<display enabled=\"true\" defaultDisplay=\"true\">\n" +                +        "<address>345</address>\n" +                +        "<position>front</position>\n" +                +        "<powerThrottlingMapId>concurrent1</powerThrottlingMapId>\n" +                +      "</display>\n" +                +      "<display enabled=\"true\">\n" +                +        "<address>678</address>\n" +                +        "<position>rear</position>\n" +                +        "<powerThrottlingMapId>concurrent2</powerThrottlingMapId>\n" +                +      "</display>\n" +                +    "</layout>\n" + +                +    "<layout>\n"                  +      "<state>99</state> \n"                  +      "<display enabled=\"true\" defaultDisplay=\"true\" "                  +                                          "refreshRateZoneId=\"zone1\">\n" @@ -361,6 +386,7 @@ public class DeviceStateToLayoutMapTest {                  +         "<refreshRateThermalThrottlingMapId>"                  +           "rr1"                  +         "</refreshRateThermalThrottlingMapId>" +                +         "<powerThrottlingMapId>power1</powerThrottlingMapId>\n"                  +       "</display>\n"                  +       "<display enabled=\"false\" displayGroup=\"group1\" "                  +                                           "refreshRateZoneId=\"zone2\">\n" @@ -370,6 +396,7 @@ public class DeviceStateToLayoutMapTest {                  +         "<refreshRateThermalThrottlingMapId>"                  +           "rr2"                  +         "</refreshRateThermalThrottlingMapId>" +                +         "<powerThrottlingMapId>power2</powerThrottlingMapId>\n"                  +       "</display>\n"                  +     "</layout>\n"                  +   "</layouts>\n"; diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java index d021f1d5aaea..16d72e40fbb5 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -117,7 +117,6 @@ import com.android.server.display.feature.DisplayManagerFlags;  import com.android.server.display.notifications.DisplayNotificationManager;  import com.android.server.input.InputManagerInternal;  import com.android.server.lights.LightsManager; -import com.android.server.pm.UserManagerInternal;  import com.android.server.sensors.SensorManagerInternal;  import com.android.server.wm.WindowManagerInternal; @@ -312,7 +311,6 @@ public class DisplayManagerServiceTest {      @Mock SensorManager mSensorManager;      @Mock DisplayDeviceConfig mMockDisplayDeviceConfig;      @Mock PackageManagerInternal mMockPackageManagerInternal; -    @Mock UserManagerInternal mMockUserManagerInternal;      @Captor ArgumentCaptor<ContentRecordingSession> mContentRecordingSessionCaptor; @@ -336,8 +334,6 @@ public class DisplayManagerServiceTest {                  VirtualDeviceManagerInternal.class, mMockVirtualDeviceManagerInternal);          LocalServices.removeServiceForTest(PackageManagerInternal.class);          LocalServices.addService(PackageManagerInternal.class, mMockPackageManagerInternal); -        LocalServices.removeServiceForTest(UserManagerInternal.class); -        LocalServices.addService(UserManagerInternal.class, mMockUserManagerInternal);          // TODO: b/287945043          mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));          mResources = Mockito.spy(mContext.getResources()); diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java index 9ac00624b343..32e28715cc74 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java @@ -33,6 +33,7 @@ import static org.mockito.ArgumentMatchers.anyInt;  import static org.mockito.Mockito.mock;  import static org.mockito.Mockito.never;  import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times;  import static org.mockito.Mockito.when;  import android.content.Context; @@ -699,7 +700,7 @@ public class LocalDisplayAdapterTest {          // Turn off.          Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_OFF, 0, -                0); +                0, null);          waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);          assertThat(mListener.changedDisplays.size()).isEqualTo(1);          mListener.changedDisplays.clear(); @@ -1003,7 +1004,7 @@ public class LocalDisplayAdapterTest {          // Turn on / initialize          assumeTrue(displayDevice.getDisplayDeviceConfig().hasSdrToHdrRatioSpline());          Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0, -                0); +                0, null);          changeStateRunnable.run();          waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);          mListener.changedDisplays.clear(); @@ -1012,7 +1013,7 @@ public class LocalDisplayAdapterTest {          // HDR time!          Runnable goHdrRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 1f, -                0); +                0, null);          waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);          // Display state didn't change, no listeners should have happened          assertThat(mListener.changedDisplays.size()).isEqualTo(0); @@ -1043,7 +1044,7 @@ public class LocalDisplayAdapterTest {          // Turn on / initialize          Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0, -                0); +                0, null);          changeStateRunnable.run();          waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);          mListener.changedDisplays.clear(); @@ -1070,7 +1071,7 @@ public class LocalDisplayAdapterTest {          // Turn on / initialize          Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0, -                0); +                0, null);          changeStateRunnable.run();          waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);          mListener.changedDisplays.clear(); @@ -1095,7 +1096,7 @@ public class LocalDisplayAdapterTest {          // Turn on / initialize          Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0, -                0); +                0, null);          changeStateRunnable.run();          waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);          mListener.changedDisplays.clear(); @@ -1118,7 +1119,7 @@ public class LocalDisplayAdapterTest {          // Turn on / initialize          Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0, -                0); +                0, null);          changeStateRunnable.run();          waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);          mListener.changedDisplays.clear(); @@ -1145,9 +1146,9 @@ public class LocalDisplayAdapterTest {              Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(                      supportedState, 0, 0, mDisplayOffloadSession);              changeStateRunnable.run(); - -            verify(mDisplayOffloader).startOffload();          } + +        verify(mDisplayOffloader, times(mDisplayOffloadSupportedStates.size())).startOffload();      }      @Test 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 065dd1f1f743..8b13018fc14b 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java @@ -720,13 +720,15 @@ public class LogicalDisplayMapperTest {                  mIdProducer, POSITION_UNKNOWN,                  /* leadDisplayAddress= */ null,                  /* brightnessThrottlingMapId= */ "concurrent", -                /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null); +                /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null, +                /* powerThrottlingMapId= */ "concurrent");          layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address,                  /* isDefault= */ false, /* isEnabled= */ true, /* displayGroup= */ null,                  mIdProducer, POSITION_UNKNOWN,                  /* leadDisplayAddress= */ null,                  /* brightnessThrottlingMapId= */ "concurrent", -                /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null); +                /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null, +                /* powerThrottlingMapId= */ "concurrent");          when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout);          layout = new Layout(); @@ -927,7 +929,7 @@ public class LogicalDisplayMapperTest {                  /* isDefault= */ false, /* isEnabled= */ true, /* displayGroupName= */ null,                  mIdProducer, POSITION_REAR, /* leadDisplayAddress= */ null,                  /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null, -                /* refreshRateThermalThrottlingMapId= */null); +                /* refreshRateThermalThrottlingMapId= */null, /* powerThrottlingMapId= */null);          when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout);          when(mDeviceStateToLayoutMapSpy.size()).thenReturn(1); @@ -986,7 +988,7 @@ public class LogicalDisplayMapperTest {          layout.createDisplayLocked(address, /* isDefault= */ false, enabled, group, mIdProducer,                  Layout.Display.POSITION_UNKNOWN, /* leadDisplayAddress= */ null,                  /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null, -                /* refreshRateThermalThrottlingMapId= */ null); +                /* refreshRateThermalThrottlingMapId= */ null, /* powerThrottlingMapId= */ null);      }      private void advanceTime(long timeMs) { diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java index 22d26224c83f..c0e0df98213f 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java @@ -35,6 +35,7 @@ import androidx.test.filters.SmallTest;  import com.android.server.display.DisplayBrightnessState;  import com.android.server.display.brightness.BrightnessReason;  import com.android.server.display.feature.DeviceConfigParameterProvider; +import com.android.server.display.feature.DisplayManagerFlags;  import com.android.server.testutils.TestHandler;  import org.junit.Before; @@ -63,12 +64,13 @@ public class BrightnessClamperControllerTest {      @Mock      private BrightnessClamper<BrightnessClamperController.DisplayDeviceData> mMockClamper;      @Mock +    private DisplayManagerFlags mFlags; +    @Mock      private BrightnessModifier mMockModifier;      @Mock      private DisplayManagerInternal.DisplayPowerRequest mMockRequest;      @Mock      private DeviceConfig.Properties mMockProperties; -      private BrightnessClamperController mClamperController;      private TestInjector mTestInjector; @@ -219,7 +221,7 @@ public class BrightnessClamperControllerTest {      private BrightnessClamperController createBrightnessClamperController() {          return new BrightnessClamperController(mTestInjector, mTestHandler, mMockExternalListener, -                mMockDisplayDeviceData, mMockContext); +                mMockDisplayDeviceData, mMockContext, mFlags);      }      private class TestInjector extends BrightnessClamperController.Injector { @@ -247,7 +249,8 @@ public class BrightnessClamperControllerTest {          List<BrightnessClamper<? super BrightnessClamperController.DisplayDeviceData>> getClampers(                  Handler handler,                  BrightnessClamperController.ClamperChangeListener clamperChangeListener, -                BrightnessClamperController.DisplayDeviceData data) { +                BrightnessClamperController.DisplayDeviceData data, +                DisplayManagerFlags flags) {              mCapturedChangeListener = clamperChangeListener;              return mClampers;          } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerClamperTest.java new file mode 100644 index 000000000000..b3f33ad858fe --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerClamperTest.java @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display.brightness.clamper; + +import static com.android.server.display.brightness.clamper.BrightnessPowerClamper.PowerChangeListener; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import android.os.PowerManager; +import android.os.RemoteException; +import android.os.Temperature; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.server.display.DisplayDeviceConfig; +import com.android.server.display.DisplayDeviceConfig.PowerThrottlingConfigData; +import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData; +import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData.ThrottlingLevel; +import com.android.server.display.feature.DeviceConfigParameterProvider; +import com.android.server.testutils.FakeDeviceConfigInterface; +import com.android.server.testutils.TestHandler; + +import junitparams.JUnitParamsRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.List; + +@RunWith(JUnitParamsRunner.class) +public class BrightnessPowerClamperTest { +    private static final String TAG = "BrightnessPowerClamperTest"; +    private static final float FLOAT_TOLERANCE = 0.001f; + +    private static final String DISPLAY_ID = "displayId"; +    @Mock +    private BrightnessClamperController.ClamperChangeListener mMockClamperChangeListener; +    private TestPmicMonitor mPmicMonitor; +    private final FakeDeviceConfigInterface mFakeDeviceConfigInterface = +            new FakeDeviceConfigInterface(); +    private final TestHandler mTestHandler = new TestHandler(null); +    private BrightnessPowerClamper mClamper; +    @Before +    public void setUp() { +        MockitoAnnotations.initMocks(this); +        mClamper = new BrightnessPowerClamper(new TestInjector(), mTestHandler, +                mMockClamperChangeListener, new TestPowerData()); +        mTestHandler.flush(); +    } + +    @Test +    public void testTypeIsPower() { +        assertEquals(BrightnessClamper.Type.POWER, mClamper.getType()); +    } + +    @Test +    public void testNoThrottlingData() { +        assertFalse(mClamper.isActive()); +        assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); +    } + +    @Test +    public void testPowerThrottlingNoOngoingAnimation() throws RemoteException { +        mPmicMonitor.setThermalStatus(Temperature.THROTTLING_SEVERE); +        mTestHandler.flush(); +        assertFalse(mClamper.isActive()); +        assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); + +        // update a new device config for power-throttling. +        mClamper.onDisplayChanged(new TestPowerData( +                List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE, 100f)))); + +        mPmicMonitor.setAvgPowerConsumed(200f); +        float expectedBrightness = 0.5f; +        expectedBrightness = expectedBrightness * PowerManager.BRIGHTNESS_MAX; + +        mTestHandler.flush(); +        // Assume current brightness as max, as there is no throttling. +        assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); +        mPmicMonitor.setThermalStatus(Temperature.THROTTLING_CRITICAL); +        // update a new device config for power-throttling. +        mClamper.onDisplayChanged(new TestPowerData( +                List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 50f)))); + +        mPmicMonitor.setAvgPowerConsumed(100f); +        expectedBrightness = 0.5f * PowerManager.BRIGHTNESS_MAX; +        mTestHandler.flush(); +        assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); +    } + +    @Test +    public void testPowerThrottlingWithOngoingAnimation() throws RemoteException { +        mPmicMonitor.setThermalStatus(Temperature.THROTTLING_SEVERE); +        mTestHandler.flush(); +        assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); + +        // update a new device config for power-throttling. +        mClamper.onDisplayChanged(new TestPowerData( +                List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE, 100f)))); + +        mPmicMonitor.setAvgPowerConsumed(200f); +        float expectedBrightness = 0.5f; +        expectedBrightness = expectedBrightness * PowerManager.BRIGHTNESS_MAX; + +        mTestHandler.flush(); +        // Assume current brightness as max, as there is no throttling. +        assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); +        mPmicMonitor.setThermalStatus(Temperature.THROTTLING_CRITICAL); +        // update a new device config for power-throttling. +        mClamper.onDisplayChanged(new TestPowerData( +                List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 50f)))); + +        mPmicMonitor.setAvgPowerConsumed(100f); +        expectedBrightness = 0.5f * PowerManager.BRIGHTNESS_MAX; +        mTestHandler.flush(); +        assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); +    } + +    @Test +    public void testPowerThrottlingRemoveBrightnessCap() throws RemoteException { +        mPmicMonitor.setThermalStatus(Temperature.THROTTLING_LIGHT); +        mTestHandler.flush(); +        assertFalse(mClamper.isActive()); +        assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); + +        // update a new device config for power-throttling. +        mClamper.onDisplayChanged(new TestPowerData( +                List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_LIGHT, 100f)))); + +        mPmicMonitor.setAvgPowerConsumed(200f); +        float expectedBrightness = 0.5f; +        expectedBrightness = expectedBrightness * PowerManager.BRIGHTNESS_MAX; + +        mTestHandler.flush(); + +        assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); +        mPmicMonitor.setThermalStatus(Temperature.THROTTLING_NONE); + +        mPmicMonitor.setAvgPowerConsumed(100f); +        // No cap applied for Temperature.THROTTLING_NONE +        expectedBrightness = PowerManager.BRIGHTNESS_MAX; +        mTestHandler.flush(); + +        // clamper should not be active anymore. +        assertFalse(mClamper.isActive()); +        // Assume current brightness as max, as there is no throttling. +        assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); +    } + + +    private static class TestPmicMonitor extends PmicMonitor { +        private Temperature mCurrentTemperature; +        private final PowerChangeListener mListener; +        TestPmicMonitor(PowerChangeListener listener, int pollingTime) { +            super(listener, pollingTime); +            mListener = listener; +        } +        public void setAvgPowerConsumed(float power) { +            int status = mCurrentTemperature.getStatus(); +            mListener.onChanged(power, status); +        } +        public void setThermalStatus(@Temperature.ThrottlingStatus int status) { +            mCurrentTemperature = new Temperature(100, Temperature.TYPE_SKIN, "test_temp", status); +        } +    } + +    private class TestInjector extends BrightnessPowerClamper.Injector { +        @Override +        TestPmicMonitor getPmicMonitor(PowerChangeListener listener, +                int pollingTime) { +            mPmicMonitor = new TestPmicMonitor(listener, pollingTime); +            return mPmicMonitor; +        } + +        @Override +        DeviceConfigParameterProvider getDeviceConfigParameterProvider() { +            return new DeviceConfigParameterProvider(mFakeDeviceConfigInterface); +        } +    } + +    private static class TestPowerData implements BrightnessPowerClamper.PowerData { + +        private final String mUniqueDisplayId; +        private final String mDataId; +        private final PowerThrottlingData mData; +        private final PowerThrottlingConfigData mConfigData; + +        private TestPowerData() { +            this(DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID, null); +        } + +        private TestPowerData(List<ThrottlingLevel> data) { +            this(DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID, data); +        } + +        private TestPowerData(String uniqueDisplayId, String dataId, List<ThrottlingLevel> data) { +            mUniqueDisplayId = uniqueDisplayId; +            mDataId = dataId; +            mData = PowerThrottlingData.create(data); +            mConfigData = new PowerThrottlingConfigData(0.1f, 10); +        } + +        @NonNull +        @Override +        public String getUniqueDisplayId() { +            return mUniqueDisplayId; +        } + +        @NonNull +        @Override +        public String getPowerThrottlingDataId() { +            return mDataId; +        } + +        @Nullable +        @Override +        public PowerThrottlingData getPowerThrottlingData() { +            return mData; +        } + +        @Nullable +        @Override +        public PowerThrottlingConfigData getPowerThrottlingConfigData() { +            return mConfigData; +        } +    } +} diff --git a/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java index c7c09b5deb35..ec27f9d220dc 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java @@ -44,12 +44,12 @@ import android.provider.Settings.System;  import android.test.mock.MockContentResolver;  import android.view.Display; -import androidx.test.InstrumentationRegistry; +import androidx.test.platform.app.InstrumentationRegistry;  import androidx.test.runner.AndroidJUnit4;  import com.android.internal.R;  import com.android.internal.util.test.FakeSettingsProvider; -import com.android.server.LocalServices; +import com.android.internal.util.test.LocalServiceKeeperRule;  import com.android.server.SystemService;  import com.android.server.twilight.TwilightListener;  import com.android.server.twilight.TwilightManager; @@ -57,6 +57,7 @@ import com.android.server.twilight.TwilightState;  import org.junit.After;  import org.junit.Before; +import org.junit.Rule;  import org.junit.Test;  import org.junit.runner.RunWith;  import org.mockito.Mockito; @@ -90,9 +91,13 @@ public class ColorDisplayServiceTest {          ColorDisplayManager.COLOR_MODE_BOOSTED,      }; +    @Rule +    public LocalServiceKeeperRule mLocalServiceKeeperRule = new LocalServiceKeeperRule(); +      @Before      public void setUp() { -        mContext = Mockito.spy(new ContextWrapper(InstrumentationRegistry.getTargetContext())); +        mContext = Mockito.spy(new ContextWrapper( +                InstrumentationRegistry.getInstrumentation().getTargetContext()));          doReturn(mContext).when(mContext).getApplicationContext();          final Resources res = Mockito.spy(mContext.getResources()); @@ -112,43 +117,36 @@ public class ColorDisplayServiceTest {          doReturn(am).when(mContext).getSystemService(Context.ALARM_SERVICE);          mTwilightManager = new MockTwilightManager(); -        LocalServices.addService(TwilightManager.class, mTwilightManager); +        mLocalServiceKeeperRule.overrideLocalService(TwilightManager.class, mTwilightManager);          mDisplayTransformManager = Mockito.mock(DisplayTransformManager.class);          doReturn(true).when(mDisplayTransformManager).needsLinearColorMatrix(); -        LocalServices.addService(DisplayTransformManager.class, mDisplayTransformManager); +        mLocalServiceKeeperRule.overrideLocalService( +                DisplayTransformManager.class, mDisplayTransformManager);          mDisplayManagerInternal = Mockito.mock(DisplayManagerInternal.class); -        LocalServices.removeServiceForTest(DisplayManagerInternal.class); -        LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternal); +        mLocalServiceKeeperRule.overrideLocalService( +                DisplayManagerInternal.class, mDisplayManagerInternal);          mCds = new ColorDisplayService(mContext);          mBinderService = mCds.new BinderService(); -        LocalServices.addService(ColorDisplayService.ColorDisplayServiceInternal.class, +        mLocalServiceKeeperRule.overrideLocalService( +                ColorDisplayService.ColorDisplayServiceInternal.class,                  mCds.new ColorDisplayServiceInternal());      }      @After      public void tearDown() { -        /* -         * Wait for internal {@link Handler} to finish processing pending messages, so that test -         * code can safelyremove {@link DisplayTransformManager} mock from {@link LocalServices}. -         */ -        mCds.mHandler.runWithScissors(() -> { /* nop */ }, /* timeout */ 1000); +        // synchronously cancel all animations +        mCds.mHandler.runWithScissors(() -> mCds.cancelAllAnimators(), /* timeout */ 1000);          mCds = null; -        LocalServices.removeServiceForTest(TwilightManager.class);          mTwilightManager = null; -        LocalServices.removeServiceForTest(DisplayTransformManager.class); -          mUserId = UserHandle.USER_NULL;          mContext = null;          FakeSettingsProvider.clearSettingsProvider(); - -        LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class); -        LocalServices.removeServiceForTest(DisplayManagerInternal.class);      }      @Test @@ -1249,10 +1247,10 @@ public class ColorDisplayServiceTest {      private void startService() {          Secure.putIntForUser(mContext.getContentResolver(), Secure.USER_SETUP_COMPLETE, 1, mUserId); -        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { -            mCds.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); -            mCds.onUserChanged(mUserId); -        }); +        InstrumentationRegistry.getInstrumentation().runOnMainSync( +                () -> mCds.onBootPhase(SystemService.PHASE_BOOT_COMPLETED)); +        // onUserChanged cancels running animations, and should be called in handler thread +        mCds.mHandler.runWithScissors(() -> mCds.onUserChanged(mUserId), 1000);      }      /** diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java index c4f72b307eb7..6a95d5c57024 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java @@ -102,6 +102,9 @@ import com.android.server.sensors.SensorManagerInternal.ProximityActiveListener;  import com.android.server.statusbar.StatusBarManagerInternal;  import com.android.server.testutils.FakeDeviceConfigInterface; +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; +  import org.junit.Before;  import org.junit.Rule;  import org.junit.Test; @@ -121,26 +124,28 @@ import java.util.concurrent.Executor;  import java.util.concurrent.TimeUnit;  import java.util.stream.Collectors; -import junitparams.JUnitParamsRunner; -import junitparams.Parameters; -  @SmallTest  @RunWith(JUnitParamsRunner.class)  public class DisplayModeDirectorTest {      public static Collection<Object[]> getAppRequestedSizeTestCases() {          var appRequestedSizeTestCases = Arrays.asList(new Object[][] { -                {DEFAULT_MODE_75.getModeId(), Float.POSITIVE_INFINITY, -                        DEFAULT_MODE_75.getRefreshRate(), Map.of()}, -                {APP_MODE_HIGH_90.getModeId(), Float.POSITIVE_INFINITY, -                        APP_MODE_HIGH_90.getRefreshRate(), -                        Map.of( +                {/*expectedBaseModeId*/ DEFAULT_MODE_75.getModeId(), +                        /*expectedPhysicalRefreshRate*/ Float.POSITIVE_INFINITY, +                        /*expectedAppRequestedRefreshRate*/ DEFAULT_MODE_75.getRefreshRate(), +                        /*votesWithPriorities*/ Map.of()}, +                {/*expectedBaseModeId*/ APP_MODE_HIGH_90.getModeId(), +                        /*expectedPhysicalRefreshRate*/ Float.POSITIVE_INFINITY, +                        /*expectedAppRequestedRefreshRate*/ APP_MODE_HIGH_90.getRefreshRate(), +                        /*votesWithPriorities*/ Map.of(                                  Vote.PRIORITY_APP_REQUEST_SIZE,                                  Vote.forSize(APP_MODE_HIGH_90.getPhysicalWidth(),                                          APP_MODE_HIGH_90.getPhysicalHeight()),                                  Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,                                  Vote.forBaseModeRefreshRate(APP_MODE_HIGH_90.getRefreshRate()))}, -                {LIMIT_MODE_70.getModeId(), Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, -                        Map.of( +                {/*expectedBaseModeId*/ LIMIT_MODE_70.getModeId(), +                        /*expectedPhysicalRefreshRate*/ Float.POSITIVE_INFINITY, +                        /*expectedAppRequestedRefreshRate*/ Float.POSITIVE_INFINITY, +                        /*votesWithPriorities*/ Map.of(                                  Vote.PRIORITY_APP_REQUEST_SIZE,                                  Vote.forSize(APP_MODE_HIGH_90.getPhysicalWidth(),                                          APP_MODE_HIGH_90.getPhysicalHeight()), @@ -149,9 +154,10 @@ public class DisplayModeDirectorTest {                                  Vote.PRIORITY_LOW_POWER_MODE,                                  Vote.forSize(LIMIT_MODE_70.getPhysicalWidth(),                                          LIMIT_MODE_70.getPhysicalHeight()))}, -                {LIMIT_MODE_70.getModeId(), LIMIT_MODE_70.getRefreshRate(), -                        LIMIT_MODE_70.getRefreshRate(), -                        Map.of( +                {/*expectedBaseModeId*/ LIMIT_MODE_70.getModeId(), +                        /*expectedPhysicalRefreshRate*/ LIMIT_MODE_70.getRefreshRate(), +                        /*expectedAppRequestedRefreshRate*/ LIMIT_MODE_70.getRefreshRate(), +                        /*votesWithPriorities*/ Map.of(                                  Vote.PRIORITY_APP_REQUEST_SIZE,                                  Vote.forSize(APP_MODE_65.getPhysicalWidth(),                                          APP_MODE_65.getPhysicalHeight()), @@ -160,9 +166,10 @@ public class DisplayModeDirectorTest {                                  Vote.PRIORITY_LOW_POWER_MODE,                                  Vote.forSize(LIMIT_MODE_70.getPhysicalWidth(),                                          LIMIT_MODE_70.getPhysicalHeight()))}, -                {LIMIT_MODE_70.getModeId(), LIMIT_MODE_70.getRefreshRate(), -                        LIMIT_MODE_70.getRefreshRate(), -                        Map.of( +                {/*expectedBaseModeId*/ LIMIT_MODE_70.getModeId(), +                        /*expectedPhysicalRefreshRate*/ LIMIT_MODE_70.getRefreshRate(), +                        /*expectedAppRequestedRefreshRate*/ LIMIT_MODE_70.getRefreshRate(), +                        /*votesWithPriorities*/ Map.of(                                  Vote.PRIORITY_APP_REQUEST_SIZE,                                  Vote.forSize(APP_MODE_65.getPhysicalWidth(),                                          APP_MODE_65.getPhysicalHeight()), @@ -173,10 +180,12 @@ public class DisplayModeDirectorTest {                                      0, 0,                                      LIMIT_MODE_70.getPhysicalWidth(),                                      LIMIT_MODE_70.getPhysicalHeight(), -                                    0, Float.POSITIVE_INFINITY)), false}, -                {APP_MODE_65.getModeId(), APP_MODE_65.getRefreshRate(), -                        APP_MODE_65.getRefreshRate(), -                        Map.of( +                                    0, Float.POSITIVE_INFINITY)), +                        /*displayResolutionRangeVotingEnabled*/ false}, +                {/*expectedBaseModeId*/ APP_MODE_65.getModeId(), +                        /*expectedPhysicalRefreshRate*/ APP_MODE_65.getRefreshRate(), +                        /*expectedAppRequestedRefreshRate*/ APP_MODE_65.getRefreshRate(), +                        /*votesWithPriorities*/ Map.of(                                  Vote.PRIORITY_APP_REQUEST_SIZE,                                  Vote.forSize(APP_MODE_65.getPhysicalWidth(),                                          APP_MODE_65.getPhysicalHeight()), @@ -187,7 +196,40 @@ public class DisplayModeDirectorTest {                                      0, 0,                                      LIMIT_MODE_70.getPhysicalWidth(),                                      LIMIT_MODE_70.getPhysicalHeight(), -                                    0, Float.POSITIVE_INFINITY)), true}}); +                                    0, Float.POSITIVE_INFINITY)), +                        /*displayResolutionRangeVotingEnabled*/ true}, +                {/*expectedBaseModeId*/ DEFAULT_MODE_75.getModeId(), +                        /*expectedPhysicalRefreshRate*/ APP_MODE_65.getRefreshRate(), +                        /*expectedAppRequestedRefreshRate*/ APP_MODE_HIGH_90.getRefreshRate(), +                        /*votesWithPriorities*/ Map.of( +                                Vote.PRIORITY_APP_REQUEST_SIZE, +                                Vote.forSize(APP_MODE_HIGH_90.getPhysicalWidth(), +                                        APP_MODE_HIGH_90.getPhysicalHeight()), +                                Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, +                                Vote.forBaseModeRefreshRate(APP_MODE_HIGH_90.getRefreshRate()), +                                Vote.PRIORITY_LOW_POWER_MODE, +                                Vote.forSizeAndPhysicalRefreshRatesRange( +                                    0, 0, +                                    LIMIT_MODE_70.getPhysicalWidth(), +                                    LIMIT_MODE_70.getPhysicalHeight(), +                                    0, APP_MODE_65.getRefreshRate())), +                        /*displayResolutionRangeVotingEnabled*/ false}, +                {/*expectedBaseModeId*/ DEFAULT_MODE_60.getModeId(), // Resolution == APP_MODE_65 +                        /*expectedPhysicalRefreshRate*/ APP_MODE_65.getRefreshRate(), +                        /*expectedAppRequestedRefreshRate*/ APP_MODE_65.getRefreshRate(), +                        /*votesWithPriorities*/ Map.of( +                                Vote.PRIORITY_APP_REQUEST_SIZE, +                                Vote.forSize(APP_MODE_HIGH_90.getPhysicalWidth(), +                                        APP_MODE_HIGH_90.getPhysicalHeight()), +                                Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, +                                Vote.forBaseModeRefreshRate(APP_MODE_HIGH_90.getRefreshRate()), +                                Vote.PRIORITY_LOW_POWER_MODE, +                                Vote.forSizeAndPhysicalRefreshRatesRange( +                                    0, 0, +                                    LIMIT_MODE_70.getPhysicalWidth(), +                                    LIMIT_MODE_70.getPhysicalHeight(), +                                    0, APP_MODE_65.getRefreshRate())), +                        /*displayResolutionRangeVotingEnabled*/ true}});          final var res = new ArrayList<Object[]>(appRequestedSizeTestCases.size() * 2); @@ -218,6 +260,8 @@ public class DisplayModeDirectorTest {      private static final boolean DEBUG = false;      private static final float FLOAT_TOLERANCE = 0.01f; +    private static final Display.Mode DEFAULT_MODE_60 = new Display.Mode( +            /*modeId=*/60, /*width=*/1900, /*height=*/1900, 60);      private static final Display.Mode APP_MODE_65 = new Display.Mode(              /*modeId=*/65, /*width=*/1900, /*height=*/1900, 65);      private static final Display.Mode LIMIT_MODE_70 = new Display.Mode( @@ -227,8 +271,7 @@ public class DisplayModeDirectorTest {      private static final Display.Mode APP_MODE_HIGH_90 = new Display.Mode(              /*modeId=*/90, /*width=*/3000, /*height=*/3000, 90);      private static final Display.Mode[] TEST_MODES = new Display.Mode[] { -        new Display.Mode( -            /*modeId=*/60, /*width=*/1900, /*height=*/1900, 60), +        DEFAULT_MODE_60,          APP_MODE_65,          LIMIT_MODE_70,          DEFAULT_MODE_75, diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java index 5b519633081a..21e3b34ed61a 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java @@ -62,8 +62,6 @@ import static com.android.server.alarm.AlarmManagerService.ACTIVE_INDEX;  import static com.android.server.alarm.AlarmManagerService.AlarmHandler.APP_STANDBY_BUCKET_CHANGED;  import static com.android.server.alarm.AlarmManagerService.AlarmHandler.CHARGING_STATUS_CHANGED;  import static com.android.server.alarm.AlarmManagerService.AlarmHandler.CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE; -import static com.android.server.alarm.AlarmManagerService.AlarmHandler.EXACT_ALARM_DENY_LIST_PACKAGES_ADDED; -import static com.android.server.alarm.AlarmManagerService.AlarmHandler.EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED;  import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REFRESH_EXACT_ALARM_CANDIDATES;  import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_EXACT_ALARMS;  import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED; @@ -75,7 +73,6 @@ import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_W  import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_QUOTA;  import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION;  import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_WINDOW; -import static com.android.server.alarm.AlarmManagerService.Constants.KEY_EXACT_ALARM_DENY_LIST;  import static com.android.server.alarm.AlarmManagerService.Constants.KEY_LISTENER_TIMEOUT;  import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MAX_DEVICE_IDLE_FUZZ;  import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MAX_INTERVAL; @@ -85,7 +82,6 @@ import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_INT  import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_WINDOW;  import static com.android.server.alarm.AlarmManagerService.Constants.KEY_PRIORITY_ALARM_DELAY;  import static com.android.server.alarm.AlarmManagerService.Constants.KEY_TEMPORARY_QUOTA_BUMP; -import static com.android.server.alarm.AlarmManagerService.Constants.MAX_EXACT_ALARM_DENY_LIST_SIZE;  import static com.android.server.alarm.AlarmManagerService.FREQUENT_INDEX;  import static com.android.server.alarm.AlarmManagerService.INDEFINITE_DELAY;  import static com.android.server.alarm.AlarmManagerService.IS_WAKEUP_MASK; @@ -799,47 +795,6 @@ public final class AlarmManagerServiceTest {      }      @Test -    public void updatingExactAlarmDenyList() { -        ArraySet<String> denyListed = new ArraySet<>(new String[]{ -                "com.example.package1", -                "com.example.package2", -                "com.example.package3", -        }); -        setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, -                "com.example.package1,com.example.package2,com.example.package3"); -        assertEquals(denyListed, mService.mConstants.EXACT_ALARM_DENY_LIST); - - -        denyListed = new ArraySet<>(new String[]{ -                "com.example.package1", -                "com.example.package4", -        }); -        setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, -                "com.example.package1,com.example.package4"); -        assertEquals(denyListed, mService.mConstants.EXACT_ALARM_DENY_LIST); - -        setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, ""); -        assertEquals(0, mService.mConstants.EXACT_ALARM_DENY_LIST.size()); -    } - -    @Test -    public void exactAlarmDenyListMaxSize() { -        final ArraySet<String> expectedSet = new ArraySet<>(); -        final StringBuilder sb = new StringBuilder("package1"); -        expectedSet.add("package1"); -        for (int i = 2; i <= 2 * MAX_EXACT_ALARM_DENY_LIST_SIZE; i++) { -            sb.append(",package"); -            sb.append(i); -            if (i <= MAX_EXACT_ALARM_DENY_LIST_SIZE) { -                expectedSet.add("package" + i); -            } -        } -        assertEquals(MAX_EXACT_ALARM_DENY_LIST_SIZE, expectedSet.size()); -        setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, sb.toString()); -        assertEquals(expectedSet, mService.mConstants.EXACT_ALARM_DENY_LIST); -    } - -    @Test      public void positiveWhileIdleQuotas() {          setDeviceConfigInt(KEY_ALLOW_WHILE_IDLE_QUOTA, -3);          assertEquals(1, mService.mConstants.ALLOW_WHILE_IDLE_QUOTA); @@ -2212,50 +2167,30 @@ public final class AlarmManagerServiceTest {      }      @Test -    public void hasScheduleExactAlarmBinderCallNotDenyListedPreT() throws RemoteException { +    public void hasScheduleExactAlarmBinderCallPreT() throws RemoteException {          mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); -        mockScheduleExactAlarmStatePreT(true, false, MODE_DEFAULT); +        mockScheduleExactAlarmStatePreT(true, MODE_DEFAULT);          assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); -        mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED); +        mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED);          assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); -        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); +        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);          assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); -        mockScheduleExactAlarmStatePreT(true, false, MODE_IGNORED); +        mockScheduleExactAlarmStatePreT(true, MODE_IGNORED);          assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));      }      @Test -    public void hasScheduleExactAlarmBinderCallDenyListedPreT() throws RemoteException { -        mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); - -        mockScheduleExactAlarmStatePreT(true, true, MODE_ERRORED); -        assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); - -        mockScheduleExactAlarmStatePreT(true, true, MODE_DEFAULT); -        assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); - -        mockScheduleExactAlarmStatePreT(true, true, MODE_IGNORED); -        assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); - -        mockScheduleExactAlarmStatePreT(true, true, MODE_ALLOWED); -        assertTrue(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); -    } - -    @Test      public void hasScheduleExactAlarmBinderCallNotDeclaredPreT() throws RemoteException {          mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); -        mockScheduleExactAlarmStatePreT(false, false, MODE_DEFAULT); -        assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); - -        mockScheduleExactAlarmStatePreT(false, false, MODE_ALLOWED); +        mockScheduleExactAlarmStatePreT(false, MODE_DEFAULT);          assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER)); -        mockScheduleExactAlarmStatePreT(false, true, MODE_ALLOWED); +        mockScheduleExactAlarmStatePreT(false, MODE_ALLOWED);          assertFalse(mBinder.hasScheduleExactAlarm(TEST_CALLING_PACKAGE, TEST_CALLING_USER));      } @@ -2281,41 +2216,33 @@ public final class AlarmManagerServiceTest {          mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, true);          // No permission, no exemption. -        mockScheduleExactAlarmStatePreT(true, true, MODE_DEFAULT); -        assertFalse(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE)); - -        // No permission, no exemption. -        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); +        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);          assertFalse(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));          // Policy permission only, no exemption. -        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); +        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);          mockUseExactAlarmState(true);          assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));          mockUseExactAlarmState(false);          // User permission only, no exemption. -        mockScheduleExactAlarmStatePreT(true, false, MODE_DEFAULT); -        assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE)); - -        // User permission only, no exemption. -        mockScheduleExactAlarmStatePreT(true, true, MODE_ALLOWED); +        mockScheduleExactAlarmStatePreT(true, MODE_DEFAULT);          assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));          // No permission, exemption. -        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); +        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);          when(mDeviceIdleInternal.isAppOnWhitelist(TEST_CALLING_UID)).thenReturn(true);          assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));          // No permission, exemption. -        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); +        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);          when(mDeviceIdleInternal.isAppOnWhitelist(TEST_CALLING_UID)).thenReturn(false);          doReturn(true).when(() -> UserHandle.isCore(TEST_CALLING_UID));          assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));          // Both permissions and exemption. -        mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED); +        mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED);          mockUseExactAlarmState(true);          assertTrue(mBinder.canScheduleExactAlarms(TEST_CALLING_PACKAGE));      } @@ -2514,17 +2441,12 @@ public final class AlarmManagerServiceTest {          assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type);      } -    private void mockScheduleExactAlarmStatePreT(boolean declared, boolean denyList, int mode) { +    private void mockScheduleExactAlarmStatePreT(boolean declared, int mode) {          String[] requesters = declared ? new String[]{TEST_CALLING_PACKAGE} : EmptyArray.STRING;          when(mPermissionManagerInternal.getAppOpPermissionPackages(SCHEDULE_EXACT_ALARM))                  .thenReturn(requesters);          mService.refreshExactAlarmCandidates(); -        if (denyList) { -            setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, TEST_CALLING_PACKAGE); -        } else { -            setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, ""); -        }          when(mAppOpsManager.checkOpNoThrow(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID,                  TEST_CALLING_PACKAGE)).thenReturn(mode);      } @@ -2556,7 +2478,7 @@ public final class AlarmManagerServiceTest {      public void alarmClockBinderCallWithoutPermission() throws RemoteException {          mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); -        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); +        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);          when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true);          final PendingIntent alarmPi = getNewMockPendingIntent(); @@ -2630,7 +2552,7 @@ public final class AlarmManagerServiceTest {      public void exactBinderCallWithAllowlist() throws RemoteException {          mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);          // If permission is denied, only then allowlist will be checked. -        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); +        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);          when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true);          final PendingIntent alarmPi = getNewMockPendingIntent(); @@ -2650,7 +2572,7 @@ public final class AlarmManagerServiceTest {      public void exactAllowWhileIdleBinderCallWithSEAPermission() throws RemoteException {          mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); -        mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED); +        mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED);          final PendingIntent alarmPi = getNewMockPendingIntent();          mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0,                  FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null); @@ -2676,7 +2598,7 @@ public final class AlarmManagerServiceTest {          mockChangeEnabled(AlarmManager.ENABLE_USE_EXACT_ALARM, true);          mockUseExactAlarmState(true); -        mockScheduleExactAlarmStatePreT(false, false, MODE_ERRORED); +        mockScheduleExactAlarmStatePreT(false, MODE_ERRORED);          final PendingIntent alarmPi = getNewMockPendingIntent();          mBinder.set(TEST_CALLING_PACKAGE, ELAPSED_REALTIME_WAKEUP, 1234, WINDOW_EXACT, 0,                  FLAG_ALLOW_WHILE_IDLE, alarmPi, null, null, null, null); @@ -2700,7 +2622,7 @@ public final class AlarmManagerServiceTest {      public void exactAllowWhileIdleBinderCallWithAllowlist() throws RemoteException {          mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);          // If permission is denied, only then allowlist will be checked. -        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); +        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);          when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true);          final PendingIntent alarmPi = getNewMockPendingIntent(); @@ -2726,7 +2648,7 @@ public final class AlarmManagerServiceTest {      public void exactBinderCallsWithoutPermissionWithoutAllowlist() throws RemoteException {          mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); -        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); +        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);          when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(false);          final PendingIntent alarmPi = getNewMockPendingIntent(); @@ -2836,7 +2758,7 @@ public final class AlarmManagerServiceTest {      public void binderCallWithUserAllowlist() throws RemoteException {          mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); -        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); +        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);          when(mDeviceIdleInternal.isAppOnWhitelist(anyInt())).thenReturn(true);          when(mAppStateTracker.isUidPowerSaveUserExempt(TEST_CALLING_UID)).thenReturn(true); @@ -3052,135 +2974,11 @@ public final class AlarmManagerServiceTest {      }      @Test -    public void denyListChanged() { -        mService.mConstants.EXACT_ALARM_DENY_LIST = new ArraySet<>(new String[]{"p1", "p2", "p3"}); -        when(mActivityManagerInternal.getStartedUserIds()).thenReturn(EmptyArray.INT); - -        setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, "p2,p4,p5"); - -        final ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class); -        verify(mService.mHandler, times(2)).sendMessageAtTime(messageCaptor.capture(), -                anyLong()); - -        final List<Message> messages = messageCaptor.getAllValues(); -        for (final Message msg : messages) { -            assertTrue("Unwanted message sent to handler: " + msg.what, -                    msg.what == EXACT_ALARM_DENY_LIST_PACKAGES_ADDED -                            || msg.what == EXACT_ALARM_DENY_LIST_PACKAGES_REMOVED); -            mService.mHandler.handleMessage(msg); -        } - -        ArraySet<String> added = new ArraySet<>(new String[]{"p4", "p5"}); -        verify(mService).handleChangesToExactAlarmDenyList(eq(added), eq(true)); - -        ArraySet<String> removed = new ArraySet<>(new String[]{"p1", "p3"}); -        verify(mService).handleChangesToExactAlarmDenyList(eq(removed), eq(false)); -    } - -    @Test -    public void permissionGrantedDueToDenyList() { -        mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); - -        final String[] packages = {"example.package.1", "example.package.2"}; - -        final int appId1 = 232; -        final int appId2 = 431; - -        final int userId1 = 42; -        final int userId2 = 53; - -        registerAppIds(packages, new Integer[]{appId1, appId2}); - -        when(mActivityManagerInternal.getStartedUserIds()).thenReturn(new int[]{userId1, userId2}); - -        when(mPermissionManagerInternal.getAppOpPermissionPackages( -                SCHEDULE_EXACT_ALARM)).thenReturn(packages); -        mService.refreshExactAlarmCandidates(); - -        final long allowListDuration = 53442; -        when(mActivityManagerInternal.getBootTimeTempAllowListDuration()).thenReturn( -                allowListDuration); - -        mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId1, appId1), MODE_ALLOWED); -        mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId2, appId1), MODE_DEFAULT); -        mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId1, appId2), MODE_IGNORED); -        mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId2, appId2), MODE_ERRORED); - -        mService.handleChangesToExactAlarmDenyList(new ArraySet<>(packages), false); - -        // No permission revoked. -        verify(mService, never()).removeExactAlarmsOnPermissionRevoked(anyInt(), anyString(), -                anyBoolean()); - -        // Permission got granted only for (appId1, userId2). -        final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); -        final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class); -        final ArgumentCaptor<UserHandle> userCaptor = ArgumentCaptor.forClass(UserHandle.class); - -        verify(mMockContext).sendBroadcastAsUser(intentCaptor.capture(), userCaptor.capture(), -                isNull(), bundleCaptor.capture()); - -        assertEquals(userId2, userCaptor.getValue().getIdentifier()); - -        // Validate the intent. -        assertEquals(AlarmManager.ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED, -                intentCaptor.getValue().getAction()); -        assertEquals(packages[0], intentCaptor.getValue().getPackage()); - -        // Validate the options. -        final BroadcastOptions bOptions = new BroadcastOptions(bundleCaptor.getValue()); -        assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, -                bOptions.getTemporaryAppAllowlistType()); -        assertEquals(allowListDuration, bOptions.getTemporaryAppAllowlistDuration()); -        assertEquals(PowerExemptionManager.REASON_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED, -                bOptions.getTemporaryAppAllowlistReasonCode()); -    } - -    @Test -    public void permissionRevokedDueToDenyList() { -        mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true); - -        final String[] packages = {"example.package.1", "example.package.2"}; - -        final int appId1 = 232; -        final int appId2 = 431; - -        final int userId1 = 42; -        final int userId2 = 53; - -        registerAppIds(packages, new Integer[]{appId1, appId2}); - -        when(mActivityManagerInternal.getStartedUserIds()).thenReturn(new int[]{userId1, userId2}); - -        when(mPermissionManagerInternal.getAppOpPermissionPackages( -                SCHEDULE_EXACT_ALARM)).thenReturn(packages); -        mService.refreshExactAlarmCandidates(); - -        mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId1, appId1), MODE_ALLOWED); -        mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId2, appId1), MODE_DEFAULT); -        mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId1, appId2), MODE_IGNORED); -        mService.mLastOpScheduleExactAlarm.put(UserHandle.getUid(userId2, appId2), MODE_ERRORED); - -        mService.handleChangesToExactAlarmDenyList(new ArraySet<>(packages), true); - -        // Permission got revoked only for (appId1, userId2) -        verify(mService, never()).removeExactAlarmsOnPermissionRevoked( -                eq(UserHandle.getUid(userId1, appId1)), eq(packages[0]), eq(true)); -        verify(mService, never()).removeExactAlarmsOnPermissionRevoked( -                eq(UserHandle.getUid(userId1, appId2)), eq(packages[1]), eq(true)); -        verify(mService, never()).removeExactAlarmsOnPermissionRevoked( -                eq(UserHandle.getUid(userId2, appId2)), eq(packages[1]), eq(true)); - -        verify(mService).removeExactAlarmsOnPermissionRevoked( -                eq(UserHandle.getUid(userId2, appId1)), eq(packages[0]), eq(true)); -    } - -    @Test      public void opChangedPermissionRevoked() throws Exception {          mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);          mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ALLOWED); -        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); +        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);          mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);          assertAndHandleMessageSync(REMOVE_EXACT_ALARMS); @@ -3193,20 +2991,7 @@ public final class AlarmManagerServiceTest {          mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false);          mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ALLOWED); -        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); - -        mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE); - -        verify(mService.mHandler, never()).sendMessageAtTime( -                argThat(m -> m.what == REMOVE_EXACT_ALARMS), anyLong()); -    } - -    @Test -    public void opChangedNoPermissionChangeDueToDenyList() throws Exception { -        mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false); - -        mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ERRORED); -        mockScheduleExactAlarmStatePreT(true, true, MODE_DEFAULT); +        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);          mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE); @@ -3222,7 +3007,7 @@ public final class AlarmManagerServiceTest {          when(mActivityManagerInternal.getBootTimeTempAllowListDuration()).thenReturn(durationMs);          mService.mLastOpScheduleExactAlarm.put(TEST_CALLING_UID, MODE_ERRORED); -        mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED); +        mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED);          mIAppOpsCallback.opChanged(OP_SCHEDULE_EXACT_ALARM, TEST_CALLING_UID, TEST_CALLING_PACKAGE);          final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); @@ -3482,7 +3267,7 @@ public final class AlarmManagerServiceTest {                  .putExtra(Intent.EXTRA_REPLACING, true);          mockUseExactAlarmState(false); -        mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED); +        mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED);          mPackageChangesReceiver.onReceive(mMockContext, packageReplacedIntent);          assertAndHandleMessageSync(CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE); @@ -3490,7 +3275,7 @@ public final class AlarmManagerServiceTest {          assertEquals(5, mService.mAlarmStore.size());          mockUseExactAlarmState(true); -        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); +        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);          mPackageChangesReceiver.onReceive(mMockContext, packageReplacedIntent);          assertAndHandleMessageSync(CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE); @@ -3498,7 +3283,7 @@ public final class AlarmManagerServiceTest {          assertEquals(5, mService.mAlarmStore.size());          mockUseExactAlarmState(false); -        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); +        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);          mPackageChangesReceiver.onReceive(mMockContext, packageReplacedIntent);          assertAndHandleMessageSync(CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE); @@ -3653,16 +3438,16 @@ public final class AlarmManagerServiceTest {          mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, true);          mockChangeEnabled(AlarmManager.SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT, false); -        mockScheduleExactAlarmStatePreT(true, true, MODE_DEFAULT); -        assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID)); +        mockScheduleExactAlarmStatePreT(true, MODE_DEFAULT); +        assertTrue(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID)); -        mockScheduleExactAlarmStatePreT(false, false, MODE_ALLOWED); +        mockScheduleExactAlarmStatePreT(false, MODE_ALLOWED);          assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID)); -        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); +        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);          assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID)); -        mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED); +        mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED);          assertTrue(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));      } @@ -3671,11 +3456,11 @@ public final class AlarmManagerServiceTest {          mockChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, false);          mockScheduleExactAlarmState(true); -        mockScheduleExactAlarmStatePreT(true, false, MODE_ALLOWED); +        mockScheduleExactAlarmStatePreT(true, MODE_ALLOWED);          assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));          mockScheduleExactAlarmState(false); -        mockScheduleExactAlarmStatePreT(true, false, MODE_ERRORED); +        mockScheduleExactAlarmStatePreT(true, MODE_ERRORED);          assertFalse(mService.hasScheduleExactAlarmInternal(TEST_CALLING_PACKAGE, TEST_CALLING_UID));      } diff --git a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java index 596a3f3d0400..b3605ccfc25d 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java @@ -280,7 +280,9 @@ public class AsyncProcessStartTest {                  0, 0);          // Sleep until timeout should have triggered -        SystemClock.sleep(ActivityManagerService.PROC_START_TIMEOUT + 1000); +        if (wedge) { +            SystemClock.sleep(ActivityManagerService.PROC_START_TIMEOUT + 1000); +        }          return app;      } diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java index 410ae35aa790..367e14b37180 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java @@ -228,7 +228,7 @@ public class BroadcastQueueTest {          LocalServices.removeServiceForTest(AlarmManagerInternal.class);          LocalServices.addService(AlarmManagerInternal.class, mAlarmManagerInt);          doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent(); -        doNothing().when(mPackageManagerInt).setPackageStoppedState(any(), anyBoolean(), anyInt()); +        doNothing().when(mPackageManagerInt).notifyComponentUsed(any(), anyInt(), any(), any());          doAnswer((invocation) -> {              return getUidForPackage(invocation.getArgument(0));          }).when(mPackageManagerInt).getPackageUid(any(), anyLong(), eq(UserHandle.USER_SYSTEM)); @@ -1014,8 +1014,9 @@ public class BroadcastQueueTest {                      eq(PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER));              // Confirm that we unstopped manifest receivers -            verify(mAms.mPackageManagerInt, atLeastOnce()).setPackageStoppedState( -                    eq(receiverApp.info.packageName), eq(false), eq(UserHandle.USER_SYSTEM)); +            verify(mAms.mPackageManagerInt, atLeastOnce()).notifyComponentUsed( +                    eq(receiverApp.info.packageName), eq(UserHandle.USER_SYSTEM), +                    eq(callerApp.info.packageName), any());          }          // Confirm that we've reported expected usage events diff --git a/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java index 1ce79a5b596b..05ac5b5720e6 100644 --- a/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java @@ -16,8 +16,6 @@  package com.android.server.display; -import static android.os.Process.INVALID_UID; -  import static org.mockito.ArgumentMatchers.any;  import static org.mockito.ArgumentMatchers.eq;  import static org.mockito.Mockito.doNothing; @@ -35,7 +33,7 @@ import androidx.test.filters.SmallTest;  import androidx.test.runner.AndroidJUnit4;  import com.android.server.LocalServices; -import com.android.server.pm.UserManagerInternal; +import com.android.server.pm.pkg.PackageStateInternal;  import org.junit.Before;  import org.junit.Rule; @@ -55,7 +53,10 @@ public class SmallAreaDetectionControllerTest {      @Mock      private PackageManagerInternal mMockPackageManagerInternal;      @Mock -    private UserManagerInternal mMockUserManagerInternal; +    private PackageStateInternal mMockPkgStateA; +    @Mock +    private PackageStateInternal mMockPkgStateB; +      private SmallAreaDetectionController mSmallAreaDetectionController; @@ -64,29 +65,18 @@ public class SmallAreaDetectionControllerTest {      private static final String PKG_NOT_INSTALLED = "com.not.installed";      private static final float THRESHOLD_A = 0.05f;      private static final float THRESHOLD_B = 0.07f; -    private static final int USER_1 = 110; -    private static final int USER_2 = 111; -    private static final int UID_A_1 = 11011111; -    private static final int UID_A_2 = 11111111; -    private static final int UID_B_1 = 11022222; -    private static final int UID_B_2 = 11122222; +    private static final int APP_ID_A = 11111; +    private static final int APP_ID_B = 22222;      @Before      public void setup() {          LocalServices.removeServiceForTest(PackageManagerInternal.class);          LocalServices.addService(PackageManagerInternal.class, mMockPackageManagerInternal); -        LocalServices.removeServiceForTest(UserManagerInternal.class); -        LocalServices.addService(UserManagerInternal.class, mMockUserManagerInternal); - -        when(mMockUserManagerInternal.getUserIds()).thenReturn(new int[]{USER_1, USER_2}); -        when(mMockPackageManagerInternal.getPackageUid(PKG_A, 0, USER_1)).thenReturn(UID_A_1); -        when(mMockPackageManagerInternal.getPackageUid(PKG_A, 0, USER_2)).thenReturn(UID_A_2); -        when(mMockPackageManagerInternal.getPackageUid(PKG_B, 0, USER_1)).thenReturn(UID_B_1); -        when(mMockPackageManagerInternal.getPackageUid(PKG_B, 0, USER_2)).thenReturn(UID_B_2); -        when(mMockPackageManagerInternal.getPackageUid(PKG_NOT_INSTALLED, 0, USER_1)).thenReturn( -                INVALID_UID); -        when(mMockPackageManagerInternal.getPackageUid(PKG_NOT_INSTALLED, 0, USER_2)).thenReturn( -                INVALID_UID); + +        when(mMockPackageManagerInternal.getPackageStateInternal(PKG_A)).thenReturn(mMockPkgStateA); +        when(mMockPackageManagerInternal.getPackageStateInternal(PKG_B)).thenReturn(mMockPkgStateB); +        when(mMockPkgStateA.getAppId()).thenReturn(APP_ID_A); +        when(mMockPkgStateB.getAppId()).thenReturn(APP_ID_B);          mSmallAreaDetectionController = spy(new SmallAreaDetectionController(                  new ContextWrapper(ApplicationProvider.getApplicationContext()), @@ -99,9 +89,9 @@ public class SmallAreaDetectionControllerTest {          final String property = PKG_A + ":" + THRESHOLD_A + "," + PKG_B + ":" + THRESHOLD_B;          mSmallAreaDetectionController.updateAllowlist(property); -        final int[] resultUidArray = {UID_A_1, UID_B_1, UID_A_2, UID_B_2}; -        final float[] resultThresholdArray = {THRESHOLD_A, THRESHOLD_B, THRESHOLD_A, THRESHOLD_B}; -        verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultUidArray), +        final int[] resultAppIdArray = {APP_ID_A, APP_ID_B}; +        final float[] resultThresholdArray = {THRESHOLD_A, THRESHOLD_B}; +        verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultAppIdArray),                  eq(resultThresholdArray));      } @@ -110,9 +100,9 @@ public class SmallAreaDetectionControllerTest {          final String property = PKG_A + "," + PKG_B + ":" + THRESHOLD_B;          mSmallAreaDetectionController.updateAllowlist(property); -        final int[] resultUidArray = {UID_B_1, UID_B_2}; -        final float[] resultThresholdArray = {THRESHOLD_B, THRESHOLD_B}; -        verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultUidArray), +        final int[] resultAppIdArray = {APP_ID_B}; +        final float[] resultThresholdArray = {THRESHOLD_B}; +        verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultAppIdArray),                  eq(resultThresholdArray));      } @@ -122,9 +112,9 @@ public class SmallAreaDetectionControllerTest {                  PKG_A + ":" + THRESHOLD_A + "," + PKG_NOT_INSTALLED + ":" + THRESHOLD_B;          mSmallAreaDetectionController.updateAllowlist(property); -        final int[] resultUidArray = {UID_A_1, UID_A_2}; -        final float[] resultThresholdArray = {THRESHOLD_A, THRESHOLD_A}; -        verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultUidArray), +        final int[] resultAppIdArray = {APP_ID_A}; +        final float[] resultThresholdArray = {THRESHOLD_A}; +        verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultAppIdArray),                  eq(resultThresholdArray));      } diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java index eb50556821eb..610ea903767e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java @@ -35,6 +35,7 @@ import static org.mockito.Mockito.verify;  import static org.mockito.Mockito.when;  import android.app.AppOpsManager; +import android.content.ComponentName;  import android.content.Context;  import android.content.Intent;  import android.content.IntentSender; @@ -427,6 +428,7 @@ public class PackageArchiverTest {          for (LauncherActivityInfo mainActivity : createLauncherActivities()) {              ArchiveState.ArchiveActivityInfo activityInfo = new ArchiveState.ArchiveActivityInfo(                      mainActivity.getLabel().toString(), +                    mainActivity.getComponentName(),                      ICON_PATH, null);              activityInfos.add(activityInfo);          } @@ -437,9 +439,11 @@ public class PackageArchiverTest {          ActivityInfo activityInfo = mock(ActivityInfo.class);          LauncherActivityInfo activity1 = mock(LauncherActivityInfo.class);          when(activity1.getLabel()).thenReturn("activity1"); +        when(activity1.getComponentName()).thenReturn(new ComponentName("pkg1", "class1"));          when(activity1.getActivityInfo()).thenReturn(activityInfo);          LauncherActivityInfo activity2 = mock(LauncherActivityInfo.class);          when(activity2.getLabel()).thenReturn("activity2"); +        when(activity2.getComponentName()).thenReturn(new ComponentName("pkg2", "class2"));          when(activity2.getActivityInfo()).thenReturn(activityInfo);          return List.of(activity1, activity2);      } diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java new file mode 100644 index 000000000000..2003d04e1dbc --- /dev/null +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsTest.java @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power.stats; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.BatteryConsumer; +import android.os.PersistableBundle; +import android.util.Xml; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.os.PowerStats; +import com.android.modules.utils.TypedXmlPullParser; +import com.android.modules.utils.TypedXmlSerializer; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.text.ParseException; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class AggregatedPowerStatsTest { +    private static final int TEST_POWER_COMPONENT = 1077; +    private static final int APP_1 = 27; +    private static final int APP_2 = 42; + +    private AggregatedPowerStatsConfig mAggregatedPowerStatsConfig; +    private PowerStats.Descriptor mPowerComponentDescriptor; + +    @Before +    public void setup() throws ParseException { +        mAggregatedPowerStatsConfig = new AggregatedPowerStatsConfig(); +        mAggregatedPowerStatsConfig.trackPowerComponent(TEST_POWER_COMPONENT) +                .trackDeviceStates( +                        AggregatedPowerStatsConfig.STATE_POWER, +                        AggregatedPowerStatsConfig.STATE_SCREEN) +                .trackUidStates( +                        AggregatedPowerStatsConfig.STATE_POWER, +                        AggregatedPowerStatsConfig.STATE_SCREEN, +                        AggregatedPowerStatsConfig.STATE_PROCESS_STATE); + +        mPowerComponentDescriptor = new PowerStats.Descriptor(TEST_POWER_COMPONENT, "fan", 2, 3, +                PersistableBundle.forPair("speed", "fast")); +    } + +    @Test +    public void aggregation() { +        AggregatedPowerStats stats = prepareAggregatePowerStats(); + +        verifyAggregatedPowerStats(stats); +    } + +    @Test +    public void xmlPersistence() throws Exception { +        AggregatedPowerStats stats = prepareAggregatePowerStats(); + +        ByteArrayOutputStream baos = new ByteArrayOutputStream(); +        TypedXmlSerializer serializer = Xml.newFastSerializer(); +        serializer.setOutput(baos, "UTF-8"); +        stats.writeXml(serializer); +        serializer.flush(); + +        TypedXmlPullParser parser = Xml.newFastPullParser(); +        parser.setInput(new ByteArrayInputStream(baos.toByteArray()), "UTF-8"); +        AggregatedPowerStats actualStats = AggregatedPowerStats.createFromXml(parser, +                mAggregatedPowerStatsConfig); + +        verifyAggregatedPowerStats(actualStats); +    } + +    private AggregatedPowerStats prepareAggregatePowerStats() { +        AggregatedPowerStats stats = new AggregatedPowerStats(mAggregatedPowerStatsConfig); +        stats.addClockUpdate(1000, 456); +        stats.setDuration(789); + +        stats.setDeviceState(AggregatedPowerStatsConfig.STATE_SCREEN, +                AggregatedPowerStatsConfig.SCREEN_STATE_ON, 2000); +        stats.setUidState(APP_1, AggregatedPowerStatsConfig.STATE_PROCESS_STATE, +                BatteryConsumer.PROCESS_STATE_CACHED, 2000); +        stats.setUidState(APP_2, AggregatedPowerStatsConfig.STATE_PROCESS_STATE, +                BatteryConsumer.PROCESS_STATE_FOREGROUND, 2000); + +        PowerStats ps = new PowerStats(mPowerComponentDescriptor); +        ps.stats[0] = 100; +        ps.stats[1] = 987; + +        ps.uidStats.put(APP_1, new long[]{389, 0, 739}); +        ps.uidStats.put(APP_2, new long[]{278, 314, 628}); + +        stats.addPowerStats(ps, 3000); + +        stats.setDeviceState(AggregatedPowerStatsConfig.STATE_SCREEN, +                AggregatedPowerStatsConfig.SCREEN_STATE_OTHER, 4000); +        stats.setUidState(APP_2, AggregatedPowerStatsConfig.STATE_PROCESS_STATE, +                BatteryConsumer.PROCESS_STATE_BACKGROUND, 4000); + +        ps.stats[0] = 444; +        ps.stats[1] = 0; + +        ps.uidStats.put(APP_1, new long[]{0, 0, 400}); +        ps.uidStats.put(APP_2, new long[]{100, 200, 300}); + +        stats.addPowerStats(ps, 5000); + +        return stats; +    } + +    private void verifyAggregatedPowerStats(AggregatedPowerStats stats) { +        PowerStats.Descriptor descriptor = stats.getPowerComponentStats(TEST_POWER_COMPONENT) +                .getPowerStatsDescriptor(); +        assertThat(descriptor.powerComponentId).isEqualTo(TEST_POWER_COMPONENT); +        assertThat(descriptor.name).isEqualTo("fan"); +        assertThat(descriptor.statsArrayLength).isEqualTo(2); +        assertThat(descriptor.uidStatsArrayLength).isEqualTo(3); +        assertThat(descriptor.extras.getString("speed")).isEqualTo("fast"); + +        assertThat(getDeviceStats(stats, +                AggregatedPowerStatsConfig.POWER_STATE_BATTERY, +                AggregatedPowerStatsConfig.SCREEN_STATE_ON)) +                .isEqualTo(new long[]{322, 987}); + +        assertThat(getDeviceStats(stats, +                AggregatedPowerStatsConfig.POWER_STATE_BATTERY, +                AggregatedPowerStatsConfig.SCREEN_STATE_OTHER)) +                .isEqualTo(new long[]{222, 0}); + +        assertThat(getUidDeviceStats(stats, +                APP_1, +                AggregatedPowerStatsConfig.POWER_STATE_BATTERY, +                AggregatedPowerStatsConfig.SCREEN_STATE_ON, +                BatteryConsumer.PROCESS_STATE_UNSPECIFIED)) +                .isEqualTo(new long[]{259, 0, 492}); + +        assertThat(getUidDeviceStats(stats, +                APP_1, +                AggregatedPowerStatsConfig.POWER_STATE_BATTERY, +                AggregatedPowerStatsConfig.SCREEN_STATE_ON, +                BatteryConsumer.PROCESS_STATE_CACHED)) +                .isEqualTo(new long[]{129, 0, 446}); + +        assertThat(getUidDeviceStats(stats, +                APP_1, +                AggregatedPowerStatsConfig.POWER_STATE_BATTERY, +                AggregatedPowerStatsConfig.SCREEN_STATE_OTHER, +                BatteryConsumer.PROCESS_STATE_CACHED)) +                .isEqualTo(new long[]{0, 0, 200}); + +        assertThat(getUidDeviceStats(stats, +                APP_2, +                AggregatedPowerStatsConfig.POWER_STATE_BATTERY, +                AggregatedPowerStatsConfig.SCREEN_STATE_ON, +                BatteryConsumer.PROCESS_STATE_UNSPECIFIED)) +                .isEqualTo(new long[]{185, 209, 418}); + +        assertThat(getUidDeviceStats(stats, +                APP_2, +                AggregatedPowerStatsConfig.POWER_STATE_BATTERY, +                AggregatedPowerStatsConfig.SCREEN_STATE_ON, +                BatteryConsumer.PROCESS_STATE_FOREGROUND)) +                .isEqualTo(new long[]{142, 204, 359}); + +        assertThat(getUidDeviceStats(stats, +                APP_2, +                AggregatedPowerStatsConfig.POWER_STATE_BATTERY, +                AggregatedPowerStatsConfig.SCREEN_STATE_OTHER, +                BatteryConsumer.PROCESS_STATE_BACKGROUND)) +                .isEqualTo(new long[]{50, 100, 150}); +    } + +    private static long[] getDeviceStats(AggregatedPowerStats stats, int... states) { +        long[] out = new long[states.length]; +        stats.getPowerComponentStats(TEST_POWER_COMPONENT).getDeviceStats(out, states); +        return out; +    } + +    private static long[] getUidDeviceStats(AggregatedPowerStats stats, int uid, int... states) { +        long[] out = new long[states.length]; +        stats.getPowerComponentStats(TEST_POWER_COMPONENT).getUidStats(out, uid, states); +        return out; +    } +} diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java index 5a2d2e3d33fd..663af5da48d2 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java @@ -42,6 +42,7 @@ import android.util.SparseArray;  import androidx.test.InstrumentationRegistry; +import com.android.internal.os.Clock;  import com.android.internal.os.CpuScalingPolicies;  import com.android.internal.os.PowerProfile; @@ -214,6 +215,7 @@ public class BatteryExternalStatsWorkerTest {      public class TestBatteryStatsImpl extends BatteryStatsImpl {          public TestBatteryStatsImpl(Context context) { +            super(Clock.SYSTEM_CLOCK, null);              mPowerProfile = new PowerProfile(context, true /* forTest */);              SparseArray<int[]> cpusByPolicy = new SparseArray<>(); diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java index 77124d0120f7..abb3be7b215b 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java @@ -113,15 +113,15 @@ public class BatteryStatsHistoryIteratorTest {      public void constrainedIteration() {          prepareHistory(); -        // Initial time is 3000 +        // Initial time is 1000_000          assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(0, 0), -                3_000L, 3_000L, 1003_000L, 2003_000L, 2004_000L); -        assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(1000_000, 0), -                1003_000L, 2003_000L, 2004_000L); -        assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(0, 2000_000L), -                3_000L, 3_000L, 1003_000L); +                1000_000L, 1000_000L, 2000_000L, 3000_000L, 3001_000L); +        assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(2000_000, 0), +                2000_000L, 3000_000L, 3001_000L); +        assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(0, 3000_000L), +                1000_000L, 1000_000L, 2000_000L);          assertIncludedEvents(mBatteryStats.iterateBatteryStatsHistory(1003_000L, 2004_000L), -                1003_000L, 2003_000L); +                2000_000L);      }      private void prepareHistory() { @@ -144,7 +144,7 @@ public class BatteryStatsHistoryIteratorTest {          ArrayList<Long> actualTimestamps = new ArrayList<>();          while (iterator.hasNext()) {              BatteryStats.HistoryItem item = iterator.next(); -            actualTimestamps.add(item.currentTime); +            actualTimestamps.add(item.time);          }          assertThat(actualTimestamps).isEqualTo(Arrays.asList(expectedTimestamps));      } diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java index f22296a6261c..1dd499c99c0c 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java @@ -41,6 +41,7 @@ import androidx.test.runner.AndroidJUnit4;  import com.android.internal.os.BatteryStatsHistory;  import com.android.internal.os.BatteryStatsHistoryIterator; +import com.android.internal.os.MonotonicClock;  import com.android.internal.os.PowerStats;  import org.junit.Before; @@ -69,6 +70,7 @@ public class BatteryStatsHistoryTest {      private File mSystemDir;      private File mHistoryDir;      private final MockClock mClock = new MockClock(); +    private final MonotonicClock mMonotonicClock = new MonotonicClock(0, mClock);      private BatteryStatsHistory mHistory;      private BatteryStats.HistoryPrinter mHistoryPrinter;      @Mock @@ -94,7 +96,7 @@ public class BatteryStatsHistoryTest {          mClock.realtime = 123;          mHistory = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32, 1024, -                mStepDetailsCalculator, mClock, mTracer) { +                mStepDetailsCalculator, mClock, mMonotonicClock, mTracer) {              @Override              public boolean readFileToParcel(Parcel out, AtomicFile file) {                  mReadFiles.add(file.getBaseFile().getName()); @@ -210,7 +212,7 @@ public class BatteryStatsHistoryTest {              mClock.realtime = 1000 * i;              fileList.add(mClock.realtime + ".bh"); -            mHistory.startNextFile(); +            mHistory.startNextFile(mClock.realtime);              createActiveFile(mHistory);              verifyFileNames(mHistory, fileList);              verifyActiveFile(mHistory, mClock.realtime + ".bh"); @@ -218,7 +220,7 @@ public class BatteryStatsHistoryTest {          // create file 32          mClock.realtime = 1000 * 32; -        mHistory.startNextFile(); +        mHistory.startNextFile(mClock.realtime);          createActiveFile(mHistory);          fileList.add("32000.bh");          fileList.remove(0); @@ -229,7 +231,7 @@ public class BatteryStatsHistoryTest {          // create file 33          mClock.realtime = 1000 * 33; -        mHistory.startNextFile(); +        mHistory.startNextFile(mClock.realtime);          createActiveFile(mHistory);          // verify file 1 is deleted          fileList.add("33000.bh"); @@ -240,7 +242,7 @@ public class BatteryStatsHistoryTest {          // create a new BatteryStatsHistory object, it will pick up existing history files.          BatteryStatsHistory history2 = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32, 1024, -                null, mClock, mTracer); +                null, mClock, mMonotonicClock, mTracer);          // verify constructor can pick up all files from file system.          verifyFileNames(history2, fileList);          verifyActiveFile(history2, "33000.bh"); @@ -262,7 +264,7 @@ public class BatteryStatsHistoryTest {          // create file 1.          mClock.realtime = 2345678; -        history2.startNextFile(); +        history2.startNextFile(mClock.realtime);          createActiveFile(history2);          verifyFileNames(history2, Arrays.asList("1234567.bh", "2345678.bh"));          verifyActiveFile(history2, "2345678.bh"); @@ -336,14 +338,14 @@ public class BatteryStatsHistoryTest {          mHistory.recordEvent(mClock.realtime, mClock.uptime,                  BatteryStats.HistoryItem.EVENT_JOB_START, "job", 42); -        mHistory.startNextFile();       // 1000.bh +        mHistory.startNextFile(mClock.realtime);       // 1000.bh          mClock.realtime = 2000;          mClock.uptime = 2000;          mHistory.recordEvent(mClock.realtime, mClock.uptime,                  BatteryStats.HistoryItem.EVENT_JOB_FINISH, "job", 42); -        mHistory.startNextFile();       // 2000.bh +        mHistory.startNextFile(mClock.realtime);       // 2000.bh          mClock.realtime = 3000;          mClock.uptime = 3000; @@ -351,7 +353,7 @@ public class BatteryStatsHistoryTest {                  HistoryItem.EVENT_ALARM, "alarm", 42);          // Flush accumulated history to disk -        mHistory.startNextFile(); +        mHistory.startNextFile(mClock.realtime);      }      private void verifyActiveFile(BatteryStatsHistory history, String file) { @@ -518,7 +520,7 @@ public class BatteryStatsHistoryTest {          // Keep the preserved part of history short - we only need to capture the very tail of          // history.          mHistory = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 1, 6000, -                mStepDetailsCalculator, mClock, mTracer); +                mStepDetailsCalculator, mClock, mMonotonicClock, mTracer);          mHistory.forceRecordAllHistory(); diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java index 5df0acb65249..b1da1fc88378 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java @@ -40,6 +40,7 @@ import androidx.test.filters.SmallTest;  import androidx.test.runner.AndroidJUnit4;  import com.android.internal.os.BatteryStatsHistoryIterator; +import com.android.internal.os.MonotonicClock;  import com.android.internal.os.PowerProfile;  import org.junit.Rule; @@ -64,6 +65,8 @@ public class BatteryUsageStatsProviderTest {              new BatteryUsageStatsRule(12345, mHistoryDir)                      .setAveragePower(PowerProfile.POWER_FLASHLIGHT, 360.0)                      .setAveragePower(PowerProfile.POWER_AUDIO, 720.0); +    private MockClock mMockClock = mStatsRule.getMockClock(); +      @Test      public void test_getBatteryUsageStats() {          BatteryStatsImpl batteryStats = prepareBatteryStats(); @@ -369,17 +372,23 @@ public class BatteryUsageStatsProviderTest {      public void testAggregateBatteryStats() {          Context context = InstrumentationRegistry.getContext();          BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats(); -        mStatsRule.setCurrentTime(5 * MINUTE_IN_MS); +        MonotonicClock monotonicClock = new MonotonicClock(0, mStatsRule.getMockClock()); + +        setTime(5 * MINUTE_IN_MS);          synchronized (batteryStats) {              batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);          } -        BatteryUsageStatsStore batteryUsageStatsStore = new BatteryUsageStatsStore(context, -                batteryStats, new File(context.getCacheDir(), "BatteryUsageStatsProviderTest"), -                new TestHandler(), Integer.MAX_VALUE); -        batteryUsageStatsStore.onSystemReady(); + +        PowerStatsStore powerStatsStore = new PowerStatsStore( +                new File(context.getCacheDir(), "BatteryUsageStatsProviderTest"), +                new TestHandler(), null);          BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, -                batteryStats, batteryUsageStatsStore); +                batteryStats, powerStatsStore); + +        batteryStats.setBatteryResetListener(reason -> +                powerStatsStore.storeBatteryUsageStats(monotonicClock.monotonicTime(), +                        provider.getBatteryUsageStats(BatteryUsageStatsQuery.DEFAULT)));          synchronized (batteryStats) {              batteryStats.noteFlashlightOnLocked(APP_UID, @@ -389,7 +398,7 @@ public class BatteryUsageStatsProviderTest {              batteryStats.noteFlashlightOffLocked(APP_UID,                      20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS);          } -        mStatsRule.setCurrentTime(25 * MINUTE_IN_MS); +        setTime(25 * MINUTE_IN_MS);          synchronized (batteryStats) {              batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);          } @@ -402,7 +411,7 @@ public class BatteryUsageStatsProviderTest {              batteryStats.noteFlashlightOffLocked(APP_UID,                      50 * MINUTE_IN_MS, 50 * MINUTE_IN_MS);          } -        mStatsRule.setCurrentTime(55 * MINUTE_IN_MS); +        setTime(55 * MINUTE_IN_MS);          synchronized (batteryStats) {              batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);          } @@ -416,7 +425,7 @@ public class BatteryUsageStatsProviderTest {              batteryStats.noteFlashlightOffLocked(APP_UID,                      70 * MINUTE_IN_MS, 70 * MINUTE_IN_MS);          } -        mStatsRule.setCurrentTime(75 * MINUTE_IN_MS); +        setTime(75 * MINUTE_IN_MS);          synchronized (batteryStats) {              batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND);          } @@ -430,7 +439,7 @@ public class BatteryUsageStatsProviderTest {              batteryStats.noteFlashlightOffLocked(APP_UID,                      90 * MINUTE_IN_MS, 90 * MINUTE_IN_MS);          } -        mStatsRule.setCurrentTime(95 * MINUTE_IN_MS); +        setTime(95 * MINUTE_IN_MS);          // Include the first and the second snapshot, but not the third or current          BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder() @@ -457,29 +466,41 @@ public class BatteryUsageStatsProviderTest {                  .of(180.0);      } +    private void setTime(long timeMs) { +        mMockClock.currentTime = timeMs; +        mMockClock.realtime = timeMs; +    } +      @Test      public void testAggregateBatteryStats_incompatibleSnapshot() {          Context context = InstrumentationRegistry.getContext();          MockBatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();          batteryStats.initMeasuredEnergyStats(new String[]{"FOO", "BAR"}); -        BatteryUsageStatsStore batteryUsageStatsStore = mock(BatteryUsageStatsStore.class); - -        when(batteryUsageStatsStore.listBatteryUsageStatsTimestamps()) -                .thenReturn(new long[]{1000, 2000}); +        PowerStatsStore powerStatsStore = mock(PowerStatsStore.class); -        when(batteryUsageStatsStore.loadBatteryUsageStats(1000)).thenReturn( +        PowerStatsSpan span0 = new PowerStatsSpan(0); +        span0.addTimeFrame(0, 1000, 1234); +        span0.addSection(new BatteryUsageStatsSection(                  new BatteryUsageStats.Builder(batteryStats.getCustomEnergyConsumerNames()) -                        .setStatsDuration(1234).build()); +                        .setStatsDuration(1234).build())); -        // Add a snapshot, with a different set of custom power components.  It should -        // be skipped by the aggregation. -        when(batteryUsageStatsStore.loadBatteryUsageStats(2000)).thenReturn( +        PowerStatsSpan span1 = new PowerStatsSpan(1); +        span1.addTimeFrame(0, 2000, 4321); +        span1.addSection(new BatteryUsageStatsSection(                  new BatteryUsageStats.Builder(new String[]{"different"}) -                        .setStatsDuration(4321).build()); +                        .setStatsDuration(4321).build())); + +        when(powerStatsStore.getTableOfContents()).thenReturn( +                List.of(span0.getMetadata(), span1.getMetadata())); + +        when(powerStatsStore.loadPowerStatsSpan(0, BatteryUsageStatsSection.TYPE)) +                .thenReturn(span0); +        when(powerStatsStore.loadPowerStatsSpan(1, BatteryUsageStatsSection.TYPE)) +                .thenReturn(span1);          BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context, -                batteryStats, batteryUsageStatsStore); +                batteryStats, powerStatsStore);          BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder()                  .aggregateSnapshots(0, 3000) diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java index 93cbea6125fa..3579fce11c8d 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java @@ -88,6 +88,10 @@ public class BatteryUsageStatsRule implements TestRule {          mBatteryStats.onSystemReady();      } +    public MockClock getMockClock() { +        return mMockClock; +    } +      public BatteryUsageStatsRule setTestPowerProfile(@XmlRes int xmlId) {          mPowerProfile.forceInitForTesting(mContext, xmlId);          return this; diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsStoreTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsStoreTest.java deleted file mode 100644 index b846e3a36656..000000000000 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsStoreTest.java +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - *      http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.power.stats; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.Mockito.mock; - -import android.content.Context; -import android.os.BatteryManager; -import android.os.BatteryUsageStats; -import android.os.BatteryUsageStatsQuery; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.util.Xml; - -import androidx.test.InstrumentationRegistry; -import androidx.test.runner.AndroidJUnit4; - -import com.android.internal.os.PowerProfile; -import com.android.modules.utils.TypedXmlSerializer; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; - -@RunWith(AndroidJUnit4.class) -@SuppressWarnings("GuardedBy") -public class BatteryUsageStatsStoreTest { -    private static final long MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES = 2 * 1024; - -    private final MockClock mMockClock = new MockClock(); -    private MockBatteryStatsImpl mBatteryStats; -    private BatteryUsageStatsStore mBatteryUsageStatsStore; -    private BatteryUsageStatsProvider mBatteryUsageStatsProvider; -    private File mStoreDirectory; - -    @Before -    public void setup() { -        mMockClock.currentTime = 123; -        mBatteryStats = new MockBatteryStatsImpl(mMockClock); -        mBatteryStats.setNoAutoReset(true); -        mBatteryStats.setPowerProfile(mock(PowerProfile.class)); -        mBatteryStats.onSystemReady(); - -        Context context = InstrumentationRegistry.getContext(); - -        mStoreDirectory = new File(context.getCacheDir(), "BatteryUsageStatsStoreTest"); -        clearDirectory(mStoreDirectory); - -        mBatteryUsageStatsStore = new BatteryUsageStatsStore(context, mBatteryStats, -                mStoreDirectory, new TestHandler(), MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES); -        mBatteryUsageStatsStore.onSystemReady(); - -        mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mBatteryStats); -    } - -    @Test -    public void testStoreSnapshot() { -        mMockClock.currentTime = 1_600_000; -        mMockClock.realtime = 1000; -        mMockClock.uptime = 1000; - -        prepareBatteryStats(); - -        mMockClock.realtime = 1_000_000; -        mMockClock.uptime = 1_000_000; -        mBatteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND); - -        final long[] timestamps = mBatteryUsageStatsStore.listBatteryUsageStatsTimestamps(); -        assertThat(timestamps).hasLength(1); -        assertThat(timestamps[0]).isEqualTo(1_600_000); - -        final BatteryUsageStats batteryUsageStats = mBatteryUsageStatsStore.loadBatteryUsageStats( -                1_600_000); -        assertThat(batteryUsageStats.getStatsStartTimestamp()).isEqualTo(123); -        assertThat(batteryUsageStats.getStatsEndTimestamp()).isEqualTo(1_600_000); -        assertThat(batteryUsageStats.getBatteryCapacity()).isEqualTo(4000); -        assertThat(batteryUsageStats.getDischargePercentage()).isEqualTo(5); -        assertThat(batteryUsageStats.getDischargeDurationMs()).isEqualTo(1_000_000 - 1_000); -        assertThat(batteryUsageStats.getAggregateBatteryConsumer( -                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE).getConsumedPower()) -                .isEqualTo(600);  // (3_600_000 - 3_000_000) / 1000 -    } - -    @Test -    public void testGarbageCollectOldSnapshots() throws Exception { -        prepareBatteryStats(); - -        mMockClock.realtime = 10_000_000; -        mMockClock.uptime = 10_000_000; -        mMockClock.currentTime = 10_000_000; - -        final int snapshotFileSize = getSnapshotFileSize(); -        final int numberOfSnapshots = -                (int) (MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES / snapshotFileSize); -        for (int i = 0; i < numberOfSnapshots + 2; i++) { -            mBatteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND); - -            mMockClock.realtime += 10_000_000; -            mMockClock.uptime += 10_000_000; -            mMockClock.currentTime += 10_000_000; -            prepareBatteryStats(); -        } - -        final long[] timestamps = mBatteryUsageStatsStore.listBatteryUsageStatsTimestamps(); -        Arrays.sort(timestamps); -        assertThat(timestamps).hasLength(numberOfSnapshots); -        // Two snapshots (10_000_000 and 20_000_000) should have been discarded -        assertThat(timestamps[0]).isEqualTo(30_000_000); -        assertThat(getDirectorySize(mStoreDirectory)) -                .isAtMost(MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES); -    } - -    @Test -    public void testRemoveAllSnapshots() throws Exception { -        prepareBatteryStats(); - -        for (int i = 0; i < 3; i++) { -            mMockClock.realtime += 10_000_000; -            mMockClock.uptime += 10_000_000; -            mMockClock.currentTime += 10_000_000; -            prepareBatteryStats(); - -            mBatteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND); -        } - -        assertThat(getDirectorySize(mStoreDirectory)).isNotEqualTo(0); - -        mBatteryUsageStatsStore.removeAllSnapshots(); - -        assertThat(getDirectorySize(mStoreDirectory)).isEqualTo(0); -    } - -    @Test -    public void testSavingStatsdAtomPullTimestamp() { -        mBatteryUsageStatsStore.setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(1234); -        assertThat(mBatteryUsageStatsStore.getLastBatteryUsageStatsBeforeResetAtomPullTimestamp()) -                .isEqualTo(1234); -        mBatteryUsageStatsStore.setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(5478); -        assertThat(mBatteryUsageStatsStore.getLastBatteryUsageStatsBeforeResetAtomPullTimestamp()) -                .isEqualTo(5478); -    } - -    private void prepareBatteryStats() { -        mBatteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, 100, -                /* plugType */ 0, 90, 72, 3700, 3_600_000, 4_000_000, 0, -                mMockClock.realtime, mMockClock.uptime, mMockClock.currentTime); -        mBatteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, 100, -                /* plugType */ 0, 85, 72, 3700, 3_000_000, 4_000_000, 0, -                mMockClock.realtime + 500_000, mMockClock.uptime + 500_000, -                mMockClock.currentTime + 500_000); -    } - -    private void clearDirectory(File dir) { -        if (dir.exists()) { -            for (File child : dir.listFiles()) { -                if (child.isDirectory()) { -                    clearDirectory(child); -                } -                child.delete(); -            } -        } -    } - -    private long getDirectorySize(File dir) { -        long size = 0; -        if (dir.exists()) { -            for (File child : dir.listFiles()) { -                if (child.isDirectory()) { -                    size += getDirectorySize(child); -                } else { -                    size += child.length(); -                } -            } -        } -        return size; -    } - -    private int getSnapshotFileSize() throws IOException { -        BatteryUsageStats stats = mBatteryUsageStatsProvider.getBatteryUsageStats( -                new BatteryUsageStatsQuery.Builder() -                        .setMaxStatsAgeMs(0) -                        .includePowerModels() -                        .build()); -        ByteArrayOutputStream out = new ByteArrayOutputStream(); -        TypedXmlSerializer serializer = Xml.newBinarySerializer(); -        serializer.setOutput(out, StandardCharsets.UTF_8.name()); -        serializer.startDocument(null, true); -        stats.writeXml(serializer); -        serializer.endDocument(); -        return out.toByteArray().length; -    } - -    private static class TestHandler extends Handler { -        TestHandler() { -            super(Looper.getMainLooper()); -        } - -        @Override -        public boolean sendMessageAtTime(Message msg, long uptimeMillis) { -            msg.getCallback().run(); -            return true; -        } -    } -} diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java index 4ecee9fe7d23..30a731818f36 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java @@ -122,7 +122,7 @@ public class MultiStateStatsTest {          // 4 * 10 = 40 bits needed to represent the composite state          MultiStateStats.States[] states = new MultiStateStats.States[10];          for (int i = 0; i < states.length; i++) { -            states[i] = new MultiStateStats.States(true, labels); +            states[i] = new MultiStateStats.States("foo", true, labels);          }          IllegalArgumentException e = assertThrows(IllegalArgumentException.class,                  () -> new MultiStateStats.Factory(DIMENSION_COUNT, states)); @@ -191,8 +191,8 @@ public class MultiStateStatsTest {      private static MultiStateStats.Factory makeFactory(boolean trackBatteryState,              boolean trackProcState, boolean trackScreenState) {          return new MultiStateStats.Factory(DIMENSION_COUNT, -                new MultiStateStats.States(trackBatteryState, "plugged-in", "on-battery"), -                new MultiStateStats.States(trackProcState, +                new MultiStateStats.States("bs", trackBatteryState, "plugged-in", "on-battery"), +                new MultiStateStats.States("ps", trackProcState,                          BatteryConsumer.processStateToString(                                  BatteryConsumer.PROCESS_STATE_UNSPECIFIED),                          BatteryConsumer.processStateToString( @@ -203,7 +203,7 @@ public class MultiStateStatsTest {                                  BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE),                          BatteryConsumer.processStateToString(                                  BatteryConsumer.PROCESS_STATE_CACHED)), -                new MultiStateStats.States(trackScreenState, "screen-off", "plugged-in")); +                new MultiStateStats.States("scr", trackScreenState, "screen-off", "plugged-in"));      }      private FactorySubject assertThatCpuPerformanceStatsFactory( diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java index 47de44324ae1..b52fc8a7c727 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java @@ -24,11 +24,14 @@ import static org.mockito.Mockito.mock;  import android.os.BatteryConsumer;  import android.os.BatteryStats;  import android.os.PersistableBundle; +import android.text.format.DateFormat; +import androidx.annotation.NonNull;  import androidx.test.filters.SmallTest;  import androidx.test.runner.AndroidJUnit4;  import com.android.internal.os.BatteryStatsHistory; +import com.android.internal.os.MonotonicClock;  import com.android.internal.os.PowerStats;  import org.junit.Before; @@ -36,16 +39,19 @@ import org.junit.Test;  import org.junit.runner.RunWith;  import java.text.ParseException; -import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.List; +import java.util.TimeZone;  @RunWith(AndroidJUnit4.class)  @SmallTest  public class PowerStatsAggregatorTest {      private static final int TEST_POWER_COMPONENT = 77;      private static final int TEST_UID = 42; +    private static final long START_TIME = 1234;      private final MockClock mClock = new MockClock(); -    private long mStartTime; +    private final MonotonicClock mMonotonicClock = new MonotonicClock(START_TIME, mClock);      private BatteryStatsHistory mHistory;      private PowerStatsAggregator mAggregator;      private int mAggregatedStatsCount; @@ -53,25 +59,25 @@ public class PowerStatsAggregatorTest {      @Before      public void setup() throws ParseException {          mHistory = new BatteryStatsHistory(32, 1024, -                mock(BatteryStatsHistory.HistoryStepDetailsCalculator.class), mClock); -        mStartTime = new SimpleDateFormat("yyyy-MM-dd HH:mm") -                .parse("2008-09-23 08:00").getTime(); -        mClock.currentTime = mStartTime; +                mock(BatteryStatsHistory.HistoryStepDetailsCalculator.class), mClock, +                mMonotonicClock); -        PowerStatsAggregator.Builder builder = new PowerStatsAggregator.Builder(mHistory); -        builder.trackPowerComponent(TEST_POWER_COMPONENT) +        AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig(); +        config.trackPowerComponent(TEST_POWER_COMPONENT)                  .trackDeviceStates( -                        PowerStatsAggregator.STATE_POWER, -                        PowerStatsAggregator.STATE_SCREEN) +                        AggregatedPowerStatsConfig.STATE_POWER, +                        AggregatedPowerStatsConfig.STATE_SCREEN)                  .trackUidStates( -                        PowerStatsAggregator.STATE_POWER, -                        PowerStatsAggregator.STATE_SCREEN, -                        PowerStatsAggregator.STATE_PROCESS_STATE); -        mAggregator = builder.build(); +                        AggregatedPowerStatsConfig.STATE_POWER, +                        AggregatedPowerStatsConfig.STATE_SCREEN, +                        AggregatedPowerStatsConfig.STATE_PROCESS_STATE); +        mAggregator = new PowerStatsAggregator(config, mHistory);      }      @Test      public void stateUpdates() { +        mClock.currentTime = 1222156800000L;    // An important date in world history +          mHistory.forceRecordAllHistory();          mHistory.recordBatteryState(mClock.realtime, mClock.uptime, 10, /* plugged */ true);          mHistory.recordStateStartEvent(mClock.realtime, mClock.uptime, @@ -98,15 +104,32 @@ public class PowerStatsAggregatorTest {          mHistory.recordProcessStateChange(mClock.realtime, mClock.uptime, TEST_UID,                  BatteryConsumer.PROCESS_STATE_BACKGROUND); -        advance(3000); +        advance(1000); + +        mClock.currentTime += 60 * 60 * 1000;       // one hour +        mHistory.recordCurrentTimeChange(mClock.realtime, mClock.uptime, mClock.currentTime); + +        advance(2000);          powerStats.stats = new long[]{20000};          powerStats.uidStats.put(TEST_UID, new long[]{4444});          mHistory.recordPowerStats(mClock.realtime, mClock.uptime, powerStats); -        mAggregator.aggregateBatteryStats(0, 0, stats -> { +        mAggregator.aggregatePowerStats(0, 0, stats -> {              assertThat(mAggregatedStatsCount++).isEqualTo(0); -            assertThat(stats.getStartTime()).isEqualTo(mStartTime); +            assertThat(stats.getStartTime()).isEqualTo(START_TIME); + +            List<AggregatedPowerStats.ClockUpdate> clockUpdates = stats.getClockUpdates(); +            assertThat(clockUpdates).hasSize(2); + +            AggregatedPowerStats.ClockUpdate clockUpdate0 = clockUpdates.get(0); +            assertThat(clockUpdate0.monotonicTime).isEqualTo(1234); +            assertThat(formatDateTime(clockUpdate0.currentTime)).isEqualTo("2008-09-23 08:00:00"); + +            AggregatedPowerStats.ClockUpdate clockUpdate1 = clockUpdates.get(1); +            assertThat(clockUpdate1.monotonicTime).isEqualTo(1234 + 3000); +            assertThat(formatDateTime(clockUpdate1.currentTime)).isEqualTo("2008-09-23 09:00:03"); +              assertThat(stats.getDuration()).isEqualTo(5000);              long[] values = new long[1]; @@ -115,40 +138,47 @@ public class PowerStatsAggregatorTest {                      TEST_POWER_COMPONENT);              assertThat(powerComponentStats.getDeviceStats(values, new int[]{ -                    PowerStatsAggregator.POWER_STATE_OTHER, -                    PowerStatsAggregator.SCREEN_STATE_ON})) +                    AggregatedPowerStatsConfig.POWER_STATE_OTHER, +                    AggregatedPowerStatsConfig.SCREEN_STATE_ON}))                      .isTrue();              assertThat(values).isEqualTo(new long[]{10000});              assertThat(powerComponentStats.getDeviceStats(values, new int[]{ -                    PowerStatsAggregator.POWER_STATE_BATTERY, -                    PowerStatsAggregator.SCREEN_STATE_OTHER})) +                    AggregatedPowerStatsConfig.POWER_STATE_BATTERY, +                    AggregatedPowerStatsConfig.SCREEN_STATE_OTHER}))                      .isTrue();              assertThat(values).isEqualTo(new long[]{20000});              assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{ -                    PowerStatsAggregator.POWER_STATE_OTHER, -                    PowerStatsAggregator.SCREEN_STATE_ON, +                    AggregatedPowerStatsConfig.POWER_STATE_OTHER, +                    AggregatedPowerStatsConfig.SCREEN_STATE_ON,                      BatteryConsumer.PROCESS_STATE_FOREGROUND}))                      .isTrue();              assertThat(values).isEqualTo(new long[]{1234});              assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{ -                    PowerStatsAggregator.POWER_STATE_BATTERY, -                    PowerStatsAggregator.SCREEN_STATE_OTHER, +                    AggregatedPowerStatsConfig.POWER_STATE_BATTERY, +                    AggregatedPowerStatsConfig.SCREEN_STATE_OTHER,                      BatteryConsumer.PROCESS_STATE_FOREGROUND}))                      .isTrue();              assertThat(values).isEqualTo(new long[]{1111});              assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{ -                    PowerStatsAggregator.POWER_STATE_BATTERY, -                    PowerStatsAggregator.SCREEN_STATE_OTHER, +                    AggregatedPowerStatsConfig.POWER_STATE_BATTERY, +                    AggregatedPowerStatsConfig.SCREEN_STATE_OTHER,                      BatteryConsumer.PROCESS_STATE_BACKGROUND}))                      .isTrue();              assertThat(values).isEqualTo(new long[]{3333});          });      } +    @NonNull +    private static CharSequence formatDateTime(long timeInMillis) { +        Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT")); +        cal.setTimeInMillis(timeInMillis); +        return DateFormat.format("yyyy-MM-dd hh:mm:ss", cal); +    } +      @Test      public void incompatiblePowerStats() {          mHistory.forceRecordAllHistory(); @@ -181,39 +211,39 @@ public class PowerStatsAggregatorTest {          mHistory.recordBatteryState(mClock.realtime, mClock.uptime, 50, /* plugged */ true); -        mAggregator.aggregateBatteryStats(0, 0, stats -> { +        mAggregator.aggregatePowerStats(0, 0, stats -> {              long[] values = new long[1];              PowerComponentAggregatedPowerStats powerComponentStats =                      stats.getPowerComponentStats(TEST_POWER_COMPONENT);              if (mAggregatedStatsCount == 0) { -                assertThat(stats.getStartTime()).isEqualTo(mStartTime); +                assertThat(stats.getStartTime()).isEqualTo(START_TIME);                  assertThat(stats.getDuration()).isEqualTo(2000);                  assertThat(powerComponentStats.getDeviceStats(values, new int[]{ -                        PowerStatsAggregator.POWER_STATE_OTHER, -                        PowerStatsAggregator.SCREEN_STATE_ON})) +                        AggregatedPowerStatsConfig.POWER_STATE_OTHER, +                        AggregatedPowerStatsConfig.SCREEN_STATE_ON}))                          .isTrue();                  assertThat(values).isEqualTo(new long[]{10000});                  assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{ -                        PowerStatsAggregator.POWER_STATE_OTHER, -                        PowerStatsAggregator.SCREEN_STATE_ON, +                        AggregatedPowerStatsConfig.POWER_STATE_OTHER, +                        AggregatedPowerStatsConfig.SCREEN_STATE_ON,                          BatteryConsumer.PROCESS_STATE_FOREGROUND}))                          .isTrue();                  assertThat(values).isEqualTo(new long[]{1234});              } else if (mAggregatedStatsCount == 1) { -                assertThat(stats.getStartTime()).isEqualTo(mStartTime + 2000); +                assertThat(stats.getStartTime()).isEqualTo(START_TIME + 2000);                  assertThat(stats.getDuration()).isEqualTo(1000);                  assertThat(powerComponentStats.getDeviceStats(values, new int[]{ -                        PowerStatsAggregator.POWER_STATE_BATTERY, -                        PowerStatsAggregator.SCREEN_STATE_ON})) +                        AggregatedPowerStatsConfig.POWER_STATE_BATTERY, +                        AggregatedPowerStatsConfig.SCREEN_STATE_ON}))                          .isTrue();                  assertThat(values).isEqualTo(new long[]{20000});                  assertThat(powerComponentStats.getUidStats(values, TEST_UID, new int[]{ -                        PowerStatsAggregator.POWER_STATE_BATTERY, -                        PowerStatsAggregator.SCREEN_STATE_ON, +                        AggregatedPowerStatsConfig.POWER_STATE_BATTERY, +                        AggregatedPowerStatsConfig.SCREEN_STATE_ON,                          BatteryConsumer.PROCESS_STATE_FOREGROUND}))                          .isTrue();                  assertThat(values).isEqualTo(new long[]{4444}); diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java new file mode 100644 index 000000000000..0e58787a6a9b --- /dev/null +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power.stats; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.os.BatteryConsumer; +import android.os.BatteryManager; +import android.os.BatteryUsageStats; +import android.os.ConditionVariable; +import android.os.Handler; +import android.os.HandlerThread; + +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.os.MonotonicClock; +import com.android.internal.os.PowerProfile; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.time.Duration; +import java.time.Instant; +import java.util.List; +import java.util.TimeZone; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +@RunWith(AndroidJUnit4.class) +public class PowerStatsSchedulerTest { +    private PowerStatsStore mPowerStatsStore; +    private Handler mHandler; +    private MockClock mClock = new MockClock(); +    private MonotonicClock mMonotonicClock = new MonotonicClock(0, mClock); +    private MockBatteryStatsImpl mBatteryStats; +    private BatteryUsageStatsProvider mBatteryUsageStatsProvider; +    private PowerStatsScheduler mPowerStatsScheduler; +    private PowerProfile mPowerProfile; +    private PowerStatsAggregator mPowerStatsAggregator; +    private AggregatedPowerStatsConfig mAggregatedPowerStatsConfig; + +    @Before +    public void setup() { +        final Context context = InstrumentationRegistry.getContext(); + +        TimeZone.setDefault(TimeZone.getTimeZone("UTC")); + +        mClock.currentTime = Instant.parse("2023-01-02T03:04:05.00Z").toEpochMilli(); +        mClock.realtime = 7654321; + +        HandlerThread bgThread = new HandlerThread("bg thread"); +        bgThread.start(); +        File systemDir = context.getCacheDir(); +        mHandler = new Handler(bgThread.getLooper()); +        mAggregatedPowerStatsConfig = new AggregatedPowerStatsConfig(); +        mPowerStatsStore = new PowerStatsStore(systemDir, mHandler, mAggregatedPowerStatsConfig); +        mPowerProfile = mock(PowerProfile.class); +        when(mPowerProfile.getAveragePower(PowerProfile.POWER_FLASHLIGHT)).thenReturn(1000000.0); +        mBatteryStats = new MockBatteryStatsImpl(mClock).setPowerProfile(mPowerProfile); +        mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mBatteryStats); +        mPowerStatsAggregator = mock(PowerStatsAggregator.class); +        mPowerStatsScheduler = new PowerStatsScheduler(context, mPowerStatsAggregator, +                TimeUnit.MINUTES.toMillis(30), TimeUnit.HOURS.toMillis(1), mPowerStatsStore, mClock, +                mMonotonicClock, mHandler, mBatteryStats, mBatteryUsageStatsProvider); +    } + +    @Test +    @SuppressWarnings("unchecked") +    public void storeAggregatePowerStats() { +        mPowerStatsStore.reset(); + +        assertThat(mPowerStatsStore.getTableOfContents()).isEmpty(); + +        mPowerStatsStore.storeAggregatedPowerStats( +                createAggregatedPowerStats(mMonotonicClock.monotonicTime(), mClock.currentTime, +                        123)); + +        long delayBeforeAggregating = TimeUnit.MINUTES.toMillis(90); +        mClock.realtime += delayBeforeAggregating; +        mClock.currentTime += delayBeforeAggregating; + +        doAnswer(invocation -> { +            // The first span is longer than 30 min, because the end time is being aligned with +            // the wall clock.  Subsequent spans should be precisely 30 minutes. +            long startTime = invocation.getArgument(0); +            long endTime = invocation.getArgument(1); +            Consumer<AggregatedPowerStats> consumer = invocation.getArgument(2); + +            long startTimeWallClock = +                    mClock.currentTime - (mMonotonicClock.monotonicTime() - startTime); +            long endTimeWallClock = +                    mClock.currentTime - (mMonotonicClock.monotonicTime() - endTime); + +            assertThat(startTime).isEqualTo(7654321 + 123); +            assertThat(endTime - startTime).isAtLeast(TimeUnit.MINUTES.toMillis(30)); +            assertThat(Instant.ofEpochMilli(endTimeWallClock)) +                    .isEqualTo(Instant.parse("2023-01-02T04:00:00Z")); + +            consumer.accept( +                    createAggregatedPowerStats(startTime, startTimeWallClock, endTime - startTime)); +            return null; +        }).doAnswer(invocation -> { +            long startTime = invocation.getArgument(0); +            long endTime = invocation.getArgument(1); +            Consumer<AggregatedPowerStats> consumer = invocation.getArgument(2); + +            long startTimeWallClock = +                    mClock.currentTime - (mMonotonicClock.monotonicTime() - startTime); +            long endTimeWallClock = +                    mClock.currentTime - (mMonotonicClock.monotonicTime() - endTime); + +            assertThat(Instant.ofEpochMilli(startTimeWallClock)) +                    .isEqualTo(Instant.parse("2023-01-02T04:00:00Z")); +            assertThat(Instant.ofEpochMilli(endTimeWallClock)) +                    .isEqualTo(Instant.parse("2023-01-02T04:30:00Z")); + +            consumer.accept( +                    createAggregatedPowerStats(startTime, startTimeWallClock, endTime - startTime)); +            return null; +        }).when(mPowerStatsAggregator).aggregatePowerStats(anyLong(), anyLong(), +                any(Consumer.class)); + +        mPowerStatsScheduler.schedulePowerStatsAggregation(); +        ConditionVariable done = new ConditionVariable(); +        mHandler.post(done::open); +        done.block(); + +        verify(mPowerStatsAggregator, times(2)) +                .aggregatePowerStats(anyLong(), anyLong(), any(Consumer.class)); + +        List<PowerStatsSpan.Metadata> contents = mPowerStatsStore.getTableOfContents(); +        assertThat(contents).hasSize(3); +        // Skip the first entry, which was placed in the store at the beginning of this test +        PowerStatsSpan.TimeFrame timeFrame1 = contents.get(1).getTimeFrames().get(0); +        PowerStatsSpan.TimeFrame timeFrame2 = contents.get(2).getTimeFrames().get(0); +        assertThat(timeFrame1.startMonotonicTime).isEqualTo(7654321 + 123); +        assertThat(timeFrame2.startMonotonicTime) +                .isEqualTo(timeFrame1.startMonotonicTime + timeFrame1.duration); +        assertThat(Instant.ofEpochMilli(timeFrame2.startTime)) +                .isEqualTo(Instant.parse("2023-01-02T04:00:00Z")); +        assertThat(Duration.ofMillis(timeFrame2.duration)).isEqualTo(Duration.ofMinutes(30)); +    } + +    private AggregatedPowerStats createAggregatedPowerStats(long monotonicTime, long currentTime, +            long duration) { +        AggregatedPowerStats stats = new AggregatedPowerStats(mAggregatedPowerStatsConfig); +        stats.addClockUpdate(monotonicTime, currentTime); +        stats.setDuration(duration); +        return stats; +    } + +    @Test +    public void storeBatteryUsageStatsOnReset() { +        mBatteryStats.forceRecordAllHistory(); +        synchronized (mBatteryStats) { +            mBatteryStats.setOnBatteryLocked(mClock.realtime, mClock.uptime, true, +                    BatteryManager.BATTERY_STATUS_DISCHARGING, 50, 0); +        } + +        mPowerStatsScheduler.start(/* schedulePeriodicPowerStatsCollection */false); + +        assertThat(mPowerStatsStore.getTableOfContents()).isEmpty(); + +        mPowerStatsScheduler.start(true); + +        synchronized (mBatteryStats) { +            mBatteryStats.noteFlashlightOnLocked(42, mClock.realtime, mClock.uptime); +        } + +        mClock.realtime += 60000; +        mClock.currentTime += 60000; + +        synchronized (mBatteryStats) { +            mBatteryStats.noteFlashlightOffLocked(42, mClock.realtime, mClock.uptime); +        } + +        mClock.realtime += 60000; +        mClock.currentTime += 60000; + +        // Battery stats reset should have the side-effect of saving accumulated battery usage stats +        synchronized (mBatteryStats) { +            mBatteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND); +        } + +        // Await completion +        ConditionVariable done = new ConditionVariable(); +        mHandler.post(done::open); +        done.block(); + +        List<PowerStatsSpan.Metadata> contents = mPowerStatsStore.getTableOfContents(); +        assertThat(contents).hasSize(1); + +        PowerStatsSpan.Metadata metadata = contents.get(0); + +        PowerStatsSpan span = mPowerStatsStore.loadPowerStatsSpan(metadata.getId(), +                BatteryUsageStatsSection.TYPE); +        assertThat(span).isNotNull(); + +        List<PowerStatsSpan.TimeFrame> timeFrames = span.getMetadata().getTimeFrames(); +        assertThat(timeFrames).hasSize(1); +        assertThat(timeFrames.get(0).startMonotonicTime).isEqualTo(7654321); +        assertThat(timeFrames.get(0).duration).isEqualTo(120000); + +        List<PowerStatsSpan.Section> sections = span.getSections(); +        assertThat(sections).hasSize(1); + +        PowerStatsSpan.Section section = sections.get(0); +        assertThat(section.getType()).isEqualTo(BatteryUsageStatsSection.TYPE); +        BatteryUsageStats bus = ((BatteryUsageStatsSection) section).getBatteryUsageStats(); +        assertThat(bus.getAggregateBatteryConsumer( +                        BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE) +                .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT)) +                .isEqualTo(60000); +    } + +    @Test +    public void alignToWallClock() { +        // Expect the aligned value to be adjusted by 1 min 30 sec - rounded to the next 15 min +        assertThat(PowerStatsScheduler.alignToWallClock(123, TimeUnit.MINUTES.toMillis(15), +                123 + TimeUnit.HOURS.toMillis(2), +                Instant.parse("2007-12-03T10:13:30.00Z").toEpochMilli())).isEqualTo( +                123 + Duration.parse("PT1M30S").toMillis()); + +        // Expect the aligned value to be adjusted by 2 min 45 sec - rounded to the next 15 min +        assertThat(PowerStatsScheduler.alignToWallClock(123, TimeUnit.MINUTES.toMillis(15), +                123 + TimeUnit.HOURS.toMillis(2), +                Instant.parse("2007-12-03T10:57:15.00Z").toEpochMilli())).isEqualTo( +                123 + Duration.parse("PT2M45S").toMillis()); + +        // Expect the aligned value to be adjusted by 15 sec - rounded to the next 1 min +        assertThat(PowerStatsScheduler.alignToWallClock(123, TimeUnit.MINUTES.toMillis(1), +                123 + TimeUnit.HOURS.toMillis(2), +                Instant.parse("2007-12-03T10:14:45.00Z").toEpochMilli())).isEqualTo( +                123 + Duration.parse("PT15S").toMillis()); + +        // Expect the aligned value to be adjusted by 1 hour 46 min 30 sec - +        // rounded to the next 3 hours +        assertThat(PowerStatsScheduler.alignToWallClock(123, TimeUnit.HOURS.toMillis(3), +                123 + TimeUnit.HOURS.toMillis(9), +                Instant.parse("2007-12-03T10:13:30.00Z").toEpochMilli())).isEqualTo( +                123 + Duration.parse("PT1H46M30S").toMillis()); +    } +} diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsStoreTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsStoreTest.java new file mode 100644 index 000000000000..d3628b5888c8 --- /dev/null +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsStoreTest.java @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power.stats; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; + +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import com.android.modules.utils.TypedXmlPullParser; +import com.android.modules.utils.TypedXmlSerializer; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +@RunWith(AndroidJUnit4.class) +@SuppressWarnings("GuardedBy") +public class PowerStatsStoreTest { +    private static final long MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES = 2 * 1024; + +    private PowerStatsStore mPowerStatsStore; +    private File mStoreDirectory; + +    @Before +    public void setup() { +        Context context = InstrumentationRegistry.getContext(); + +        mStoreDirectory = new File(context.getCacheDir(), "PowerStatsStoreTest"); +        clearDirectory(mStoreDirectory); + +        mPowerStatsStore = new PowerStatsStore(mStoreDirectory, +                MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES, +                new TestHandler(), +                (sectionType, parser) -> { +                    if (sectionType.equals(TestSection.TYPE)) { +                        return TestSection.readXml(parser); +                    } +                    return null; +                }); +    } + +    @Test +    public void garbageCollectOldSpans() throws Exception { +        int spanSize = 500; +        final int numberOfSnaps = +                (int) (MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES / spanSize); +        for (int i = 0; i < numberOfSnaps + 2; i++) { +            PowerStatsSpan span = new PowerStatsSpan(i); +            span.addSection(new TestSection(i, spanSize)); +            mPowerStatsStore.storePowerStatsSpan(span); +        } + +        assertThat(getDirectorySize(mStoreDirectory)) +                .isAtMost(MAX_BATTERY_STATS_SNAPSHOT_STORAGE_BYTES); + +        List<PowerStatsSpan.Metadata> toc = mPowerStatsStore.getTableOfContents(); +        assertThat(toc.size()).isLessThan(numberOfSnaps); +        int minPreservedSpanId = numberOfSnaps - toc.size(); +        for (PowerStatsSpan.Metadata metadata : toc) { +            assertThat(metadata.getId()).isAtLeast(minPreservedSpanId); +        } +    } + +    @Test +    public void reset() throws Exception { +        for (int i = 0; i < 3; i++) { +            PowerStatsSpan span = new PowerStatsSpan(i); +            span.addSection(new TestSection(i, 42)); +            mPowerStatsStore.storePowerStatsSpan(span); +        } + +        assertThat(getDirectorySize(mStoreDirectory)).isNotEqualTo(0); + +        mPowerStatsStore.reset(); + +        assertThat(getDirectorySize(mStoreDirectory)).isEqualTo(0); +    } + +    private void clearDirectory(File dir) { +        if (dir.exists()) { +            for (File child : dir.listFiles()) { +                if (child.isDirectory()) { +                    clearDirectory(child); +                } +                child.delete(); +            } +        } +    } + +    private long getDirectorySize(File dir) { +        long size = 0; +        if (dir.exists()) { +            for (File child : dir.listFiles()) { +                if (child.isDirectory()) { +                    size += getDirectorySize(child); +                } else { +                    size += child.length(); +                } +            } +        } +        return size; +    } + +    private static class TestSection extends PowerStatsSpan.Section { +        public static final String TYPE = "much-text"; + +        private final int mSize; +        private final int mValue; + +        TestSection(int value, int size) { +            super(TYPE); +            mSize = size; +            mValue = value; +        } + +        @Override +        void write(TypedXmlSerializer serializer) throws IOException { +            StringBuilder sb = new StringBuilder(); +            for (int i = 0; i < mSize; i++) { +                sb.append("X"); +            } +            serializer.startTag(null, "much-text"); +            serializer.attributeInt(null, "value", mValue); +            serializer.text(sb.toString()); +            serializer.endTag(null, "much-text"); +        } + +        public static TestSection readXml(TypedXmlPullParser parser) throws XmlPullParserException { +            TestSection section = null; +            int eventType = parser.getEventType(); +            while (eventType != XmlPullParser.END_DOCUMENT +                   && !(eventType == XmlPullParser.END_TAG +                        && parser.getName().equals("much-text"))) { +                if (eventType == XmlPullParser.START_TAG && parser.getName().equals("much-text")) { +                    section = new TestSection(parser.getAttributeInt(null, "value"), 0); +                } +            } +            return section; +        } +    } + +    private static class TestHandler extends Handler { +        TestHandler() { +            super(Looper.getMainLooper()); +        } + +        @Override +        public boolean sendMessageAtTime(Message msg, long uptimeMillis) { +            msg.getCallback().run(); +            return true; +        } +    } +} diff --git a/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java b/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java index 2ffe4aacda73..df46054f0f6f 100644 --- a/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java +++ b/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java @@ -1079,7 +1079,7 @@ public class PowerStatsServiceTest {          GetSupportedPowerMonitorsResult result = new GetSupportedPowerMonitorsResult();          mService.getSupportedPowerMonitorsImpl(result);          assertThat(result.powerMonitors).isNotNull(); -        assertThat(Arrays.stream(result.powerMonitors).map(pm -> pm.name).toList()) +        assertThat(Arrays.stream(result.powerMonitors).map(PowerMonitor::getName).toList())                  .containsAtLeast(                          "energyconsumer0",                          "BLUETOOTH/1", @@ -1130,7 +1130,7 @@ public class PowerStatsServiceTest {          mService.getSupportedPowerMonitorsImpl(supportedPowerMonitorsResult);          Map<String, PowerMonitor> map =                  Arrays.stream(supportedPowerMonitorsResult.powerMonitors) -                        .collect(Collectors.toMap(pm -> pm.name, pm -> pm)); +                        .collect(Collectors.toMap(PowerMonitor::getName, pm -> pm));          PowerMonitor consumer1 = map.get("energyconsumer0");          PowerMonitor consumer2 = map.get("BLUETOOTH/1");          PowerMonitor measurement1 = map.get("[channelname0]:channelsubsystem0"); @@ -1196,6 +1196,6 @@ public class PowerStatsServiceTest {          supportedPowerMonitorsResult = new GetSupportedPowerMonitorsResult();          mService.getSupportedPowerMonitorsImpl(supportedPowerMonitorsResult);          assertThat(Arrays.stream(supportedPowerMonitorsResult.powerMonitors) -                .map(pm -> pm.name).toList()).contains("energyconsumer0"); +                .map(PowerMonitor::getName).toList()).contains("energyconsumer0");      }  } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java index 49f22eca5643..cf315a4e0b9a 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java @@ -33,6 +33,7 @@ import static org.mockito.ArgumentMatchers.anyBoolean;  import static org.mockito.ArgumentMatchers.anyInt;  import static org.mockito.ArgumentMatchers.eq;  import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.doAnswer;  import static org.mockito.Mockito.doCallRealMethod;  import static org.mockito.Mockito.doThrow;  import static org.mockito.Mockito.mock; @@ -53,12 +54,18 @@ import android.content.pm.ApplicationInfo;  import android.content.pm.PackageManager;  import android.content.pm.ResolveInfo;  import android.content.pm.ServiceInfo; +import android.content.res.XmlResourceParser;  import android.graphics.drawable.Icon;  import android.hardware.display.DisplayManagerGlobal; +import android.net.Uri;  import android.os.Bundle;  import android.os.IBinder;  import android.os.LocaleList;  import android.os.UserHandle; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider;  import android.provider.Settings;  import android.testing.TestableContext;  import android.view.Display; @@ -93,8 +100,13 @@ import org.mockito.Captor;  import org.mockito.Mock;  import org.mockito.Mockito;  import org.mockito.MockitoAnnotations; +import org.mockito.stubbing.Answer;  import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference;  /**   * APCT tests for {@link AccessibilityManagerService}. @@ -104,6 +116,10 @@ public class AccessibilityManagerServiceTest {      public final A11yTestableContext mTestableContext = new A11yTestableContext(              ApplicationProvider.getApplicationContext()); +    @Rule +    public final CheckFlagsRule mCheckFlagsRule = +            DeviceFlagsValueProvider.createCheckFlagsRule(); +      private static final int ACTION_ID = 20;      private static final String LABEL = "label";      private static final String INTENT_ACTION = "TESTACTION"; @@ -204,6 +220,8 @@ public class AccessibilityManagerServiceTest {                  mA11yms.getCurrentUserIdLocked());          when(mMockServiceInfo.getResolveInfo()).thenReturn(mMockResolveInfo);          mMockResolveInfo.serviceInfo = mock(ServiceInfo.class); +        mMockResolveInfo.serviceInfo.packageName = "packageName"; +        mMockResolveInfo.serviceInfo.name = "className";          mMockResolveInfo.serviceInfo.applicationInfo = mock(ApplicationInfo.class);          when(mMockBinder.queryLocalInterface(any())).thenReturn(mMockServiceClient); @@ -581,6 +599,73 @@ public class AccessibilityManagerServiceTest {                  ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString());      } +    @Test +    @RequiresFlagsDisabled(Flags.FLAG_SCAN_PACKAGES_WITHOUT_LOCK) +    // Test old behavior to validate lock detection for the old (locked access) case. +    public void testPackageMonitorScanPackages_scansWhileHoldingLock() { +        setupAccessibilityServiceConnection(0); +        final AtomicReference<Set<Boolean>> lockState = collectLockStateWhilePackageScanning(); +        when(mMockPackageManager.queryIntentServicesAsUser(any(), anyInt(), anyInt())) +                .thenReturn(List.of(mMockResolveInfo)); +        when(mMockSecurityPolicy.canRegisterService(any())).thenReturn(true); + +        final Intent packageIntent = new Intent(Intent.ACTION_PACKAGE_ADDED); +        packageIntent.setData(Uri.parse("test://package")); +        packageIntent.putExtra(Intent.EXTRA_USER_HANDLE, mA11yms.getCurrentUserIdLocked()); +        packageIntent.putExtra(Intent.EXTRA_REPLACING, true); +        mA11yms.getPackageMonitor().doHandlePackageEvent(packageIntent); + +        assertThat(lockState.get()).containsExactly(true); +    } + +    @Test +    @RequiresFlagsEnabled(Flags.FLAG_SCAN_PACKAGES_WITHOUT_LOCK) +    public void testPackageMonitorScanPackages_scansWithoutHoldingLock() { +        setupAccessibilityServiceConnection(0); +        final AtomicReference<Set<Boolean>> lockState = collectLockStateWhilePackageScanning(); +        when(mMockPackageManager.queryIntentServicesAsUser(any(), anyInt(), anyInt())) +                .thenReturn(List.of(mMockResolveInfo)); +        when(mMockSecurityPolicy.canRegisterService(any())).thenReturn(true); + +        final Intent packageIntent = new Intent(Intent.ACTION_PACKAGE_ADDED); +        packageIntent.setData(Uri.parse("test://package")); +        packageIntent.putExtra(Intent.EXTRA_USER_HANDLE, mA11yms.getCurrentUserIdLocked()); +        packageIntent.putExtra(Intent.EXTRA_REPLACING, true); +        mA11yms.getPackageMonitor().doHandlePackageEvent(packageIntent); + +        assertThat(lockState.get()).containsExactly(false); +    } + +    @Test +    @RequiresFlagsEnabled(Flags.FLAG_SCAN_PACKAGES_WITHOUT_LOCK) +    public void testSwitchUserScanPackages_scansWithoutHoldingLock() { +        setupAccessibilityServiceConnection(0); +        final AtomicReference<Set<Boolean>> lockState = collectLockStateWhilePackageScanning(); +        when(mMockPackageManager.queryIntentServicesAsUser(any(), anyInt(), anyInt())) +                .thenReturn(List.of(mMockResolveInfo)); +        when(mMockSecurityPolicy.canRegisterService(any())).thenReturn(true); + +        mA11yms.switchUser(mA11yms.getCurrentUserIdLocked() + 1); + +        assertThat(lockState.get()).containsExactly(false); +    } + +    // Single package intents can trigger multiple PackageMonitor callbacks. +    // Collect the state of the lock in a set, since tests only care if calls +    // were all locked or all unlocked. +    private AtomicReference<Set<Boolean>> collectLockStateWhilePackageScanning() { +        final AtomicReference<Set<Boolean>> lockState = +                new AtomicReference<>(new HashSet<Boolean>()); +        doAnswer((Answer<XmlResourceParser>) invocation -> { +            lockState.updateAndGet(set -> { +                set.add(mA11yms.unsafeIsLockHeld()); +                return set; +            }); +            return null; +        }).when(mMockResolveInfo.serviceInfo).loadXmlMetaData(any(), any()); +        return lockState; +    } +      private void mockManageAccessibilityGranted(TestableContext context) {          context.getTestablePermissions().setPermission(Manifest.permission.MANAGE_ACCESSIBILITY,                  PackageManager.PERMISSION_GRANTED); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java index 1cd61e90126e..efcdbd488a39 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java @@ -44,6 +44,10 @@ import android.content.Context;  import android.graphics.PointF;  import android.os.Looper;  import android.os.SystemClock; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider;  import android.testing.DexmakerShareClassLoaderRule;  import android.view.InputDevice;  import android.view.MotionEvent; @@ -56,6 +60,7 @@ import androidx.test.runner.AndroidJUnit4;  import com.android.server.accessibility.AccessibilityManagerService;  import com.android.server.accessibility.AccessibilityTraceManager;  import com.android.server.accessibility.EventStreamTransformation; +import com.android.server.accessibility.Flags;  import com.android.server.accessibility.utils.GestureLogParser;  import com.android.server.testutils.OffsettableClock; @@ -76,6 +81,7 @@ import java.nio.charset.Charset;  import java.util.ArrayList;  import java.util.List; +  @RunWith(AndroidJUnit4.class)  public class TouchExplorerTest { @@ -119,6 +125,9 @@ public class TouchExplorerTest {      public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =              new DexmakerShareClassLoaderRule(); +    @Rule +    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); +      /**       * {@link TouchExplorer#sendDownForAllNotInjectedPointers} injecting events with the same object       * is resulting {@link ArgumentCaptor} to capture events with last state. Before implementation @@ -161,11 +170,16 @@ public class TouchExplorerTest {          goFromStateClearTo(STATE_TOUCH_EXPLORING_1FINGER);          // Wait for transiting to touch exploring state.          mHandler.fastForward(2 * USER_INTENT_TIMEOUT); -        moveEachPointers(mLastEvent, p(10, 10)); -        send(mLastEvent); +        assertState(STATE_TOUCH_EXPLORING); +        // Manually construct the next move event. Using moveEachPointers() will batch the move +        // event which produces zero movement for some reason. +        float[] x = new float[1]; +        float[] y = new float[1]; +        x[0] = mLastEvent.getX(0) + mTouchSlop; +        y[0] = mLastEvent.getY(0) + mTouchSlop; +        send(manyPointerEvent(ACTION_MOVE, x, y));          goToStateClearFrom(STATE_TOUCH_EXPLORING_1FINGER);          assertCapturedEvents(ACTION_HOVER_ENTER, ACTION_HOVER_MOVE, ACTION_HOVER_EXIT); -        assertState(STATE_TOUCH_EXPLORING);      }      /** @@ -173,7 +187,34 @@ public class TouchExplorerTest {       * change the coordinates.       */      @Test -    public void testOneFingerMoveWithExtraMoveEvents() { +    @RequiresFlagsEnabled(Flags.FLAG_REDUCE_TOUCH_EXPLORATION_SENSITIVITY) +    public void testOneFingerMoveWithExtraMoveEvents_generatesOneMoveEvent() { +        goFromStateClearTo(STATE_TOUCH_EXPLORING_1FINGER); +        // Inject a set of move events that have the same coordinates as the down event. +        moveEachPointers(mLastEvent, p(0, 0)); +        send(mLastEvent); +        // Wait for transition to touch exploring state. +        mHandler.fastForward(2 * USER_INTENT_TIMEOUT); +        // Now move for real. +        moveAtLeastTouchSlop(mLastEvent); +        send(mLastEvent); +        // One more move event with no change. +        moveEachPointers(mLastEvent, p(0, 0)); +        send(mLastEvent); +        goToStateClearFrom(STATE_TOUCH_EXPLORING_1FINGER); +        assertCapturedEvents( +                ACTION_HOVER_ENTER, +                ACTION_HOVER_MOVE, +                ACTION_HOVER_EXIT); +    } + +    /** +     * Test the case where ACTION_DOWN is followed by a number of ACTION_MOVE events that do not +     * change the coordinates. +     */ +    @Test +    @RequiresFlagsDisabled(Flags.FLAG_REDUCE_TOUCH_EXPLORATION_SENSITIVITY) +    public void testOneFingerMoveWithExtraMoveEvents_generatesThreeMoveEvent() {          goFromStateClearTo(STATE_TOUCH_EXPLORING_1FINGER);          // Inject a set of move events that have the same coordinates as the down event.          moveEachPointers(mLastEvent, p(0, 0)); @@ -181,7 +222,7 @@ public class TouchExplorerTest {          // Wait for transition to touch exploring state.          mHandler.fastForward(2 * USER_INTENT_TIMEOUT);          // Now move for real. -        moveEachPointers(mLastEvent, p(10, 10)); +        moveAtLeastTouchSlop(mLastEvent);          send(mLastEvent);          // One more move event with no change.          moveEachPointers(mLastEvent, p(0, 0)); @@ -242,7 +283,7 @@ public class TouchExplorerTest {          moveEachPointers(mLastEvent, p(0, 0), p(0, 0));          send(mLastEvent);          // Now move for real. -        moveEachPointers(mLastEvent, p(10, 10), p(10, 10)); +        moveEachPointers(mLastEvent, p(mTouchSlop, mTouchSlop), p(mTouchSlop, mTouchSlop));          send(mLastEvent);          goToStateClearFrom(STATE_DRAGGING_2FINGERS);          assertCapturedEvents(ACTION_DOWN, ACTION_MOVE, ACTION_MOVE, ACTION_MOVE, ACTION_UP); @@ -251,7 +292,7 @@ public class TouchExplorerTest {      @Test      public void testUpEvent_OneFingerMove_clearStateAndInjectHoverEvents() {          goFromStateClearTo(STATE_TOUCH_EXPLORING_1FINGER); -        moveEachPointers(mLastEvent, p(10, 10)); +        moveAtLeastTouchSlop(mLastEvent);          send(mLastEvent);          // Wait 10 ms to make sure that hover enter and exit are not scheduled for the same moment.          mHandler.fastForward(10); @@ -277,7 +318,7 @@ public class TouchExplorerTest {          // Wait for the finger moving to the second view.          mHandler.fastForward(oneThirdUserIntentTimeout); -        moveEachPointers(mLastEvent, p(10, 10)); +        moveAtLeastTouchSlop(mLastEvent);          send(mLastEvent);          // Wait for the finger lifting from the second view. @@ -402,7 +443,6 @@ public class TouchExplorerTest {          // Manually construct the next move event. Using moveEachPointers() will batch the move          // event onto the pointer up event which will mean that the move event still has a pointer          // count of 3. -        // Todo: refactor to avoid using batching as there is no special reason to do it that way.          float[] x = new float[2];          float[] y = new float[2];          x[0] = mLastEvent.getX(0) + 100; @@ -734,6 +774,9 @@ public class TouchExplorerTest {          }      } +    private void moveAtLeastTouchSlop(MotionEvent event) { +        moveEachPointers(event, p(2 * mTouchSlop, 0)); +    }      /**       * A {@link android.os.Handler} that doesn't process messages until {@link #fastForward(int)} is       * invoked. diff --git a/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java index 988cd818ca28..feb6bd930bf3 100644 --- a/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/am/BatteryStatsServiceTest.java @@ -16,6 +16,8 @@  package com.android.server.am; +import static com.google.common.truth.Truth.assertThat; +  import static org.junit.Assert.assertTrue;  import android.content.Context; @@ -34,6 +36,7 @@ import org.junit.Ignore;  import org.junit.Test;  import org.junit.runner.RunWith; +import java.io.File;  import java.util.concurrent.CountDownLatch;  import java.util.concurrent.TimeUnit;  import java.util.concurrent.atomic.AtomicBoolean; @@ -49,8 +52,9 @@ public final class BatteryStatsServiceTest {          final Context context = InstrumentationRegistry.getContext();          mBgThread = new HandlerThread("bg thread");          mBgThread.start(); -        mBatteryStatsService = new BatteryStatsService(context, -                context.getCacheDir(), new Handler(mBgThread.getLooper())); +        File systemDir = context.getCacheDir(); +        Handler handler = new Handler(mBgThread.getLooper()); +        mBatteryStatsService = new BatteryStatsService(context, systemDir, handler);      }      @After @@ -121,4 +125,14 @@ public final class BatteryStatsServiceTest {              waitThread.join(1000);          }      } + +    @Test +    public void testSavingStatsdAtomPullTimestamp() { +        mBatteryStatsService.setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(1234); +        assertThat(mBatteryStatsService.getLastBatteryUsageStatsBeforeResetAtomPullTimestamp()) +                .isEqualTo(1234); +        mBatteryStatsService.setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(5478); +        assertThat(mBatteryStatsService.getLastBatteryUsageStatsBeforeResetAtomPullTimestamp()) +                .isEqualTo(5478); +    }  } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java index 9e5a0479ea70..3a3dd6ea2746 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java @@ -36,6 +36,7 @@ import static org.mockito.Mockito.when;  import android.app.ActivityManager;  import android.app.ActivityTaskManager;  import android.content.ComponentName; +import android.hardware.biometrics.BiometricConstants;  import android.hardware.biometrics.BiometricManager;  import android.hardware.biometrics.common.AuthenticateReason;  import android.hardware.biometrics.common.ICancellationSignal; @@ -59,6 +60,7 @@ import com.android.server.biometrics.log.OperationContextExt;  import com.android.server.biometrics.sensors.AuthSessionCoordinator;  import com.android.server.biometrics.sensors.ClientMonitorCallback;  import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; +import com.android.server.biometrics.sensors.LockoutTracker;  import com.android.server.biometrics.sensors.face.UsageStats;  import org.junit.Before; @@ -103,7 +105,7 @@ public class FaceAuthenticationClientTest {      @Mock      private ClientMonitorCallback mCallback;      @Mock -    private Sensor.HalSessionCallback mHalSessionCallback; +    private AidlResponseHandler mAidlResponseHandler;      @Mock      private ActivityTaskManager mActivityTaskManager;      @Mock @@ -112,6 +114,8 @@ public class FaceAuthenticationClientTest {      private AuthSessionCoordinator mAuthSessionCoordinator;      @Mock      private BiometricManager mBiometricManager; +    @Mock +    private LockoutTracker mLockoutTracker;      @Captor      private ArgumentCaptor<OperationContextExt> mOperationContextCaptor;      @Captor @@ -236,27 +240,63 @@ public class FaceAuthenticationClientTest {          verify(mCallback).onClientFinished(client, true);      } +    @Test +    public void authWithNoLockout() throws RemoteException { +        when(mLockoutTracker.getLockoutModeForUser(anyInt())).thenReturn( +                LockoutTracker.LOCKOUT_NONE); + +        final FaceAuthenticationClient client = createClientWithLockoutTracker(mLockoutTracker); +        client.start(mCallback); + +        verify(mHal).authenticate(OP_ID); +    } + +    @Test +    public void authWithLockout() throws RemoteException { +        when(mLockoutTracker.getLockoutModeForUser(anyInt())).thenReturn( +                LockoutTracker.LOCKOUT_PERMANENT); + +        final FaceAuthenticationClient client = createClientWithLockoutTracker(mLockoutTracker); +        client.start(mCallback); + +        verify(mClientMonitorCallbackConverter).onError(anyInt(), anyInt(), +                eq(BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT), anyInt()); +        verify(mHal, never()).authenticate(anyInt()); +    } +      private FaceAuthenticationClient createClient() throws RemoteException {          return createClient(2 /* version */, mClientMonitorCallbackConverter, -                false /* allowBackgroundAuthentication */); +                false /* allowBackgroundAuthentication */, +                null /* lockoutTracker */);      }      private FaceAuthenticationClient createClientWithNullListener() throws RemoteException {          return createClient(2 /* version */, null /* listener */, -                true /* allowBackgroundAuthentication */); +                true /* allowBackgroundAuthentication */, +                null /* lockoutTracker */);      }      private FaceAuthenticationClient createClient(int version) throws RemoteException {          return createClient(version, mClientMonitorCallbackConverter, -                false /* allowBackgroundAuthentication */); +                false /* allowBackgroundAuthentication */, +                null /* lockoutTracker */); +    } + +    private FaceAuthenticationClient createClientWithLockoutTracker(LockoutTracker lockoutTracker) +            throws RemoteException { +        return createClient(0 /* version */, +                mClientMonitorCallbackConverter, +                true /* allowBackgroundAuthentication */, +                lockoutTracker);      }      private FaceAuthenticationClient createClient(int version,              ClientMonitorCallbackConverter listener, -            boolean allowBackgroundAuthentication) throws RemoteException { +            boolean allowBackgroundAuthentication, +            LockoutTracker lockoutTracker) throws RemoteException {          when(mHal.getInterfaceVersion()).thenReturn(version); -        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback); +        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler);          final FaceAuthenticateOptions options = new FaceAuthenticateOptions.Builder()                  .setOpPackageName("test-owner")                  .setUserId(USER_ID) @@ -270,7 +310,7 @@ public class FaceAuthenticationClientTest {                  false /* restricted */, options, 4 /* cookie */,                  false /* requireConfirmation */,                  mBiometricLogger, mBiometricContext, true /* isStrongBiometric */, -                mUsageStats, null /* mLockoutCache */, allowBackgroundAuthentication, +                mUsageStats, lockoutTracker, allowBackgroundAuthentication,                  null /* sensorPrivacyManager */, 0 /* biometricStrength */) {              @Override              protected ActivityTaskManager getActivityTaskManager() { diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java index ade3e8275157..fbf0e13c2ac9 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java @@ -87,7 +87,7 @@ public class FaceDetectClientTest {      @Mock      private ClientMonitorCallback mCallback;      @Mock -    private Sensor.HalSessionCallback mHalSessionCallback; +    private AidlResponseHandler mAidlResponseHandler;      @Captor      private ArgumentCaptor<OperationContextExt> mOperationContextCaptor;      @Captor @@ -170,7 +170,7 @@ public class FaceDetectClientTest {      private FaceDetectClient createClient(int version) throws RemoteException {          when(mHal.getInterfaceVersion()).thenReturn(version); -        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback); +        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler);          return new FaceDetectClient(mContext, () -> aidl, mToken,                  99 /* requestId */, mClientMonitorCallbackConverter,                  new FaceAuthenticateOptions.Builder() diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java index 54d116f07805..128f3149e6d4 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java @@ -83,7 +83,7 @@ public class FaceEnrollClientTest {      @Mock      private ClientMonitorCallback mCallback;      @Mock -    private Sensor.HalSessionCallback mHalSessionCallback; +    private AidlResponseHandler mAidlResponseHandler;      @Captor      private ArgumentCaptor<OperationContextExt> mOperationContextCaptor;      @Captor @@ -150,7 +150,7 @@ public class FaceEnrollClientTest {      private FaceEnrollClient createClient(int version) throws RemoteException {          when(mHal.getInterfaceVersion()).thenReturn(version); -        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback); +        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler);          return new FaceEnrollClient(mContext, () -> aidl, mToken, mClientMonitorCallbackConverter,                  USER_ID, HAT, "com.foo.bar", 44 /* requestId */,                  mUtils, new int[0] /* disabledFeatures */, 6 /* timeoutSec */, diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClientTest.java new file mode 100644 index 000000000000..c8bfaa90d863 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGenerateChallengeClientTest.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.face.aidl; + +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.biometrics.face.ISession; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.ClientMonitorCallback; +import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@Presubmit +@SmallTest +public class FaceGenerateChallengeClientTest { +    private static final String TAG = "FaceGenerateChallengeClientTest"; +    private static final int USER_ID = 2; +    private static final int SENSOR_ID = 4; +    private static final long CHALLENGE = 200; + +    @Rule +    public final MockitoRule mockito = MockitoJUnit.rule(); + +    @Mock +    private AidlSession mAidlSession; +    @Mock +    private ISession mSession; +    @Mock +    private IBinder mToken; +    @Mock +    private ClientMonitorCallbackConverter mListener; +    @Mock +    private ClientMonitorCallback mCallback; +    @Mock +    private Context mContext; +    @Mock +    private BiometricLogger mBiometricLogger; +    @Mock +    private BiometricContext mBiometricContext; + +    private FaceGenerateChallengeClient mClient; + +    @Before +    public void setUp() throws RemoteException { +        when(mAidlSession.getSession()).thenReturn(mSession); +        doAnswer(invocation -> { +            mClient.onChallengeGenerated(SENSOR_ID, USER_ID, CHALLENGE); +            return null; +        }).when(mSession).generateChallenge(); +    } + +    @Test +    public void generateChallenge() throws RemoteException { +        createClient(mListener); +        mClient.start(mCallback); + +        verify(mListener).onChallengeGenerated(SENSOR_ID, USER_ID, CHALLENGE); +        verify(mCallback).onClientFinished(mClient, true); +    } + +    @Test +    public void generateChallenge_nullListener() { +        createClient(null); +        mClient.start(mCallback); + +        verify(mCallback).onClientFinished(mClient, false); +    } + +    private void createClient(ClientMonitorCallbackConverter listener) { +        mClient = new FaceGenerateChallengeClient(mContext, () -> mAidlSession, mToken, listener, +                USER_ID, TAG, SENSOR_ID, mBiometricLogger, mBiometricContext); +    } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClientTest.java new file mode 100644 index 000000000000..9d0c84edb366 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceGetFeatureClientTest.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.face.aidl; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.hardware.biometrics.BiometricFaceConstants; +import android.hardware.biometrics.face.ISession; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.test.suitebuilder.annotation.SmallTest; +import android.testing.TestableContext; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.ClientMonitorCallback; +import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.List; + +@Presubmit +@SmallTest +public class FaceGetFeatureClientTest { +    private static final String TAG = "FaceGetFeatureClientTest"; +    private static final int USER_ID = 2; +    private static final int SENSOR_ID = 4; + +    @Rule +    public final MockitoRule mockito = MockitoJUnit.rule(); + +    @Mock +    private AidlSession mAidlSession; +    @Mock +    private ISession mSession; +    @Mock +    private IBinder mToken; +    @Mock +    private ClientMonitorCallbackConverter mListener; +    @Mock +    private ClientMonitorCallback mCallback; +    TestableContext mContext = new TestableContext( +            InstrumentationRegistry.getInstrumentation().getContext()); +    @Mock +    private BiometricLogger mBiometricLogger; +    @Mock +    private BiometricContext mBiometricContext; + +    private final int mFeature = BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION; +    private FaceGetFeatureClient mClient; + +    @Before +    public void setUp() throws RemoteException { +        mClient = new FaceGetFeatureClient(mContext, () -> mAidlSession, mToken, mListener, +                USER_ID, TAG, SENSOR_ID, mBiometricLogger, mBiometricContext, mFeature); + +        when(mAidlSession.getSession()).thenReturn(mSession); +        doAnswer(invocation -> { +            mClient.onFeatureGet(true, new byte[]{ +                    AidlConversionUtils.convertFrameworkToAidlFeature(mFeature)}); +            return null; +        }).when(mSession).getFeatures(); +    } + +    @Test +    public void getFeature() throws RemoteException { +        ArgumentCaptor<int[]> featuresToSend = ArgumentCaptor.forClass(int[].class); +        ArgumentCaptor<boolean[]> featureState = ArgumentCaptor.forClass(boolean[].class); +        mClient.start(mCallback); + +        verify(mListener).onFeatureGet(eq(true), featuresToSend.capture(), +                featureState.capture()); +        assertThat(featuresToSend.getValue()).asList().containsExactlyElementsIn(List.of(mFeature)); +        assertThat(featureState.getValue()).asList().containsExactlyElementsIn(List.of(true)); +    } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClientTest.java new file mode 100644 index 000000000000..1b4c01723027 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClientTest.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.face.aidl; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.biometrics.BiometricAuthenticator; +import android.hardware.biometrics.face.ISession; +import android.hardware.face.Face; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.test.suitebuilder.annotation.SmallTest; + +import androidx.annotation.NonNull; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.BiometricUtils; +import com.android.server.biometrics.sensors.ClientMonitorCallback; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Presubmit +@SmallTest +public class FaceInternalCleanupClientTest { +    private static final String TAG = "FaceInternalCleanupClientTest"; +    private static final int USER_ID = 2; +    private static final int SENSOR_ID = 4; + +    @Rule +    public final MockitoRule mockito = MockitoJUnit.rule(); + +    @Mock +    private AidlSession mAidlSession; +    @Mock +    private ISession mSession; +    @Mock +    private ClientMonitorCallback mCallback; +    @Mock +    Context mContext; +    @Mock +    private BiometricLogger mBiometricLogger; +    @Mock +    private BiometricContext mBiometricContext; +    @Mock +    private BiometricUtils<Face> mBiometricUtils; +    @Mock +    private Map<Integer, Long> mAuthenticatorIds; + +    private final List<Face> mEnrolledList = new ArrayList<>(); +    private final int mBiometricId = 1; +    private final Face mFace = new Face("face", mBiometricId, 1 /* deviceId */); +    private FaceInternalCleanupClient mClient; +    private List<Integer> mAddedIds; + +    @Before +    public void setUp() throws RemoteException { +        when(mAidlSession.getSession()).thenReturn(mSession); + +        mEnrolledList.add(mFace); +        mAddedIds = new ArrayList<>(); +        mClient = new FaceInternalCleanupClient(mContext, () -> mAidlSession, USER_ID, TAG, +                SENSOR_ID, mBiometricLogger, mBiometricContext, mBiometricUtils, +                mAuthenticatorIds) { +            @Override +            protected void onAddUnknownTemplate(int userId, +                    @NonNull BiometricAuthenticator.Identifier identifier) { +                mAddedIds.add(identifier.getBiometricId()); +            } +        }; +    } + +    @Test +    public void removesUnknownTemplate() throws Exception { +        final List<Face> templates = List.of( +                new Face("one", 1, 1), +                new Face("two", 2, 1) +        ); +        mClient.start(mCallback); +        for (int i = templates.size() - 1; i >= 0; i--) { +            mClient.getCurrentEnumerateClient().onEnumerationResult(templates.get(i), i); +        } +        for (int i = templates.size() - 1; i >= 0; i--) { +            mClient.getCurrentRemoveClient().onRemoved(templates.get(i), 0); +        } + +        assertThat(mAddedIds).isEmpty(); +        final ArgumentCaptor<int[]> captor = ArgumentCaptor.forClass(int[].class); + +        verify(mSession, times(2)).removeEnrollments(captor.capture()); +        assertThat(captor.getAllValues().stream() +                .flatMap(x -> Arrays.stream(x).boxed()) +                .collect(Collectors.toList())) +                .containsExactly(1, 2); +        verify(mCallback).onClientFinished(eq(mClient), eq(true)); +    } + +    @Test +    public void addsUnknownTemplateWhenVirtualIsEnabled() throws Exception { +        mClient.setFavorHalEnrollments(); +        final List<Face> templates = List.of( +                new Face("one", 1, 1), +                new Face("two", 2, 1) +        ); +        mClient.start(mCallback); +        for (int i = templates.size() - 1; i >= 0; i--) { +            mClient.getCurrentEnumerateClient().onEnumerationResult(templates.get(i), i); +        } + +        assertThat(mAddedIds).containsExactly(1, 2); +        verify(mSession, never()).removeEnrollments(any()); +        verify(mCallback).onClientFinished(eq(mClient), eq(true)); +    } + +    @Test +    public void cleanupUnknownHalTemplatesAfterEnumerationWhenVirtualIsDisabled() { +        final List<Face> templates = List.of( +                new Face("one", 1, 1), +                new Face("two", 2, 1), +                new Face("three", 3, 1) +        ); +        mClient.start(mCallback); +        for (int i = templates.size() - 1; i >= 0; i--) { +            mClient.getCurrentEnumerateClient().onEnumerationResult(templates.get(i), i); +        } + +        // The first template is removed after enumeration +        assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(2); + +        // Simulate finishing the removal of the first template. +        // |remaining| is 0 because one FaceRemovalClient is associated with only one +        // biometrics ID. +        mClient.getCurrentRemoveClient().onRemoved(templates.get(0), 0); + +        assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(1); + +        // Simulate finishing the removal of the second template. +        mClient.getCurrentRemoveClient().onRemoved(templates.get(1), 0); + +        assertThat(mClient.getUnknownHALTemplates()).isEmpty(); +    } + +    @Test +    public void noUnknownTemplates() throws RemoteException { +        mClient.start(mCallback); +        mClient.getCurrentEnumerateClient().onEnumerationResult(null, 0); + +        verify(mSession).enumerateEnrollments(); +        assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0); +        verify(mSession, never()).removeEnrollments(any()); +        verify(mCallback).onClientFinished(mClient, true); +    } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClientTest.java new file mode 100644 index 000000000000..8d74fd1d56be --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClientTest.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.face.aidl; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.biometrics.face.ISession; +import android.hardware.face.Face; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.BiometricUtils; +import com.android.server.biometrics.sensors.ClientMonitorCallback; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.ArrayList; +import java.util.List; + +@Presubmit +@SmallTest +public class FaceInternalEnumerateClientTest { +    private static final String TAG = "FaceInternalEnumerateClientTest"; +    private static final int USER_ID = 2; +    private static final int SENSOR_ID = 4; + +    @Rule +    public final MockitoRule mockito = MockitoJUnit.rule(); + +    @Mock +    private AidlSession mAidlSession; +    @Mock +    private ISession mSession; +    @Mock +    private IBinder mToken; +    @Mock +    private ClientMonitorCallback mCallback; +    @Mock +    Context mContext; +    @Mock +    private BiometricLogger mBiometricLogger; +    @Mock +    private BiometricContext mBiometricContext; +    @Mock +    private BiometricUtils<Face> mBiometricUtils; + +    private final int mBiometricId = 1; +    private final Face mFace = new Face("face", mBiometricId, 1 /* deviceId */); +    private FaceInternalEnumerateClient mClient; + +    @Before +    public void setUp() { +        when(mAidlSession.getSession()).thenReturn(mSession); + +        final List<Face> enrolled = new ArrayList<>(); +        enrolled.add(mFace); +        mClient = new FaceInternalEnumerateClient(mContext, () -> mAidlSession, mToken, USER_ID, +                TAG, enrolled, mBiometricUtils, SENSOR_ID, mBiometricLogger, mBiometricContext); +    } + +    @Test +    public void internalCleanupClient_noTemplatesRemaining() throws RemoteException { +        doAnswer(invocation -> { +            mClient.onEnumerationResult(mFace, 0); +            return null; +        }).when(mSession).enumerateEnrollments(); + +        mClient.start(mCallback); + +        verify(mSession).enumerateEnrollments(); +        assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0); +        verify(mBiometricUtils, never()).removeBiometricForUser(any(), anyInt(), anyInt()); +        verify(mCallback).onClientFinished(mClient, true); +    } + +    @Test +    public void internalCleanupClient_nullIdentifier_remainingOne() throws RemoteException { +        doAnswer(invocation -> { +            mClient.onEnumerationResult(null, 1); +            return null; +        }).when(mSession).enumerateEnrollments(); + +        mClient.start(mCallback); + +        verify(mSession).enumerateEnrollments(); +        assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0); +        verify(mBiometricUtils, never()).removeBiometricForUser(any(), anyInt(), anyInt()); +        verify(mCallback, never()).onClientFinished(mClient, true); +    } + +    @Test +    public void internalCleanupClient_nullIdentifier_noTemplatesRemaining() throws RemoteException { +        doAnswer(invocation -> { +            mClient.onEnumerationResult(null, 0); +            return null; +        }).when(mSession).enumerateEnrollments(); + +        mClient.start(mCallback); + +        verify(mSession).enumerateEnrollments(); +        assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0); +        verify(mBiometricUtils).removeBiometricForUser(mContext, USER_ID, mBiometricId); +        verify(mCallback).onClientFinished(mClient, true); +    } + +    @Test +    public void internalCleanupClient_templatesRemaining() throws RemoteException { +        final Face identifier = new Face("face", 2, 1); +        doAnswer(invocation -> { +            mClient.onEnumerationResult(identifier, 1); +            return null; +        }).when(mSession).enumerateEnrollments(); + +        mClient.start(mCallback); + +        verify(mSession).enumerateEnrollments(); +        assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(1); +        verify(mBiometricUtils, never()).removeBiometricForUser(any(), anyInt(), anyInt()); +        verify(mCallback, never()).onClientFinished(mClient, true); +    } + +    @Test +    public void internalCleanupClient_differentIdentifier_noTemplatesRemaining() +            throws RemoteException { +        final Face identifier = new Face("face", 2, 1); +        doAnswer(invocation -> { +            mClient.onEnumerationResult(identifier, 0); +            return null; +        }).when(mSession).enumerateEnrollments(); + +        mClient.start(mCallback); + +        verify(mSession).enumerateEnrollments(); +        assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(1); +        verify(mBiometricUtils).removeBiometricForUser(mContext, USER_ID, mBiometricId); +        verify(mCallback).onClientFinished(mClient, true); +    } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java index 76a5accc5fe9..1d9e9334f043 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRemovalClientTest.java @@ -76,7 +76,7 @@ public class FaceRemovalClientTest {      @Mock      private ClientMonitorCallback mCallback;      @Mock -    private Sensor.HalSessionCallback mHalSessionCallback; +    private AidlResponseHandler mAidlResponseHandler;      @Mock      private BiometricUtils<Face> mUtils;      @Mock @@ -115,7 +115,7 @@ public class FaceRemovalClientTest {      private FaceRemovalClient createClient(int version, int[] biometricIds) throws RemoteException {          when(mHal.getInterfaceVersion()).thenReturn(version); -        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback); +        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler);          return new FaceRemovalClient(mContext, () -> aidl, mToken,                  mClientMonitorCallbackConverter, biometricIds, USER_ID,                  "own-it", mUtils /* utils */, 5 /* sensorId */, mBiometricLogger, mBiometricContext, diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClientTest.java new file mode 100644 index 000000000000..dbbd69bdd3b5 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceResetLockoutClientTest.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.face.aidl; + +import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_STRONG; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.biometrics.face.ISession; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.AuthSessionCoordinator; +import com.android.server.biometrics.sensors.ClientMonitorCallback; +import com.android.server.biometrics.sensors.LockoutCache; +import com.android.server.biometrics.sensors.LockoutResetDispatcher; +import com.android.server.biometrics.sensors.LockoutTracker; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@Presubmit +@SmallTest +public class FaceResetLockoutClientTest { +    private static final String TAG = "FaceResetLockoutClientTest"; +    private static final int USER_ID = 2; +    private static final int SENSOR_ID = 4; + +    @Rule +    public final MockitoRule mockito = MockitoJUnit.rule(); + +    @Mock +    private AidlSession mAidlSession; +    @Mock +    private ISession mSession; +    @Mock +    private ClientMonitorCallback mCallback; +    @Mock +    Context mContext; +    @Mock +    private BiometricLogger mBiometricLogger; +    @Mock +    private BiometricContext mBiometricContext; +    private final byte[] mHardwareAuthToken = new byte[69]; +    @Mock +    private LockoutCache mLockoutTracker; +    @Mock +    private LockoutResetDispatcher mLockoutResetDispatcher; +    @Mock +    private AuthSessionCoordinator mAuthSessionCoordinator; + +    private FaceResetLockoutClient mClient; + +    @Before +    public void setUp() { +        mClient = new FaceResetLockoutClient(mContext, () -> mAidlSession, USER_ID, TAG, SENSOR_ID, +                mBiometricLogger, mBiometricContext, mHardwareAuthToken, mLockoutTracker, +                mLockoutResetDispatcher, BIOMETRIC_STRONG); + +        when(mAidlSession.getSession()).thenReturn(mSession); +        when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator); +    } + +    @Test +    public void testResetLockout_onLockoutCleared() throws RemoteException { +        doAnswer(invocation -> { +            mClient.onLockoutCleared(); +            return null; +        }).when(mSession).resetLockout(any()); +        mClient.start(mCallback); + +        verify(mSession).resetLockout(any()); +        verify(mAuthSessionCoordinator).resetLockoutFor(USER_ID, BIOMETRIC_STRONG, -1); +        verify(mLockoutTracker).setLockoutModeForUser(USER_ID, LockoutTracker.LOCKOUT_NONE); +        verify(mLockoutResetDispatcher).notifyLockoutResetCallbacks(SENSOR_ID); +        verify(mCallback).onClientFinished(mClient, true); +    } + +    @Test +    public void testResetLockout_onError() throws RemoteException { +        doAnswer(invocation -> { +            mClient.onError(0, 0); +            return null; +        }).when(mSession).resetLockout(any()); +        mClient.start(mCallback); + +        verify(mSession).resetLockout(any()); +        verify(mAuthSessionCoordinator, never()).resetLockoutFor(USER_ID, +                BIOMETRIC_STRONG, -1); +        verify(mLockoutTracker, never()).setLockoutModeForUser(USER_ID, +                LockoutTracker.LOCKOUT_NONE); +        verify(mLockoutResetDispatcher, never()).notifyLockoutResetCallbacks(SENSOR_ID); +        verify(mCallback).onClientFinished(mClient, false); +    } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClientTest.java new file mode 100644 index 000000000000..fb5502a1795d --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceRevokeChallengeClientTest.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.face.aidl; + +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.biometrics.face.ISession; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.ClientMonitorCallback; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@Presubmit +@SmallTest +public class FaceRevokeChallengeClientTest { +    private static final String TAG = "FaceRevokeChallengeClientTest"; +    private static final long CHALLENGE = 200L; +    private static final int USER_ID = 2; +    private static final int SENSOR_ID = 4; + +    @Rule +    public final MockitoRule mockito = MockitoJUnit.rule(); + +    @Mock +    private AidlSession mAidlSession; +    @Mock +    private ISession mSession; +    @Mock +    private IBinder mToken; +    @Mock +    private ClientMonitorCallback mCallback; +    @Mock +    Context mContext; +    @Mock +    private BiometricLogger mBiometricLogger; +    @Mock +    private BiometricContext mBiometricContext; + +    private FaceRevokeChallengeClient mClient; + +    @Before +    public void setUp() { +        when(mAidlSession.getSession()).thenReturn(mSession); + +        mClient = new FaceRevokeChallengeClient(mContext, () -> mAidlSession, mToken, USER_ID, TAG, +                SENSOR_ID, mBiometricLogger, mBiometricContext, CHALLENGE); +    } + +    @Test +    public void revokeChallenge() throws RemoteException { +        doAnswer(invocation -> { +            mClient.onChallengeRevoked(SENSOR_ID, USER_ID, CHALLENGE); +            return null; +        }).when(mSession).revokeChallenge(CHALLENGE); +        mClient.start(mCallback); + +        verify(mSession).revokeChallenge(CHALLENGE); +        verify(mCallback).onClientFinished(mClient, true); +    } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClientTest.java new file mode 100644 index 000000000000..eb8cc9c63e75 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceSetFeatureClientTest.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.face.aidl; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.hardware.biometrics.BiometricFaceConstants; +import android.hardware.biometrics.face.ISession; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.test.suitebuilder.annotation.SmallTest; +import android.testing.TestableContext; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.ClientMonitorCallback; +import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@Presubmit +@SmallTest +public class FaceSetFeatureClientTest { +    private static final String TAG = "FaceSetFeatureClientTest"; +    private static final int USER_ID = 2; +    private static final int SENSOR_ID = 4; + +    @Rule +    public final MockitoRule mockito = MockitoJUnit.rule(); + +    @Mock +    private AidlSession mAidlSession; +    @Mock +    private ISession mSession; +    @Mock +    private IBinder mToken; +    @Mock +    private ClientMonitorCallbackConverter mListener; +    @Mock +    private ClientMonitorCallback mCallback; +    @Mock +    private BiometricLogger mBiometricLogger; +    @Mock +    private BiometricContext mBiometricContext; + +    private final int mFeature = BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION; +    private final boolean mEnabled = true; +    private final byte[] mHardwareAuthToken = new byte[69]; +    private FaceSetFeatureClient mClient; +    TestableContext mContext = new TestableContext( +            InstrumentationRegistry.getInstrumentation().getContext()); + +    @Before +    public void setUp() { +        when(mAidlSession.getSession()).thenReturn(mSession); + +        mClient = new FaceSetFeatureClient(mContext, () -> mAidlSession, mToken, mListener, USER_ID, +                TAG, SENSOR_ID, mBiometricLogger, mBiometricContext, mFeature, mEnabled, +                mHardwareAuthToken); +    } + +    @Test +    public void setFeature_onFeatureSet() throws RemoteException { +        doAnswer(invocation -> { +            mClient.onFeatureSet(true); +            return null; +        }).when(mSession).setFeature(any(), +                eq(AidlConversionUtils.convertFrameworkToAidlFeature(mFeature)), eq(mEnabled)); +        mClient.start(mCallback); + +        verify(mSession).setFeature(any(), +                eq(AidlConversionUtils.convertFrameworkToAidlFeature(mFeature)), +                eq(mEnabled)); +        verify(mListener).onFeatureSet(true, mFeature); +        verify(mCallback).onClientFinished(mClient, true); +    } + +    @Test +    public void setFeature_onError() throws RemoteException { +        doAnswer(invocation -> { +            mClient.onError(0, 0); +            return null; +        }).when(mSession).setFeature(any(), +                eq(AidlConversionUtils.convertFrameworkToAidlFeature(mFeature)), +                eq(mEnabled)); +        mClient.start(mCallback); + +        verify(mSession).setFeature(any(), +                eq(AidlConversionUtils.convertFrameworkToAidlFeature(mFeature)), +                eq(mEnabled)); +        verify(mListener).onFeatureSet(false, mFeature); +        verify(mCallback).onClientFinished(mClient, false); +    } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java index be9f52e00b16..7a293e80c183 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java @@ -74,7 +74,7 @@ public class SensorTest {      @Mock      private UserAwareBiometricScheduler.UserSwitchCallback mUserSwitchCallback;      @Mock -    private Sensor.HalSessionCallback.Callback mHalSessionCallback; +    private AidlResponseHandler.HardwareUnavailableCallback mHardwareUnavailableCallback;      @Mock      private LockoutResetDispatcher mLockoutResetDispatcher;      @Mock @@ -94,7 +94,7 @@ public class SensorTest {      private final LockoutCache mLockoutCache = new LockoutCache();      private UserAwareBiometricScheduler mScheduler; -    private Sensor.HalSessionCallback mHalCallback; +    private AidlResponseHandler mHalCallback;      @Before      public void setUp() { @@ -111,10 +111,9 @@ public class SensorTest {                  mBiometricService,                  () -> USER_ID,                  mUserSwitchCallback); -        mHalCallback = new Sensor.HalSessionCallback(mContext, new Handler(mLooper.getLooper()), -                TAG, mScheduler, SENSOR_ID, -                USER_ID, mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator, -                mHalSessionCallback); +        mHalCallback = new AidlResponseHandler(mContext, mScheduler, SENSOR_ID, USER_ID, +                mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator, +                mHardwareUnavailableCallback);      }      @Test @@ -153,11 +152,11 @@ public class SensorTest {          sensorProps.commonProps.sensorId = 1;          final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal(                  sensorProps.commonProps.sensorId, sensorProps.commonProps.sensorStrength, -                sensorProps.commonProps.maxEnrollmentsPerUser, null, +                sensorProps.commonProps.maxEnrollmentsPerUser, null /* componentInfo */,                  sensorProps.sensorType, sensorProps.supportsDetectInteraction,                  sensorProps.halControlsPreview, false /* resetLockoutRequiresChallenge */); -        final Sensor sensor = new Sensor("SensorTest", mFaceProvider, mContext, null, -                internalProp, mLockoutResetDispatcher, mBiometricContext); +        final Sensor sensor = new Sensor("SensorTest", mFaceProvider, mContext, +                null /* handler */, internalProp, mLockoutResetDispatcher, mBiometricContext);          mScheduler.reset(); @@ -181,7 +180,7 @@ public class SensorTest {          sensorProps.commonProps.sensorId = 1;          final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal(                  sensorProps.commonProps.sensorId, sensorProps.commonProps.sensorStrength, -                sensorProps.commonProps.maxEnrollmentsPerUser, null, +                sensorProps.commonProps.maxEnrollmentsPerUser, null /* componentInfo */,                  sensorProps.sensorType, sensorProps.supportsDetectInteraction,                  sensorProps.halControlsPreview, false /* resetLockoutRequiresChallenge */);          final Sensor sensor = new Sensor("SensorTest", mFaceProvider, mContext, null, diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapterTest.java new file mode 100644 index 000000000000..9a40e8a7201a --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/AidlToHidlAdapterTest.java @@ -0,0 +1,342 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.face.hidl; + +import static com.android.server.biometrics.sensors.face.hidl.AidlToHidlAdapter.ENROLL_TIMEOUT_SEC; +import static com.android.server.biometrics.sensors.face.hidl.FaceGenerateChallengeClient.CHALLENGE_TIMEOUT_SEC; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.hardware.biometrics.BiometricFaceConstants; +import android.hardware.biometrics.common.ICancellationSignal; +import android.hardware.biometrics.face.EnrollmentType; +import android.hardware.biometrics.face.Feature; +import android.hardware.biometrics.face.V1_0.IBiometricsFace; +import android.hardware.biometrics.face.V1_0.OptionalBool; +import android.hardware.biometrics.face.V1_0.OptionalUint64; +import android.hardware.biometrics.face.V1_0.Status; +import android.hardware.face.Face; +import android.hardware.face.FaceManager; +import android.hardware.keymaster.HardwareAuthToken; +import android.hardware.keymaster.Timestamp; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.test.suitebuilder.annotation.SmallTest; +import android.testing.TestableContext; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.server.biometrics.HardwareAuthTokenUtils; +import com.android.server.biometrics.sensors.face.aidl.AidlConversionUtils; +import com.android.server.biometrics.sensors.face.aidl.AidlResponseHandler; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.time.Clock; +import java.util.ArrayList; +import java.util.List; + +@Presubmit +@SmallTest +public class AidlToHidlAdapterTest { +    @Rule +    public final MockitoRule mockito = MockitoJUnit.rule(); + +    @Mock +    private IBiometricsFace mSession; +    @Mock +    FaceManager mFaceManager; +    @Mock +    private AidlResponseHandler mAidlResponseHandler; +    @Mock +    private HardwareAuthToken mHardwareAuthToken; +    @Mock +    private Clock mClock; + +    private final long mChallenge = 100L; +    private AidlToHidlAdapter mAidlToHidlAdapter; +    private final Face mFace = new Face("face" /* name */, 1 /* faceId */, 0 /* deviceId */); +    private final int mFeature = BiometricFaceConstants.FEATURE_REQUIRE_REQUIRE_DIVERSITY; +    private final byte[] mFeatures = new byte[]{Feature.REQUIRE_ATTENTION}; + +    @Before +    public void setUp() throws RemoteException { +        TestableContext testableContext = new TestableContext( +                InstrumentationRegistry.getInstrumentation().getContext()); +        testableContext.addMockSystemService(FaceManager.class, mFaceManager); +        mAidlToHidlAdapter = new AidlToHidlAdapter(testableContext, () -> mSession, 0 /* userId */, +                mAidlResponseHandler, mClock); +        mHardwareAuthToken.timestamp = new Timestamp(); +        mHardwareAuthToken.mac = new byte[10]; +        final OptionalUint64 result = new OptionalUint64(); +        result.status = Status.OK; +        result.value = mChallenge; + +        when(mSession.generateChallenge(anyInt())).thenReturn(result); +        when(mFaceManager.getEnrolledFaces(anyInt())).thenReturn(List.of(mFace)); +    } + +    @Test +    public void testGenerateChallengeCache() throws RemoteException { +        verify(mSession).setCallback(any()); + +        final ArgumentCaptor<Long> challengeCaptor = ArgumentCaptor.forClass(Long.class); + +        mAidlToHidlAdapter.generateChallenge(); + +        verify(mSession).generateChallenge(CHALLENGE_TIMEOUT_SEC); +        verify(mAidlResponseHandler).onChallengeGenerated(challengeCaptor.capture()); +        assertThat(challengeCaptor.getValue()).isEqualTo(mChallenge); + +        forwardTime(10 /* seconds */); +        mAidlToHidlAdapter.generateChallenge(); +        forwardTime(20 /* seconds */); +        mAidlToHidlAdapter.generateChallenge(); + +        //Confirms that the challenge is cached and the hal method is not called again +        verifyNoMoreInteractions(mSession); +        verify(mAidlResponseHandler, times(3)) +                .onChallengeGenerated(mChallenge); + +        forwardTime(60 /* seconds */); +        mAidlToHidlAdapter.generateChallenge(); + +        //HAL method called after challenge has timed out +        verify(mSession, times(2)).generateChallenge(CHALLENGE_TIMEOUT_SEC); +    } + +    @Test +    public void testRevokeChallenge_waitsUntilEmpty() throws RemoteException { +        for (int i = 0; i < 3; i++) { +            mAidlToHidlAdapter.generateChallenge(); +            forwardTime(10 /* seconds */); +        } +        for (int i = 0; i < 3; i++) { +            mAidlToHidlAdapter.revokeChallenge(0); +            forwardTime((i + 1) * 10 /* seconds */); +        } + +        verify(mSession).revokeChallenge(); +    } + +    @Test +    public void testRevokeChallenge_timeout() throws RemoteException { +        mAidlToHidlAdapter.generateChallenge(); +        mAidlToHidlAdapter.generateChallenge(); +        forwardTime(700); +        mAidlToHidlAdapter.generateChallenge(); +        mAidlToHidlAdapter.revokeChallenge(0); + +        verify(mSession).revokeChallenge(); +    } + +    @Test +    public void testEnroll() throws RemoteException { +        ICancellationSignal cancellationSignal = mAidlToHidlAdapter.enroll(mHardwareAuthToken, +                EnrollmentType.DEFAULT, mFeatures, +                null /* previewSurface */); +        ArgumentCaptor<ArrayList<Integer>> featureCaptor = ArgumentCaptor.forClass(ArrayList.class); + +        verify(mSession).enroll(any(), eq(ENROLL_TIMEOUT_SEC), featureCaptor.capture()); + +        ArrayList<Integer> features = featureCaptor.getValue(); + +        assertThat(features).containsExactly( +                AidlConversionUtils.convertAidlToFrameworkFeature(mFeatures[0])); + +        cancellationSignal.cancel(); + +        verify(mSession).cancel(); +    } + +    @Test +    public void testAuthenticate() throws RemoteException { +        final int operationId = 2; +        ICancellationSignal cancellationSignal = mAidlToHidlAdapter.authenticate(operationId); + +        verify(mSession).authenticate(operationId); + +        cancellationSignal.cancel(); + +        verify(mSession).cancel(); +    } + +    @Test +    public void testDetectInteraction() throws RemoteException { +        ICancellationSignal cancellationSignal = mAidlToHidlAdapter.detectInteraction(); + +        verify(mSession).authenticate(0); + +        cancellationSignal.cancel(); + +        verify(mSession).cancel(); +    } + +    @Test +    public void testEnumerateEnrollments() throws RemoteException { +        mAidlToHidlAdapter.enumerateEnrollments(); + +        verify(mSession).enumerate(); +    } + +    @Test +    public void testRemoveEnrollment() throws RemoteException { +        final int[] enrollments = new int[]{1}; +        mAidlToHidlAdapter.removeEnrollments(enrollments); + +        verify(mSession).remove(enrollments[0]); +    } + +    @Test +    public void testGetFeatures_onResultSuccess() throws RemoteException { +        final OptionalBool result = new OptionalBool(); +        result.status = Status.OK; +        result.value = true; +        ArgumentCaptor<byte[]> featureRetrieved = ArgumentCaptor.forClass(byte[].class); + +        when(mSession.getFeature(eq(mFeature), anyInt())).thenReturn(result); + +        mAidlToHidlAdapter.setFeature(mFeature); +        mAidlToHidlAdapter.getFeatures(); + +        verify(mSession).getFeature(eq(mFeature), anyInt()); +        verify(mAidlResponseHandler).onFeaturesRetrieved(featureRetrieved.capture()); +        assertThat(featureRetrieved.getValue()[0]).isEqualTo( +                AidlConversionUtils.convertFrameworkToAidlFeature(mFeature)); +    } + +    @Test +    public void testGetFeatures_onResultFailed() throws RemoteException { +        final OptionalBool result = new OptionalBool(); +        result.status = Status.OK; +        result.value = false; +        ArgumentCaptor<byte[]> featureRetrieved = ArgumentCaptor.forClass(byte[].class); + +        when(mSession.getFeature(eq(mFeature), anyInt())).thenReturn(result); + +        mAidlToHidlAdapter.setFeature(mFeature); +        mAidlToHidlAdapter.getFeatures(); + +        verify(mSession).getFeature(eq(mFeature), anyInt()); +        verify(mAidlResponseHandler).onFeaturesRetrieved(featureRetrieved.capture()); +        assertThat(featureRetrieved.getValue().length).isEqualTo(0); +    } + +    @Test +    public void testGetFeatures_onStatusFailed() throws RemoteException { +        final OptionalBool result = new OptionalBool(); +        result.status = Status.INTERNAL_ERROR; +        result.value = false; + +        when(mSession.getFeature(eq(mFeature), anyInt())).thenReturn(result); + +        mAidlToHidlAdapter.setFeature(mFeature); +        mAidlToHidlAdapter.getFeatures(); + +        verify(mSession).getFeature(eq(mFeature), anyInt()); +        verify(mAidlResponseHandler, never()).onFeaturesRetrieved(any()); +        verify(mAidlResponseHandler).onError(BiometricFaceConstants.FACE_ERROR_UNKNOWN, 0); +    } + +    @Test +    public void testGetFeatures_featureNotSet() throws RemoteException { +        mAidlToHidlAdapter.getFeatures(); + +        verify(mSession, never()).getFeature(eq(mFeature), anyInt()); +        verify(mAidlResponseHandler, never()).onFeaturesRetrieved(any()); +    } + +    @Test +    public void testSetFeatureSuccessful() throws RemoteException { +        byte feature = Feature.REQUIRE_ATTENTION; +        boolean enabled = true; + +        when(mSession.setFeature(anyInt(), anyBoolean(), any(), anyInt())).thenReturn(Status.OK); + +        mAidlToHidlAdapter.setFeature(mHardwareAuthToken, feature, enabled); + +        verify(mAidlResponseHandler).onFeatureSet(feature); +    } + +    @Test +    public void testSetFeatureFailed() throws RemoteException { +        byte feature = Feature.REQUIRE_ATTENTION; +        boolean enabled = true; + +        when(mSession.setFeature(anyInt(), anyBoolean(), any(), anyInt())) +                .thenReturn(Status.INTERNAL_ERROR); + +        mAidlToHidlAdapter.setFeature(mHardwareAuthToken, feature, enabled); + +        verify(mAidlResponseHandler).onError(BiometricFaceConstants.FACE_ERROR_UNKNOWN, +                0 /* vendorCode */); +    } + +    @Test +    public void testGetAuthenticatorId() throws RemoteException { +        final long authenticatorId = 2L; +        final OptionalUint64 result = new OptionalUint64(); +        result.status = Status.OK; +        result.value = authenticatorId; + +        when(mSession.getAuthenticatorId()).thenReturn(result); + +        mAidlToHidlAdapter.getAuthenticatorId(); + +        verify(mSession).getAuthenticatorId(); +        verify(mAidlResponseHandler).onAuthenticatorIdRetrieved(authenticatorId); +    } + +    @Test +    public void testResetLockout() throws RemoteException { +        mAidlToHidlAdapter.resetLockout(mHardwareAuthToken); + +        ArgumentCaptor<ArrayList> hatCaptor = ArgumentCaptor.forClass(ArrayList.class); + +        verify(mSession).resetLockout(hatCaptor.capture()); + +        assertThat(hatCaptor.getValue()).containsExactlyElementsIn(processHAT(mHardwareAuthToken)); +    } + +    private ArrayList<Byte> processHAT(HardwareAuthToken hat) { +        ArrayList<Byte> hardwareAuthToken = new ArrayList<>(); +        for (byte b : HardwareAuthTokenUtils.toByteArray(hat)) { +            hardwareAuthToken.add(b); +        } +        return hardwareAuthToken; +    } + +    private void forwardTime(long seconds) { +        when(mClock.millis()).thenReturn(seconds * 1000); +    } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java index 8a11e31014d5..79a528c59f49 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java @@ -53,11 +53,15 @@ import android.os.IBinder;  import android.os.RemoteException;  import android.os.test.TestLooper;  import android.platform.test.annotations.Presubmit; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider;  import android.testing.TestableContext;  import androidx.test.filters.SmallTest;  import androidx.test.platform.app.InstrumentationRegistry; +import com.android.server.biometrics.Flags;  import com.android.server.biometrics.log.BiometricContext;  import com.android.server.biometrics.log.BiometricLogger;  import com.android.server.biometrics.log.CallbackWithProbe; @@ -66,6 +70,7 @@ import com.android.server.biometrics.log.Probe;  import com.android.server.biometrics.sensors.AuthSessionCoordinator;  import com.android.server.biometrics.sensors.ClientMonitorCallback;  import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; +import com.android.server.biometrics.sensors.LockoutTracker;  import org.junit.Before;  import org.junit.Rule; @@ -102,6 +107,9 @@ public class FingerprintAuthenticationClientTest {              InstrumentationRegistry.getInstrumentation().getTargetContext(), null);      @Rule      public final MockitoRule mockito = MockitoJUnit.rule(); +    @Rule +    public final CheckFlagsRule mCheckFlagsRule = +            DeviceFlagsValueProvider.createCheckFlagsRule();      @Mock      private ISession mHal; @@ -124,7 +132,7 @@ public class FingerprintAuthenticationClientTest {      @Mock      private ClientMonitorCallback mCallback;      @Mock -    private Sensor.HalSessionCallback mHalSessionCallback; +    private AidlResponseHandler mAidlResponseHandler;      @Mock      private ActivityTaskManager mActivityTaskManager;      @Mock @@ -135,6 +143,8 @@ public class FingerprintAuthenticationClientTest {      private AuthSessionCoordinator mAuthSessionCoordinator;      @Mock      private Clock mClock; +    @Mock +    private LockoutTracker mLockoutTracker;      @Captor      private ArgumentCaptor<OperationContextExt> mOperationContextCaptor;      @Captor @@ -425,37 +435,65 @@ public class FingerprintAuthenticationClientTest {          verify(mCallback).onClientFinished(client, true);      } +    @Test +    public void testLockoutTracker_authSuccess() throws RemoteException { +        final FingerprintAuthenticationClient client = createClient(1 /* version */, +                true /* allowBackgroundAuthentication */, mClientMonitorCallbackConverter, +                mLockoutTracker); +        client.start(mCallback); +        client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */, +                2 /* deviceId */), true /* authenticated */, new ArrayList<>()); + +        verify(mLockoutTracker).resetFailedAttemptsForUser(true, USER_ID); +        verify(mLockoutTracker, never()).addFailedAttemptForUser(anyInt()); +    } + +    @Test +    @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL) +    public void testLockoutTracker_authFailed() throws RemoteException { +        final FingerprintAuthenticationClient client = createClient(1 /* version */, +                true /* allowBackgroundAuthentication */, mClientMonitorCallbackConverter, +                mLockoutTracker); +        client.start(mCallback); +        client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */, +                2 /* deviceId */), false /* authenticated */, new ArrayList<>()); + +        verify(mLockoutTracker, never()).resetFailedAttemptsForUser(anyBoolean(), anyInt()); +        verify(mLockoutTracker).addFailedAttemptForUser(USER_ID); +    } +      private FingerprintAuthenticationClient createClient() throws RemoteException {          return createClient(100 /* version */, true /* allowBackgroundAuthentication */, -                mClientMonitorCallbackConverter); +                mClientMonitorCallbackConverter, null);      }      private FingerprintAuthenticationClient createClientWithoutBackgroundAuth()              throws RemoteException {          return createClient(100 /* version */, false /* allowBackgroundAuthentication */, -                mClientMonitorCallbackConverter); +                mClientMonitorCallbackConverter, null);      }      private FingerprintAuthenticationClient createClient(int version) throws RemoteException {          return createClient(version, true /* allowBackgroundAuthentication */, -                mClientMonitorCallbackConverter); +                mClientMonitorCallbackConverter, null);      }      private FingerprintAuthenticationClient createClientWithNullListener() throws RemoteException {          return createClient(100 /* version */, true /* allowBackgroundAuthentication */, -                null /* listener */); +                null, /* listener */null);      }      private FingerprintAuthenticationClient createClient(int version, -            boolean allowBackgroundAuthentication, ClientMonitorCallbackConverter listener) +            boolean allowBackgroundAuthentication, ClientMonitorCallbackConverter listener, +            LockoutTracker lockoutTracker)              throws RemoteException {          when(mHal.getInterfaceVersion()).thenReturn(version); -        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback); +        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler);          final FingerprintAuthenticateOptions options = new FingerprintAuthenticateOptions.Builder()                  .setOpPackageName("test-owner") -                .setUserId(5) -                .setSensorId(9) +                .setUserId(USER_ID) +                .setSensorId(SENSOR_ID)                  .build();          return new FingerprintAuthenticationClient(mContext, () -> aidl, mToken,                  REQUEST_ID, listener, OP_ID, @@ -463,10 +501,11 @@ public class FingerprintAuthenticationClientTest {                  false /* requireConfirmation */,                  mBiometricLogger, mBiometricContext,                  true /* isStrongBiometric */, -                null /* taskStackListener */, null /* lockoutCache */, +                null /* taskStackListener */,                  mUdfpsOverlayController, mSideFpsController, allowBackgroundAuthentication,                  mSensorProps, -                new Handler(mLooper.getLooper()), 0 /* biometricStrength */, mClock) { +                new Handler(mLooper.getLooper()), 0 /* biometricStrength */, mClock, +                lockoutTracker) {              @Override              protected ActivityTaskManager getActivityTaskManager() {                  return mActivityTaskManager; diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java index 78d3a9dd9f9e..a467c84845cd 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java @@ -83,7 +83,7 @@ public class FingerprintDetectClientTest {      @Mock      private ClientMonitorCallback mCallback;      @Mock -    private Sensor.HalSessionCallback mHalSessionCallback; +    private AidlResponseHandler mAidlResponseHandler;      @Captor      private ArgumentCaptor<OperationContextExt> mOperationContextCaptor;      @Captor @@ -150,7 +150,7 @@ public class FingerprintDetectClientTest {      @Test      public void testWhenListenerIsNull() { -        final AidlSession aidl = new AidlSession(0, mHal, USER_ID, mHalSessionCallback); +        final AidlSession aidl = new AidlSession(0, mHal, USER_ID, mAidlResponseHandler);          final FingerprintDetectClient client =  new FingerprintDetectClient(mContext, () -> aidl,                  mToken, 6 /* requestId */, null /* listener */,                  new FingerprintAuthenticateOptions.Builder() @@ -173,7 +173,7 @@ public class FingerprintDetectClientTest {      private FingerprintDetectClient createClient(int version) throws RemoteException {          when(mHal.getInterfaceVersion()).thenReturn(version); -        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback); +        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler);          return new FingerprintDetectClient(mContext, () -> aidl, mToken,                  6 /* requestId */, mClientMonitorCallbackConverter,                  new FingerprintAuthenticateOptions.Builder() diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java index ef253801b118..c7eb1db3e74b 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java @@ -102,7 +102,7 @@ public class FingerprintEnrollClientTest {      @Mock      private ClientMonitorCallback mCallback;      @Mock -    private Sensor.HalSessionCallback mHalSessionCallback; +    private AidlResponseHandler mAidlResponseHandler;      @Mock      private Probe mLuxProbe;      @Captor @@ -291,7 +291,7 @@ public class FingerprintEnrollClientTest {      private FingerprintEnrollClient createClient(int version) throws RemoteException {          when(mHal.getInterfaceVersion()).thenReturn(version); -        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback); +        final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mAidlResponseHandler);          return new FingerprintEnrollClient(mContext, () -> aidl, mToken, REQUEST_ID,          mClientMonitorCallbackConverter, 0 /* userId */,          HAT, "owner", mBiometricUtils, 8 /* sensorId */, diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClientTest.java new file mode 100644 index 000000000000..840961938cb2 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintGenerateChallengeClientTest.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.fingerprint.aidl; + +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.biometrics.fingerprint.ISession; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.ClientMonitorCallback; +import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@Presubmit +@SmallTest +public class FingerprintGenerateChallengeClientTest { +    private static final String TAG = "FingerprintGenerateChallengeClientTest"; +    private static final int USER_ID = 2; +    private static final int SENSOR_ID = 4; +    private static final long CHALLENGE = 200; + +    @Rule +    public final MockitoRule mockito = MockitoJUnit.rule(); + +    @Mock +    private AidlSession mAidlSession; +    @Mock +    private ISession mSession; +    @Mock +    private IBinder mToken; +    @Mock +    private ClientMonitorCallbackConverter mListener; +    @Mock +    private ClientMonitorCallback mCallback; +    @Mock +    private Context mContext; +    @Mock +    private BiometricLogger mBiometricLogger; +    @Mock +    private BiometricContext mBiometricContext; + +    private FingerprintGenerateChallengeClient mClient; + +    @Before +    public void setUp() { +        when(mAidlSession.getSession()).thenReturn(mSession); + +        mClient = new FingerprintGenerateChallengeClient(mContext, () -> mAidlSession, mToken, +                mListener, USER_ID, TAG, SENSOR_ID, mBiometricLogger, mBiometricContext); +    } + +    @Test +    public void generateChallenge() throws RemoteException { +        doAnswer(invocation -> { +            mClient.onChallengeGenerated(SENSOR_ID, USER_ID, CHALLENGE); +            return null; +        }).when(mSession).generateChallenge(); +        mClient.start(mCallback); + +        verify(mSession).generateChallenge(); +        verify(mListener).onChallengeGenerated(SENSOR_ID, USER_ID, CHALLENGE); +        verify(mCallback).onClientFinished(mClient, true); +    } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java index 580644347c84..c9482ceb00f5 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java @@ -28,6 +28,7 @@ import static org.mockito.Mockito.when;  import android.hardware.biometrics.BiometricAuthenticator;  import android.hardware.biometrics.fingerprint.ISession;  import android.hardware.fingerprint.Fingerprint; +import android.os.RemoteException;  import android.platform.test.annotations.Presubmit;  import android.testing.TestableContext; @@ -41,7 +42,6 @@ import com.android.server.biometrics.sensors.ClientMonitorCallback;  import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;  import org.junit.Before; -import org.junit.Ignore;  import org.junit.Rule;  import org.junit.Test;  import org.mockito.ArgumentCaptor; @@ -70,9 +70,9 @@ public class FingerprintInternalCleanupClientTest {              InstrumentationRegistry.getInstrumentation().getTargetContext(), null);      @Mock -    private AidlSession mAidlSession; +    ISession mSession;      @Mock -    private ISession mSession; +    private AidlSession mAidlSession;      @Mock      private BiometricLogger mLogger;      @Mock @@ -87,15 +87,16 @@ public class FingerprintInternalCleanupClientTest {      @Before      public void setup() { -        when(mAidlSession.getSession()).thenReturn(mSession);          mAddedIds = new ArrayList<>(); + +        when(mAidlSession.getSession()).thenReturn(mSession);      } -    @Ignore("TODO(b/229015801): verify cleanup behavior")      @Test      public void removesUnknownTemplate() throws Exception {          mClient = createClient(); +        final ArgumentCaptor<int[]> captor = ArgumentCaptor.forClass(int[].class);          final List<Fingerprint> templates = List.of(                  new Fingerprint("one", 1, 1),                  new Fingerprint("two", 2, 1) @@ -108,8 +109,8 @@ public class FingerprintInternalCleanupClientTest {              mClient.getCurrentRemoveClient().onRemoved(templates.get(i), 0);          } +        verify(mSession).enumerateEnrollments();          assertThat(mAddedIds).isEmpty(); -        final ArgumentCaptor<int[]> captor = ArgumentCaptor.forClass(int[].class);          verify(mSession, times(2)).removeEnrollments(captor.capture());          assertThat(captor.getAllValues().stream()                  .flatMap(x -> Arrays.stream(x).boxed()) @@ -132,13 +133,15 @@ public class FingerprintInternalCleanupClientTest {              mClient.getCurrentEnumerateClient().onEnumerationResult(templates.get(i), i);          } +        verify(mSession).enumerateEnrollments();          assertThat(mAddedIds).containsExactly(1, 2);          verify(mSession, never()).removeEnrollments(any());          verify(mCallback).onClientFinished(eq(mClient), eq(true));      }      @Test -    public void cleanupUnknownHalTemplatesAfterEnumerationWhenVirtualIsDisabled() { +    public void cleanupUnknownHalTemplatesAfterEnumerationWhenVirtualIsDisabled() +            throws RemoteException {          mClient = createClient();          final List<Fingerprint> templates = List.of( @@ -150,6 +153,8 @@ public class FingerprintInternalCleanupClientTest {          for (int i = templates.size() - 1; i >= 0; i--) {              mClient.getCurrentEnumerateClient().onEnumerationResult(templates.get(i), i);          } + +        verify(mSession).enumerateEnrollments();          // The first template is removed after enumeration          assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(2); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClientTest.java new file mode 100644 index 000000000000..723f916f99c8 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClientTest.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.fingerprint.aidl; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.biometrics.fingerprint.ISession; +import android.hardware.fingerprint.Fingerprint; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.BiometricUtils; +import com.android.server.biometrics.sensors.ClientMonitorCallback; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@Presubmit +@SmallTest +public class FingerprintInternalEnumerateClientTest { +    private static final String TAG = "FingerprintInternalEnumerateClientTest"; +    private static final int USER_ID = 2; +    private static final int SENSOR_ID = 4; + +    @Rule +    public final MockitoRule mockito = MockitoJUnit.rule(); + +    @Mock +    private AidlSession mAidlSession; +    @Mock +    private ISession mSession; +    @Mock +    private IBinder mToken; +    @Mock +    private ClientMonitorCallback mCallback; +    @Mock +    private Context mContext; +    @Mock +    private BiometricLogger mBiometricLogger; +    @Mock +    private BiometricContext mBiometricContext; +    @Mock +    private BiometricUtils<Fingerprint> mBiometricUtils; + +    private FingerprintInternalEnumerateClient mClient; + +    @Before +    public void setUp() { +        when(mAidlSession.getSession()).thenReturn(mSession); + +        List<Fingerprint> enrolled = new ArrayList<>(); +        enrolled.add(new Fingerprint("one", 1, 1)); +        mClient = new FingerprintInternalEnumerateClient(mContext, () -> mAidlSession, mToken, +                USER_ID, TAG, enrolled, mBiometricUtils, SENSOR_ID, mBiometricLogger, +                mBiometricContext); +    } + +    @Test +    public void internalEnumerate_unknownTemplates() throws RemoteException { +        doAnswer(invocation -> { +            mClient.onEnumerationResult(new Fingerprint("two", 2, 1), 1); +            mClient.onEnumerationResult(new Fingerprint("three", 3, 1), 0); +            return null; +        }).when(mSession).enumerateEnrollments(); +        mClient.start(mCallback); + +        verify(mSession).enumerateEnrollments(); +        assertThat(mClient.getUnknownHALTemplates().stream() +                .flatMap(x -> Stream.of(x.getBiometricId())) +                .collect(Collectors.toList())).containsExactly(2, 3); +        verify(mBiometricUtils).removeBiometricForUser(mContext, USER_ID, 1); +        verify(mCallback).onClientFinished(mClient, true); +    } + +    @Test +    public void internalEnumerate_noUnknownTemplates() throws RemoteException { +        doAnswer(invocation -> { +            mClient.onEnumerationResult(new Fingerprint("one", 1, 1), 0); +            return null; +        }).when(mSession).enumerateEnrollments(); +        mClient.start(mCallback); + +        verify(mSession).enumerateEnrollments(); +        assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0); +        verify(mBiometricUtils, never()).removeBiometricForUser(any(), anyInt(), anyInt()); +        verify(mCallback).onClientFinished(mClient, true); +    } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClientTest.java new file mode 100644 index 000000000000..64f07e20e958 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRemovalClientTest.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.fingerprint.aidl; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.biometrics.fingerprint.ISession; +import android.hardware.fingerprint.Fingerprint; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.BiometricUtils; +import com.android.server.biometrics.sensors.ClientMonitorCallback; +import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@Presubmit +@SmallTest +public class FingerprintRemovalClientTest { +    private static final String TAG = "FingerprintRemovalClientTest"; +    private static final int USER_ID = 2; +    private static final int SENSOR_ID = 4; + +    @Rule +    public final MockitoRule mockito = MockitoJUnit.rule(); + +    @Mock +    private AidlSession mAidlSession; +    @Mock +    private ISession mSession; +    @Mock +    private IBinder mToken; +    @Mock +    private ClientMonitorCallbackConverter mListener; +    @Mock +    private ClientMonitorCallback mCallback; +    @Mock +    private Context mContext; +    @Mock +    private BiometricLogger mBiometricLogger; +    @Mock +    private BiometricContext mBiometricContext; +    @Mock +    private BiometricUtils<Fingerprint> mBiometricUtils; +    @Mock +    private Map<Integer, Long> mAuthenticatorIds; + +    private FingerprintRemovalClient mClient; +    private int[] mBiometricIds = new int[]{1, 2}; + +    @Before +    public void setUp() { +        when(mAidlSession.getSession()).thenReturn(mSession); + +        mClient = new FingerprintRemovalClient(mContext, () -> mAidlSession, mToken, mListener, +                mBiometricIds, USER_ID, TAG, mBiometricUtils, SENSOR_ID, +                mBiometricLogger, mBiometricContext, mAuthenticatorIds); +    } + +    @Test +    public void removalMultipleFingerprints() throws RemoteException { +        when(mBiometricUtils.getBiometricsForUser(any(), anyInt())).thenReturn( +                List.of(new Fingerprint("three", 3, 1))); +        doAnswer(invocation -> { +            mClient.onRemoved(new Fingerprint("one", 1, 1), 1); +            mClient.onRemoved(new Fingerprint("two", 2, 1), 0); +            return null; +        }).when(mSession).removeEnrollments(mBiometricIds); +        mClient.start(mCallback); + +        verify(mSession).removeEnrollments(mBiometricIds); +        verify(mBiometricUtils, times(2)).removeBiometricForUser(eq(mContext), +                eq(USER_ID), anyInt()); +        verifyNoMoreInteractions(mAuthenticatorIds); +        verify(mListener, times(2)).onRemoved(any(), anyInt()); +        verify(mCallback).onClientFinished(mClient, true); +    } + +    @Test +    public void removeFingerprint_nullIdentifier() throws RemoteException { +        doAnswer(invocation -> { +            mClient.onRemoved(null, 0); +            return null; +        }).when(mSession).removeEnrollments(mBiometricIds); +        mClient.start(mCallback); + +        verify(mSession).removeEnrollments(mBiometricIds); +        verify(mListener).onError(anyInt(), anyInt(), anyInt(), anyInt()); +        verify(mCallback).onClientFinished(mClient, false); +    } + +    @Test +    public void removeFingerprints_noFingerprintEnrolled() throws RemoteException { +        doAnswer(invocation -> { +            mClient.onRemoved(new Fingerprint("one", 1, 1), 1); +            mClient.onRemoved(new Fingerprint("two", 2, 1), 0); +            return null; +        }).when(mSession).removeEnrollments(mBiometricIds); +        when(mBiometricUtils.getBiometricsForUser(any(), anyInt())).thenReturn(new ArrayList<>()); + +        mClient.start(mCallback); + +        verify(mSession).removeEnrollments(mBiometricIds); +        verify(mBiometricUtils, times(2)).removeBiometricForUser(eq(mContext), +                eq(USER_ID), anyInt()); +        verify(mAuthenticatorIds).put(USER_ID, 0L); +        verify(mListener, times(2)).onRemoved(any(), anyInt()); +        verify(mCallback).onClientFinished(mClient, true); +    } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClientTest.java new file mode 100644 index 000000000000..a4746dea7ac5 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintResetLockoutClientTest.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.fingerprint.aidl; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.biometrics.BiometricManager; +import android.hardware.biometrics.fingerprint.ISession; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.AuthSessionCoordinator; +import com.android.server.biometrics.sensors.ClientMonitorCallback; +import com.android.server.biometrics.sensors.LockoutResetDispatcher; +import com.android.server.biometrics.sensors.LockoutTracker; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@Presubmit +@SmallTest +public class FingerprintResetLockoutClientTest { +    private static final String TAG = "FingerprintResetLockoutClientTest"; +    private static final int USER_ID = 2; +    private static final int SENSOR_ID = 4; + +    @Rule +    public final MockitoRule mockito = MockitoJUnit.rule(); + +    @Mock +    private AidlSession mAidlSession; +    @Mock +    private ISession mSession; +    @Mock +    private ClientMonitorCallback mCallback; +    @Mock +    private Context mContext; +    @Mock +    private BiometricLogger mBiometricLogger; +    @Mock +    private BiometricContext mBiometricContext; +    @Mock +    private LockoutTracker mLockoutTracker; +    @Mock +    private LockoutResetDispatcher mLockoutResetDispatcher; +    @Mock +    private AuthSessionCoordinator mAuthSessionCoordinator; + +    private FingerprintResetLockoutClient mClient; + +    @Before +    public void setUp() { +        when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator); +        when(mAidlSession.getSession()).thenReturn(mSession); + +        mClient = new FingerprintResetLockoutClient(mContext, () -> mAidlSession, USER_ID, TAG, +                SENSOR_ID, mBiometricLogger, mBiometricContext, new byte[69], +                mLockoutTracker, mLockoutResetDispatcher, +                BiometricManager.Authenticators.BIOMETRIC_STRONG); +    } + +    @Test +    public void resetLockout_onLockoutCleared() throws RemoteException { +        doAnswer(invocation -> { +            mClient.onLockoutCleared(); +            return null; +        }).when(mSession).resetLockout(any()); +        mClient.start(mCallback); + +        verify(mSession).resetLockout(any()); +        verify(mLockoutTracker).setLockoutModeForUser(USER_ID, LockoutTracker.LOCKOUT_NONE); +        verify(mLockoutTracker).resetFailedAttemptsForUser(true, USER_ID); +        verify(mLockoutResetDispatcher).notifyLockoutResetCallbacks(SENSOR_ID); +        verify(mAuthSessionCoordinator).resetLockoutFor(eq(USER_ID), +                eq(BiometricManager.Authenticators.BIOMETRIC_STRONG), anyLong()); +    } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClientTest.java new file mode 100644 index 000000000000..f19b2f7da8a5 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintRevokeChallengeClientTest.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.fingerprint.aidl; + +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.biometrics.fingerprint.ISession; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import com.android.server.biometrics.log.BiometricContext; +import com.android.server.biometrics.log.BiometricLogger; +import com.android.server.biometrics.sensors.ClientMonitorCallback; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@Presubmit +@SmallTest +public class FingerprintRevokeChallengeClientTest { +    private static final String TAG = "FingerprintRevokeChallengeClientTest"; +    private static final int USER_ID = 2; +    private static final int SENSOR_ID = 4; +    private static final long CHALLENGE = 200; + +    @Rule +    public final MockitoRule mockito = MockitoJUnit.rule(); + +    @Mock +    private AidlSession mAidlSession; +    @Mock +    private ISession mSession; +    @Mock +    private IBinder mToken; +    @Mock +    private ClientMonitorCallback mCallback; +    @Mock +    private Context mContext; +    @Mock +    private BiometricLogger mBiometricLogger; +    @Mock +    private BiometricContext mBiometricContext; + +    private FingerprintRevokeChallengeClient mClient; + +    @Before +    public void setUp() { +        when(mAidlSession.getSession()).thenReturn(mSession); + +        mClient = new FingerprintRevokeChallengeClient(mContext, () -> mAidlSession, mToken, +                USER_ID, TAG, SENSOR_ID, mBiometricLogger, mBiometricContext, CHALLENGE); +    } + +    @Test +    public void revokeChallenge_sameChallenge() throws RemoteException { +        doAnswer(invocation -> { +            mClient.onChallengeRevoked(CHALLENGE); +            return null; +        }).when(mSession).revokeChallenge(CHALLENGE); +        mClient.start(mCallback); + +        verify(mSession).revokeChallenge(CHALLENGE); +        verify(mCallback).onClientFinished(mClient, true); +    } + +    @Test +    public void revokeChallenge_differentChallenge() throws RemoteException { +        doAnswer(invocation -> { +            mClient.onChallengeRevoked(CHALLENGE + 1); +            return null; +        }).when(mSession).revokeChallenge(CHALLENGE); +        mClient.start(mCallback); + +        verify(mSession).revokeChallenge(CHALLENGE); +        verify(mCallback).onClientFinished(mClient, false); +    } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java index 15d7601dde34..410260074fbd 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java @@ -75,7 +75,7 @@ public class SensorTest {      @Mock      private UserAwareBiometricScheduler.UserSwitchCallback mUserSwitchCallback;      @Mock -    private Sensor.HalSessionCallback.Callback mHalSessionCallback; +    private AidlResponseHandler.HardwareUnavailableCallback mHardwareUnavailableCallback;      @Mock      private LockoutResetDispatcher mLockoutResetDispatcher;      @Mock @@ -97,7 +97,7 @@ public class SensorTest {      private final LockoutCache mLockoutCache = new LockoutCache();      private UserAwareBiometricScheduler mScheduler; -    private Sensor.HalSessionCallback mHalCallback; +    private AidlResponseHandler mHalCallback;      @Before      public void setUp() { @@ -113,10 +113,9 @@ public class SensorTest {                  mBiometricService,                  () -> USER_ID,                  mUserSwitchCallback); -        mHalCallback = new Sensor.HalSessionCallback(mContext, new Handler(mLooper.getLooper()), -                TAG, mScheduler, SENSOR_ID, -                USER_ID, mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator, -                mHalSessionCallback); +        mHalCallback = new AidlResponseHandler(mContext, mScheduler, SENSOR_ID, USER_ID, +                mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator, +                mHardwareUnavailableCallback);      }      @Test diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapterTest.java new file mode 100644 index 000000000000..b78ba82bd8fe --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/AidlToHidlAdapterTest.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.sensors.fingerprint.hidl; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.hardware.biometrics.common.ICancellationSignal; +import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint; +import android.hardware.keymaster.HardwareAuthToken; +import android.hardware.keymaster.Timestamp; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@Presubmit +@SmallTest +public class AidlToHidlAdapterTest { +    @Rule +    public final MockitoRule mockito = MockitoJUnit.rule(); + +    @Mock +    private IBiometricsFingerprint mSession; +    @Mock +    private AidlResponseHandler mAidlResponseHandler; +    @Mock +    private HardwareAuthToken mHardwareAuthToken; + +    private final long mChallenge = 100L; +    private final int mUserId = 0; +    private AidlToHidlAdapter mAidlToHidlAdapter; + +    @Before +    public void setUp() { +        mAidlToHidlAdapter = new AidlToHidlAdapter(() -> mSession, mUserId, +                mAidlResponseHandler); +        mHardwareAuthToken.timestamp = new Timestamp(); +        mHardwareAuthToken.mac = new byte[10]; +    } + +    @Test +    public void testGenerateChallenge() throws RemoteException { +        when(mSession.preEnroll()).thenReturn(mChallenge); +        mAidlToHidlAdapter.generateChallenge(); + +        verify(mSession).preEnroll(); +        verify(mAidlResponseHandler).onChallengeGenerated(mChallenge); +    } + +    @Test +    public void testRevokeChallenge() throws RemoteException { +        mAidlToHidlAdapter.revokeChallenge(mChallenge); + +        verify(mSession).postEnroll(); +        verify(mAidlResponseHandler).onChallengeRevoked(0L); +    } + +    @Test +    public void testEnroll() throws RemoteException { +        final ICancellationSignal cancellationSignal = +                mAidlToHidlAdapter.enroll(mHardwareAuthToken); + +        verify(mSession).enroll(any(), anyInt(), eq(AidlToHidlAdapter.ENROLL_TIMEOUT_SEC)); + +        cancellationSignal.cancel(); + +        verify(mSession).cancel(); +    } + +    @Test +    public void testAuthenticate() throws RemoteException { +        final int operationId = 2; +        final ICancellationSignal cancellationSignal = mAidlToHidlAdapter.authenticate(operationId); + +        verify(mSession).authenticate(operationId, mUserId); + +        cancellationSignal.cancel(); + +        verify(mSession).cancel(); +    } + +    @Test +    public void testDetectInteraction() throws RemoteException { +        final ICancellationSignal cancellationSignal = mAidlToHidlAdapter.detectInteraction(); + +        verify(mSession).authenticate(0 /* operationId */, mUserId); + +        cancellationSignal.cancel(); + +        verify(mSession).cancel(); +    } + +    @Test +    public void testEnumerateEnrollments() throws RemoteException { +        mAidlToHidlAdapter.enumerateEnrollments(); + +        verify(mSession).enumerate(); +    } + +    @Test +    public void testRemoveEnrollment() throws RemoteException { +        final int[] enrollmentIds = new int[]{1}; +        mAidlToHidlAdapter.removeEnrollments(enrollmentIds); + +        verify(mSession).remove(mUserId, enrollmentIds[0]); +    } + +    @Test +    public void testRemoveMultipleEnrollments() throws RemoteException { +        final int[] enrollmentIds = new int[]{1, 2}; +        mAidlToHidlAdapter.removeEnrollments(enrollmentIds); + +        verify(mSession).remove(mUserId, 0); +    } + +    @Test +    public void testResetLockout() throws RemoteException { +        mAidlToHidlAdapter.resetLockout(mHardwareAuthToken); + +        verify(mAidlResponseHandler).onLockoutCleared(); +    } +} diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java index e8cbcf9a6874..a3d415e4918f 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java @@ -337,11 +337,7 @@ public class VirtualDeviceManagerServiceTest {          LocalServices.removeServiceForTest(DisplayManagerInternal.class);          LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock); -        mSetFlagsRule.disableFlags(Flags.FLAG_VDM_PUBLIC_APIS); -        mSetFlagsRule.disableFlags(Flags.FLAG_DYNAMIC_POLICY); -        mSetFlagsRule.disableFlags(Flags.FLAG_STREAM_PERMISSIONS); -        mSetFlagsRule.disableFlags(Flags.FLAG_VDM_CUSTOM_HOME); -        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_NATIVE_VDM); +        mSetFlagsRule.initAllFlagsToReleaseConfigDefault();          doReturn(true).when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());          doNothing().when(mInputManagerInternalMock).setPointerAcceleration(anyFloat(), anyInt()); diff --git a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java index 67b70684eede..5cc84b197e03 100644 --- a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java @@ -16,6 +16,8 @@  package com.android.server.contentcapture; +import static android.view.contentprotection.flags.Flags.FLAG_PARSE_GROUPS_CONFIG_ENABLED; +  import static com.google.common.truth.Truth.assertThat;  import static org.mockito.ArgumentMatchers.anyInt; @@ -33,6 +35,7 @@ import android.content.Context;  import android.content.pm.PackageManager;  import android.content.pm.ParceledListSlice;  import android.content.pm.UserInfo; +import android.platform.test.flag.junit.SetFlagsRule;  import android.service.contentcapture.ContentCaptureServiceInfo;  import android.view.contentcapture.ContentCaptureEvent; @@ -56,6 +59,9 @@ import org.mockito.Mock;  import org.mockito.junit.MockitoJUnit;  import org.mockito.junit.MockitoRule; +import java.util.Collections; +import java.util.List; +  /**   * Test for {@link ContentCaptureManagerService}.   * @@ -84,6 +90,8 @@ public class ContentCaptureManagerServiceTest {      @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); +    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); +      @Mock private UserManagerInternal mMockUserManagerInternal;      @Mock private ContentProtectionBlocklistManager mMockContentProtectionBlocklistManager; @@ -96,6 +104,10 @@ public class ContentCaptureManagerServiceTest {      private boolean mDevCfgEnableContentProtectionReceiver; +    private List<List<String>> mDevCfgContentProtectionRequiredGroups = List.of(List.of("a")); + +    private List<List<String>> mDevCfgContentProtectionOptionalGroups = Collections.emptyList(); +      private int mContentProtectionBlocklistManagersCreated;      private int mContentProtectionServiceInfosCreated; @@ -367,7 +379,21 @@ public class ContentCaptureManagerServiceTest {      }      @Test -    public void isContentProtectionReceiverEnabled_withoutManagers() { +    public void isContentProtectionReceiverEnabled_true() { +        when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true); +        when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true); +        mDevCfgEnableContentProtectionReceiver = true; +        mContentCaptureManagerService = new TestContentCaptureManagerService(); + +        boolean actual = +                mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted( +                        USER_ID, PACKAGE_NAME); + +        assertThat(actual).isTrue(); +    } + +    @Test +    public void isContentProtectionReceiverEnabled_false_withoutManagers() {          boolean actual =                  mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(                          USER_ID, PACKAGE_NAME); @@ -378,7 +404,7 @@ public class ContentCaptureManagerServiceTest {      }      @Test -    public void isContentProtectionReceiverEnabled_disabledWithFlag() { +    public void isContentProtectionReceiverEnabled_false_disabledWithFlag() {          mDevCfgEnableContentProtectionReceiver = true;          mContentCaptureManagerService = new TestContentCaptureManagerService();          mContentCaptureManagerService.mDevCfgEnableContentProtectionReceiver = false; @@ -393,6 +419,22 @@ public class ContentCaptureManagerServiceTest {      }      @Test +    public void isContentProtectionReceiverEnabled_false_emptyGroups() { +        mDevCfgEnableContentProtectionReceiver = true; +        mDevCfgContentProtectionRequiredGroups = Collections.emptyList(); +        mDevCfgContentProtectionOptionalGroups = Collections.emptyList(); +        mContentCaptureManagerService = new TestContentCaptureManagerService(); + +        boolean actual = +                mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted( +                        USER_ID, PACKAGE_NAME); + +        assertThat(actual).isFalse(); +        verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt()); +        verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString()); +    } + +    @Test      public void onLoginDetected_disabledAfterConstructor() {          mDevCfgEnableContentProtectionReceiver = true;          mContentCaptureManagerService = new TestContentCaptureManagerService(); @@ -437,29 +479,91 @@ public class ContentCaptureManagerServiceTest {      }      @Test -    public void parseContentProtectionGroupsConfig_null() { +    public void parseContentProtectionGroupsConfig_disabled_null() { +        mSetFlagsRule.disableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED);          ContentCaptureManagerService service = new ContentCaptureManagerService(sContext); +          assertThat(service.parseContentProtectionGroupsConfig(null)).isEmpty();      }      @Test -    public void parseContentProtectionGroupsConfig_empty() { +    public void parseContentProtectionGroupsConfig_disabled_empty() { +        mSetFlagsRule.disableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED);          ContentCaptureManagerService service = new ContentCaptureManagerService(sContext); +          assertThat(service.parseContentProtectionGroupsConfig("")).isEmpty();      }      @Test -    public void parseContentProtectionGroupsConfig_notEmpty() { +    public void parseContentProtectionGroupsConfig_disabled_notEmpty() { +        mSetFlagsRule.disableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED);          ContentCaptureManagerService service = new ContentCaptureManagerService(sContext); +          assertThat(service.parseContentProtectionGroupsConfig("a")).isEmpty();      } +    @Test +    public void parseContentProtectionGroupsConfig_enabled_null() { +        mSetFlagsRule.enableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED); +        ContentCaptureManagerService service = new ContentCaptureManagerService(sContext); + +        assertThat(service.parseContentProtectionGroupsConfig(null)).isEmpty(); +    } + +    @Test +    public void parseContentProtectionGroupsConfig_enabled_empty() { +        mSetFlagsRule.enableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED); +        ContentCaptureManagerService service = new ContentCaptureManagerService(sContext); + +        assertThat(service.parseContentProtectionGroupsConfig("")).isEmpty(); +    } + +    @Test +    public void parseContentProtectionGroupsConfig_enabled_singleValue() { +        mSetFlagsRule.enableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED); +        ContentCaptureManagerService service = new ContentCaptureManagerService(sContext); + +        assertThat(service.parseContentProtectionGroupsConfig("a")) +                .isEqualTo(List.of(List.of("a"))); +    } + +    @Test +    public void parseContentProtectionGroupsConfig_enabled_multipleValues() { +        mSetFlagsRule.enableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED); +        ContentCaptureManagerService service = new ContentCaptureManagerService(sContext); + +        assertThat(service.parseContentProtectionGroupsConfig("a,b")) +                .isEqualTo(List.of(List.of("a", "b"))); +    } + +    @Test +    public void parseContentProtectionGroupsConfig_enabled_multipleGroups() { +        mSetFlagsRule.enableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED); +        ContentCaptureManagerService service = new ContentCaptureManagerService(sContext); + +        assertThat(service.parseContentProtectionGroupsConfig("a,b;c;d,e")) +                .isEqualTo(List.of(List.of("a", "b"), List.of("c"), List.of("d", "e"))); +    } + +    @Test +    public void parseContentProtectionGroupsConfig_enabled_emptyValues() { +        mSetFlagsRule.enableFlags(FLAG_PARSE_GROUPS_CONFIG_ENABLED); +        ContentCaptureManagerService service = new ContentCaptureManagerService(sContext); + +        assertThat(service.parseContentProtectionGroupsConfig("a;b;;;c;,d,,e,;,;")) +                .isEqualTo(List.of(List.of("a"), List.of("b"), List.of("c"), List.of("d", "e"))); +    } +      private class TestContentCaptureManagerService extends ContentCaptureManagerService {          TestContentCaptureManagerService() {              super(sContext);              this.mDevCfgEnableContentProtectionReceiver =                      ContentCaptureManagerServiceTest.this.mDevCfgEnableContentProtectionReceiver; +            this.mDevCfgContentProtectionRequiredGroups = +                    ContentCaptureManagerServiceTest.this.mDevCfgContentProtectionRequiredGroups; +            this.mDevCfgContentProtectionOptionalGroups = +                    ContentCaptureManagerServiceTest.this.mDevCfgContentProtectionOptionalGroups;          }          @Override diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java index 9b32a809d2b5..de3cfbf859ff 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java @@ -54,8 +54,8 @@ import com.android.internal.widget.LockPatternUtils;  import com.android.internal.widget.LockSettingsInternal;  import com.android.server.AlarmManagerInternal;  import com.android.server.LocalServices; -import com.android.server.PersistentDataBlockManagerInternal;  import com.android.server.net.NetworkPolicyManagerInternal; +import com.android.server.pdb.PersistentDataBlockManagerInternal;  import com.android.server.pm.PackageManagerLocal;  import com.android.server.pm.UserManagerInternal;  import com.android.server.wm.ActivityTaskManagerInternal; diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java index 16fdfb16444a..76aa40c04e3d 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java @@ -77,7 +77,7 @@ import com.android.internal.util.test.FakeSettingsProvider;  import com.android.internal.widget.LockPatternUtils;  import com.android.internal.widget.LockSettingsInternal;  import com.android.server.AlarmManagerInternal; -import com.android.server.PersistentDataBlockManagerInternal; +import com.android.server.pdb.PersistentDataBlockManagerInternal;  import com.android.server.net.NetworkPolicyManagerInternal;  import com.android.server.pm.PackageManagerLocal;  import com.android.server.pm.UserManagerInternal; diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java index a029db922372..fa3c7a4c4769 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java @@ -22,7 +22,7 @@ import static org.mockito.Mockito.mock;  import android.content.Context; -import com.android.server.PersistentDataBlockManagerInternal; +import com.android.server.pdb.PersistentDataBlockManagerInternal;  import org.mockito.invocation.InvocationOnMock;  import org.mockito.stubbing.Answer; diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java index 23f14f8468bb..02b86db6ab6f 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java @@ -53,8 +53,8 @@ import androidx.test.runner.AndroidJUnit4;  import com.android.internal.util.test.FakeSettingsProvider;  import com.android.internal.util.test.FakeSettingsProviderRule; -import com.android.server.PersistentDataBlockManagerInternal;  import com.android.server.locksettings.LockSettingsStorage.PersistentData; +import com.android.server.pdb.PersistentDataBlockManagerInternal;  import org.junit.After;  import org.junit.Before; diff --git a/services/tests/servicestests/src/com/android/server/media/BluetoothRouteControllerTest.java b/services/tests/servicestests/src/com/android/server/media/BluetoothRouteControllerTest.java index 75d71daa208d..06f117bdbd65 100644 --- a/services/tests/servicestests/src/com/android/server/media/BluetoothRouteControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/media/BluetoothRouteControllerTest.java @@ -38,9 +38,10 @@ import org.junit.runners.JUnit4;  public class BluetoothRouteControllerTest {      private final BluetoothRouteController.BluetoothRoutesUpdatedListener -            mBluetoothRoutesUpdatedListener = routes -> { -                // Empty on purpose. -            }; +            mBluetoothRoutesUpdatedListener = +                    () -> { +                        // Empty on purpose. +                    };      @Rule      public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); diff --git a/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java b/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java index ec4b8a804533..14b121d3945c 100644 --- a/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java @@ -38,7 +38,7 @@ import org.junit.runners.JUnit4;  public class DeviceRouteControllerTest {      private final DeviceRouteController.OnDeviceRouteChangedListener mOnDeviceRouteChangedListener = -            deviceRoute -> { +            () -> {                  // Empty on purpose.              }; diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java index d85768dd7588..f94aff706a67 100644 --- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java @@ -26,6 +26,7 @@ import static android.media.projection.ReviewGrantedConsentResult.UNKNOWN;  import static android.view.Display.DEFAULT_DISPLAY;  import static android.view.Display.INVALID_DISPLAY; +import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS;  import static com.google.common.truth.Truth.assertThat;  import static org.mockito.ArgumentMatchers.any; @@ -66,6 +67,7 @@ import androidx.test.filters.FlakyTest;  import androidx.test.filters.SmallTest;  import androidx.test.platform.app.InstrumentationRegistry; +import com.android.internal.util.FrameworkStatsLog;  import com.android.server.LocalServices;  import com.android.server.testutils.OffsettableClock;  import com.android.server.wm.WindowManagerInternal; @@ -128,6 +130,14 @@ public class MediaProjectionManagerServiceTest {                  }              }; +    private final MediaProjectionManagerService.Injector mMediaProjectionMetricsLoggerInjector = +            new MediaProjectionManagerService.Injector() { +                @Override +                MediaProjectionMetricsLogger mediaProjectionMetricsLogger() { +                    return mMediaProjectionMetricsLogger; +                } +            }; +      private Context mContext;      private MediaProjectionManagerService mService;      private OffsettableClock mClock; @@ -142,6 +152,8 @@ public class MediaProjectionManagerServiceTest {      private PackageManager mPackageManager;      @Mock      private IMediaProjectionWatcherCallback mWatcherCallback; +    @Mock +    private MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;      @Captor      private ArgumentCaptor<ContentRecordingSession> mSessionCaptor; @@ -734,6 +746,25 @@ public class MediaProjectionManagerServiceTest {      }      @Test +    public void setContentRecordingSession_success_logsCaptureInProgress() +            throws Exception { +        mService.addCallback(mWatcherCallback); +        MediaProjectionManagerService service = new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector); +        MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(); +        projection.start(mIMediaProjectionCallback); +        doReturn(true).when(mWindowManagerInternal).setContentRecordingSession( +                any(ContentRecordingSession.class)); + +        service.setContentRecordingSession(DISPLAY_SESSION); + +        verify(mMediaProjectionMetricsLogger).notifyProjectionStateChange( +                projection.uid, +                MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS, +                FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN +        ); +    } + +    @Test      public void setContentRecordingSession_notifiesListenersOnCallbackLooper()              throws Exception {          mService = new MediaProjectionManagerService(mContext, mTestLooperInjector); diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionSessionIdGeneratorTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionSessionIdGeneratorTest.java new file mode 100644 index 000000000000..07cdf4df47ae --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionSessionIdGeneratorTest.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.media.projection; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.content.SharedPreferences; +import android.platform.test.annotations.Presubmit; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; + +/** + * Tests for the {@link MediaProjectionSessionIdGenerator} class. + * + * <p>Build/Install/Run: atest FrameworksServicesTests:MediaProjectionSessionIdGeneratorTest + */ +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class MediaProjectionSessionIdGeneratorTest { + +    private static final String TEST_PREFS_FILE = "media-projection-session-id-test"; + +    private final Context mContext = +            InstrumentationRegistry.getInstrumentation().getTargetContext(); +    private final File mSharedPreferencesFile = new File(mContext.getCacheDir(), TEST_PREFS_FILE); +    private final SharedPreferences mSharedPreferences = createSharePreferences(); +    private final MediaProjectionSessionIdGenerator mGenerator = +            createGenerator(mSharedPreferences); + +    @Before +    public void setUp() { +        mSharedPreferences.edit().clear().commit(); +    } + +    @After +    public void tearDown() { +        mSharedPreferences.edit().clear().commit(); +        mSharedPreferencesFile.delete(); +    } + +    @Test +    public void getCurrentSessionId_byDefault_returns0() { +        assertThat(mGenerator.getCurrentSessionId()).isEqualTo(0); +    } + +    @Test +    public void getCurrentSessionId_multipleTimes_returnsSameValue() { +        assertThat(mGenerator.getCurrentSessionId()).isEqualTo(0); +        assertThat(mGenerator.getCurrentSessionId()).isEqualTo(0); +        assertThat(mGenerator.getCurrentSessionId()).isEqualTo(0); +    } + +    @Test +    public void createAndGetNewSessionId_returnsIncrementedId() { +        int previousValue = mGenerator.getCurrentSessionId(); + +        int newValue = mGenerator.createAndGetNewSessionId(); + +        assertThat(newValue).isEqualTo(previousValue + 1); +    } + +    @Test +    public void createAndGetNewSessionId_persistsNewValue() { +        int newValue = mGenerator.createAndGetNewSessionId(); + +        MediaProjectionSessionIdGenerator newInstance = createGenerator(createSharePreferences()); + +        assertThat(newInstance.getCurrentSessionId()).isEqualTo(newValue); +    } + +    private SharedPreferences createSharePreferences() { +        return mContext.getSharedPreferences(mSharedPreferencesFile, Context.MODE_PRIVATE); +    } + +    private MediaProjectionSessionIdGenerator createGenerator(SharedPreferences sharedPreferences) { +        return new MediaProjectionSessionIdGenerator(sharedPreferences); +    } +} diff --git a/services/tests/servicestests/src/com/android/server/pdb/OWNERS b/services/tests/servicestests/src/com/android/server/pdb/OWNERS new file mode 100644 index 000000000000..6dfb888dedad --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/pdb/OWNERS @@ -0,0 +1 @@ +include /services/core/java/com/android/server/pdb/OWNERS diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java index 8e7ba7030e82..dd7dec0bdb2b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java @@ -130,12 +130,22 @@ public class BackNavigationControllerTests extends WindowTestsBase {          // verify if back animation would start.          assertTrue("Animation scheduled", backNavigationInfo.isPrepareRemoteAnimation()); -        // reset drawing status +        // reset drawing status to test translucent activity          backNavigationInfo.onBackNavigationFinished(false);          mBackNavigationController.clearBackAnimations(); -        topTask.forAllWindows(w -> { -            makeWindowVisibleAndDrawn(w); -        }, true); +        final ActivityRecord topActivity = topTask.getTopMostActivity(); +        makeWindowVisibleAndDrawn(topActivity.findMainWindow()); +        // simulate translucent +        topActivity.setOccludesParent(false); +        backNavigationInfo = startBackNavigation(); +        assertThat(typeToString(backNavigationInfo.getType())) +                .isEqualTo(typeToString(BackNavigationInfo.TYPE_CALLBACK)); + +        // reset drawing status to test keyguard occludes +        topActivity.setOccludesParent(true); +        backNavigationInfo.onBackNavigationFinished(false); +        mBackNavigationController.clearBackAnimations(); +        makeWindowVisibleAndDrawn(topActivity.findMainWindow());          setupKeyguardOccluded();          backNavigationInfo = startBackNavigation();          assertThat(typeToString(backNavigationInfo.getType())) @@ -201,9 +211,7 @@ public class BackNavigationControllerTests extends WindowTestsBase {          // reset drawing status          backNavigationInfo.onBackNavigationFinished(false);          mBackNavigationController.clearBackAnimations(); -        testCase.recordFront.forAllWindows(w -> { -            makeWindowVisibleAndDrawn(w); -        }, true); +        makeWindowVisibleAndDrawn(testCase.recordFront.findMainWindow());          setupKeyguardOccluded();          backNavigationInfo = startBackNavigation();          assertThat(typeToString(backNavigationInfo.getType())) diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java index 233a2076a867..84d42d42f834 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java @@ -18,16 +18,20 @@ package com.android.server.wm;  import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;  import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset;  import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;  import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;  import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;  import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER; +import static com.android.server.wm.utils.LastCallVerifier.lastCall;  import static org.junit.Assert.assertNotNull; +import static org.junit.Assume.assumeFalse; +import static org.junit.Assume.assumeTrue;  import static org.mockito.ArgumentMatchers.any;  import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt;  import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.reset;  import static org.mockito.Mockito.when;  import android.graphics.Rect; @@ -35,8 +39,9 @@ import android.platform.test.annotations.Presubmit;  import android.view.SurfaceControl;  import android.view.SurfaceSession; -import com.android.server.wm.SurfaceAnimator.AnimationType;  import com.android.server.testutils.StubTransaction; +import com.android.server.wm.utils.MockAnimationAdapter; +import com.android.window.flags.Flags;  import org.junit.Before;  import org.junit.Test; @@ -118,102 +123,168 @@ public class DimmerTests extends WindowTestsBase {          }      } -    private MockSurfaceBuildingContainer mHost; -    private Dimmer mDimmer; -    private SurfaceControl.Transaction mTransaction; -    private Dimmer.SurfaceAnimatorStarter mSurfaceAnimatorStarter; +    static class MockAnimationAdapterFactory extends SmoothDimmer.AnimationAdapterFactory { +        public AnimationAdapter get(LocalAnimationAdapter.AnimationSpec alphaAnimationSpec, +                SurfaceAnimationRunner runner) { +            return sTestAnimation; +        } +    } -    private static class SurfaceAnimatorStarterImpl implements Dimmer.SurfaceAnimatorStarter { +    private static class SurfaceAnimatorStarterImpl implements LegacyDimmer.SurfaceAnimatorStarter {          @Override          public void startAnimation(SurfaceAnimator surfaceAnimator, SurfaceControl.Transaction t, -                AnimationAdapter anim, boolean hidden, @AnimationType int type) { +                AnimationAdapter anim, boolean hidden, @SurfaceAnimator.AnimationType int type) {              surfaceAnimator.mStaticAnimationFinishedCallback.onAnimationFinished(type, anim);          }      } +    private MockSurfaceBuildingContainer mHost; +    private Dimmer mDimmer; +    private SurfaceControl.Transaction mTransaction; +    private TestWindowContainer mChild; +    private static AnimationAdapter sTestAnimation; +    private static LegacyDimmer.SurfaceAnimatorStarter sSurfaceAnimatorStarter; +      @Before      public void setUp() throws Exception {          mHost = new MockSurfaceBuildingContainer(mWm); -        mSurfaceAnimatorStarter = spy(new SurfaceAnimatorStarterImpl());          mTransaction = spy(StubTransaction.class); -        mDimmer = new Dimmer(mHost, mSurfaceAnimatorStarter); +        mChild = new TestWindowContainer(mWm); +        if (Flags.dimmerRefactor()) { +            sTestAnimation = spy(new MockAnimationAdapter()); +            mDimmer = new SmoothDimmer(mHost, new MockAnimationAdapterFactory()); +        } else { +            sSurfaceAnimatorStarter = spy(new SurfaceAnimatorStarterImpl()); +            mDimmer = new LegacyDimmer(mHost, sSurfaceAnimatorStarter); +        }      }      @Test      public void testUpdateDimsAppliesCrop() { -        TestWindowContainer child = new TestWindowContainer(mWm); -        mHost.addChild(child, 0); +        mHost.addChild(mChild, 0);          final float alpha = 0.8f; -        mDimmer.dimAbove(child, alpha); +        mDimmer.dimAbove(mChild, alpha);          int width = 100;          int height = 300; -        mDimmer.mDimState.mDimBounds.set(0, 0, width, height); +        mDimmer.getDimBounds().set(0, 0, width, height);          mDimmer.updateDims(mTransaction); -        verify(mTransaction).setWindowCrop(getDimLayer(), width, height); -        verify(mTransaction).show(getDimLayer()); +        verify(mTransaction).setWindowCrop(mDimmer.getDimLayer(), width, height); +        verify(mTransaction).show(mDimmer.getDimLayer());      }      @Test -    public void testDimAboveWithChildCreatesSurfaceAboveChild() { -        TestWindowContainer child = new TestWindowContainer(mWm); -        mHost.addChild(child, 0); +    public void testDimAboveWithChildCreatesSurfaceAboveChild_Smooth() { +        assumeTrue(Flags.dimmerRefactor()); +        final float alpha = 0.8f; +        mHost.addChild(mChild, 0); +        mDimmer.dimAbove(mChild, alpha); +        SurfaceControl dimLayer = mDimmer.getDimLayer(); + +        assertNotNull("Dimmer should have created a surface", dimLayer); + +        mDimmer.updateDims(mTransaction); +        verify(sTestAnimation).startAnimation(eq(dimLayer), eq(mTransaction), +                anyInt(), any(SurfaceAnimator.OnAnimationFinishedCallback.class)); +        verify(mTransaction).setRelativeLayer(dimLayer, mChild.mControl, 1); +        verify(mTransaction, lastCall()).setAlpha(dimLayer, alpha); +    } +    @Test +    public void testDimAboveWithChildCreatesSurfaceAboveChild_Legacy() { +        assumeFalse(Flags.dimmerRefactor());          final float alpha = 0.8f; -        mDimmer.dimAbove(child, alpha); -        SurfaceControl dimLayer = getDimLayer(); +        mHost.addChild(mChild, 0); +        mDimmer.dimAbove(mChild, alpha); +        SurfaceControl dimLayer = mDimmer.getDimLayer();          assertNotNull("Dimmer should have created a surface", dimLayer);          verify(mHost.getPendingTransaction()).setAlpha(dimLayer, alpha); -        verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, child.mControl, 1); +        verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, mChild.mControl, 1);      }      @Test -    public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild() { -        TestWindowContainer child = new TestWindowContainer(mWm); -        mHost.addChild(child, 0); +    public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild_Smooth() { +        assumeTrue(Flags.dimmerRefactor()); +        final float alpha = 0.7f; +        mHost.addChild(mChild, 0); +        mDimmer.dimBelow(mChild, alpha, 50); +        SurfaceControl dimLayer = mDimmer.getDimLayer(); -        final float alpha = 0.8f; -        mDimmer.dimBelow(child, alpha, 0); -        SurfaceControl dimLayer = getDimLayer(); +        assertNotNull("Dimmer should have created a surface", dimLayer); + +        mDimmer.updateDims(mTransaction); +        verify(sTestAnimation).startAnimation(eq(dimLayer), eq(mTransaction), +                anyInt(), any(SurfaceAnimator.OnAnimationFinishedCallback.class)); +        verify(mTransaction).setRelativeLayer(dimLayer, mChild.mControl, -1); +        verify(mTransaction, lastCall()).setAlpha(dimLayer, alpha); +        verify(mTransaction).setBackgroundBlurRadius(dimLayer, 50); +    } + +    @Test +    public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild_Legacy() { +        assumeFalse(Flags.dimmerRefactor()); +        final float alpha = 0.7f; +        mHost.addChild(mChild, 0); +        mDimmer.dimBelow(mChild, alpha, 50); +        SurfaceControl dimLayer = mDimmer.getDimLayer();          assertNotNull("Dimmer should have created a surface", dimLayer);          verify(mHost.getPendingTransaction()).setAlpha(dimLayer, alpha); -        verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, child.mControl, -1); +        verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, mChild.mControl, -1);      }      @Test -    public void testDimBelowWithChildSurfaceDestroyedWhenReset() { -        TestWindowContainer child = new TestWindowContainer(mWm); -        mHost.addChild(child, 0); +    public void testDimBelowWithChildSurfaceDestroyedWhenReset_Smooth() { +        assumeTrue(Flags.dimmerRefactor()); +        mHost.addChild(mChild, 0); + +        final float alpha = 0.8f; +        // Dim once +        mDimmer.dimBelow(mChild, alpha, 0); +        SurfaceControl dimLayer = mDimmer.getDimLayer(); +        mDimmer.updateDims(mTransaction); +        // Reset, and don't dim +        mDimmer.resetDimStates(); +        mDimmer.updateDims(mTransaction); +        verify(mTransaction).show(dimLayer); +        verify(mTransaction).remove(dimLayer); +    } + +    @Test +    public void testDimBelowWithChildSurfaceDestroyedWhenReset_Legacy() { +        assumeFalse(Flags.dimmerRefactor()); +        mHost.addChild(mChild, 0);          final float alpha = 0.8f; -        mDimmer.dimAbove(child, alpha); -        SurfaceControl dimLayer = getDimLayer(); +        mDimmer.dimAbove(mChild, alpha); +        SurfaceControl dimLayer = mDimmer.getDimLayer();          mDimmer.resetDimStates();          mDimmer.updateDims(mTransaction); -        verify(mSurfaceAnimatorStarter).startAnimation(any(SurfaceAnimator.class), any( -                SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean(), +        verify(sSurfaceAnimatorStarter).startAnimation(any(SurfaceAnimator.class), +                any(SurfaceControl.Transaction.class), any(AnimationAdapter.class), +                anyBoolean(),                  eq(ANIMATION_TYPE_DIMMER));          verify(mHost.getPendingTransaction()).remove(dimLayer);      }      @Test      public void testDimBelowWithChildSurfaceNotDestroyedWhenPersisted() { -        TestWindowContainer child = new TestWindowContainer(mWm); -        mHost.addChild(child, 0); +        mHost.addChild(mChild, 0);          final float alpha = 0.8f; -        mDimmer.dimAbove(child, alpha); -        SurfaceControl dimLayer = getDimLayer(); +        // Dim once +        mDimmer.dimBelow(mChild, alpha, 0); +        SurfaceControl dimLayer = mDimmer.getDimLayer(); +        mDimmer.updateDims(mTransaction); +        // Reset and dim again          mDimmer.resetDimStates(); -        mDimmer.dimAbove(child, alpha); - +        mDimmer.dimAbove(mChild, alpha);          mDimmer.updateDims(mTransaction);          verify(mTransaction).show(dimLayer);          verify(mTransaction, never()).remove(dimLayer); @@ -221,14 +292,12 @@ public class DimmerTests extends WindowTestsBase {      @Test      public void testDimUpdateWhileDimming() { -        TestWindowContainer child = new TestWindowContainer(mWm); -        mHost.addChild(child, 0); - +        mHost.addChild(mChild, 0);          final float alpha = 0.8f; -        mDimmer.dimAbove(child, alpha); -        final Rect bounds = mDimmer.mDimState.mDimBounds; +        mDimmer.dimAbove(mChild, alpha); +        final Rect bounds = mDimmer.getDimBounds(); -        SurfaceControl dimLayer = getDimLayer(); +        SurfaceControl dimLayer = mDimmer.getDimLayer();          bounds.set(0, 0, 10, 10);          mDimmer.updateDims(mTransaction);          verify(mTransaction).setWindowCrop(dimLayer, bounds.width(), bounds.height()); @@ -242,41 +311,56 @@ public class DimmerTests extends WindowTestsBase {      }      @Test -    public void testRemoveDimImmediately() { -        TestWindowContainer child = new TestWindowContainer(mWm); -        mHost.addChild(child, 0); +    public void testRemoveDimImmediately_Smooth() { +        assumeTrue(Flags.dimmerRefactor()); +        mHost.addChild(mChild, 0); +        mDimmer.dimAbove(mChild, 1); +        SurfaceControl dimLayer = mDimmer.getDimLayer(); +        mDimmer.updateDims(mTransaction); +        verify(mTransaction, times(1)).show(dimLayer); -        mDimmer.dimAbove(child, 1); -        SurfaceControl dimLayer = getDimLayer(); +        reset(sTestAnimation); +        mDimmer.dontAnimateExit(); +        mDimmer.resetDimStates(); +        mDimmer.updateDims(mTransaction); +        verify(sTestAnimation, never()).startAnimation( +                any(SurfaceControl.class), any(SurfaceControl.Transaction.class), +                anyInt(), any(SurfaceAnimator.OnAnimationFinishedCallback.class)); +        verify(mTransaction).remove(dimLayer); +    } + +    @Test +    public void testRemoveDimImmediately_Legacy() { +        assumeFalse(Flags.dimmerRefactor()); +        mHost.addChild(mChild, 0); +        mDimmer.dimAbove(mChild, 1); +        SurfaceControl dimLayer = mDimmer.getDimLayer();          mDimmer.updateDims(mTransaction);          verify(mTransaction, times(1)).show(dimLayer); -        reset(mSurfaceAnimatorStarter); +        reset(sSurfaceAnimatorStarter);          mDimmer.dontAnimateExit();          mDimmer.resetDimStates();          mDimmer.updateDims(mTransaction); -        verify(mSurfaceAnimatorStarter, never()).startAnimation(any(SurfaceAnimator.class), any( -                SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean(), +        verify(sSurfaceAnimatorStarter, never()).startAnimation(any(SurfaceAnimator.class), +                any(SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean(),                  eq(ANIMATION_TYPE_DIMMER));          verify(mTransaction).remove(dimLayer);      }      @Test -    public void testDimmerWithBlurUpdatesTransaction() { +    public void testDimmerWithBlurUpdatesTransaction_Legacy() { +        assumeFalse(Flags.dimmerRefactor());          TestWindowContainer child = new TestWindowContainer(mWm);          mHost.addChild(child, 0);          final int blurRadius = 50;          mDimmer.dimBelow(child, 0, blurRadius); -        SurfaceControl dimLayer = getDimLayer(); +        SurfaceControl dimLayer = mDimmer.getDimLayer();          assertNotNull("Dimmer should have created a surface", dimLayer);          verify(mHost.getPendingTransaction()).setBackgroundBlurRadius(dimLayer, blurRadius);          verify(mHost.getPendingTransaction()).setRelativeLayer(dimLayer, child.mControl, -1);      } - -    private SurfaceControl getDimLayer() { -        return mDimmer.mDimState.mDimLayer; -    }  } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java index 2bf13857e537..6235b3b67145 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -26,7 +26,9 @@ import static android.view.WindowManager.TRANSIT_NONE;  import static android.view.WindowManager.TRANSIT_OPEN;  import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT;  import static android.window.TaskFragmentOperation.OP_TYPE_DELETE_TASK_FRAGMENT; +import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_BOTTOM_OF_TASK;  import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_FRONT; +import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_TOP_OF_TASK;  import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;  import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;  import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS; @@ -1655,6 +1657,127 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {          assertEquals(frontMostTaskFragment, tf0);      } +    @Test +    public void testApplyTransaction_reorderToBottomOfTask() { +        mController.unregisterOrganizer(mIOrganizer); +        mController.registerOrganizerInternal(mIOrganizer, true /* isSystemOrganizer */); +        final Task task = createTask(mDisplayContent); +        // Create a non-embedded Activity at the bottom. +        final ActivityRecord bottomActivity = new ActivityBuilder(mAtm) +                .setTask(task) +                .build(); +        final TaskFragment tf0 = createTaskFragment(task); +        final TaskFragment tf1 = createTaskFragment(task); +        // Create a non-embedded Activity at the top. +        final ActivityRecord topActivity = new ActivityBuilder(mAtm) +                .setTask(task) +                .build(); + +        // Ensure correct order of the children before the operation +        assertEquals(topActivity, task.getChildAt(3).asActivityRecord()); +        assertEquals(tf1, task.getChildAt(2).asTaskFragment()); +        assertEquals(tf0, task.getChildAt(1).asTaskFragment()); +        assertEquals(bottomActivity, task.getChildAt(0).asActivityRecord()); + +        // Reorder TaskFragment to bottom +        final TaskFragmentOperation operation = new TaskFragmentOperation.Builder( +                OP_TYPE_REORDER_TO_BOTTOM_OF_TASK).build(); +        mTransaction.addTaskFragmentOperation(tf1.getFragmentToken(), operation); +        assertApplyTransactionAllowed(mTransaction); + +        // Ensure correct order of the children after the operation +        assertEquals(topActivity, task.getChildAt(3).asActivityRecord()); +        assertEquals(tf0, task.getChildAt(2).asTaskFragment()); +        assertEquals(bottomActivity, task.getChildAt(1).asActivityRecord()); +        assertEquals(tf1, task.getChildAt(0).asTaskFragment()); +    } + +    @Test +    public void testApplyTransaction_reorderToTopOfTask() { +        mController.unregisterOrganizer(mIOrganizer); +        mController.registerOrganizerInternal(mIOrganizer, true /* isSystemOrganizer */); +        final Task task = createTask(mDisplayContent); +        // Create a non-embedded Activity at the bottom. +        final ActivityRecord bottomActivity = new ActivityBuilder(mAtm) +                .setTask(task) +                .build(); +        final TaskFragment tf0 = createTaskFragment(task); +        final TaskFragment tf1 = createTaskFragment(task); +        // Create a non-embedded Activity at the top. +        final ActivityRecord topActivity = new ActivityBuilder(mAtm) +                .setTask(task) +                .build(); + +        // Ensure correct order of the children before the operation +        assertEquals(topActivity, task.getChildAt(3).asActivityRecord()); +        assertEquals(tf1, task.getChildAt(2).asTaskFragment()); +        assertEquals(tf0, task.getChildAt(1).asTaskFragment()); +        assertEquals(bottomActivity, task.getChildAt(0).asActivityRecord()); + +        // Reorder TaskFragment to top +        final TaskFragmentOperation operation = new TaskFragmentOperation.Builder( +                OP_TYPE_REORDER_TO_TOP_OF_TASK).build(); +        mTransaction.addTaskFragmentOperation(tf0.getFragmentToken(), operation); +        assertApplyTransactionAllowed(mTransaction); + +        // Ensure correct order of the children after the operation +        assertEquals(tf0, task.getChildAt(3).asTaskFragment()); +        assertEquals(topActivity, task.getChildAt(2).asActivityRecord()); +        assertEquals(tf1, task.getChildAt(1).asTaskFragment()); +        assertEquals(bottomActivity, task.getChildAt(0).asActivityRecord()); +    } + +    @Test +    public void testApplyTransaction_reorderToBottomOfTask_failsIfNotSystemOrganizer() { +        testApplyTransaction_reorder_failsIfNotSystemOrganizer_common( +                OP_TYPE_REORDER_TO_BOTTOM_OF_TASK); +    } + +    @Test +    public void testApplyTransaction_reorderToTopOfTask_failsIfNotSystemOrganizer() { +        testApplyTransaction_reorder_failsIfNotSystemOrganizer_common( +                OP_TYPE_REORDER_TO_TOP_OF_TASK); +    } + +    private void testApplyTransaction_reorder_failsIfNotSystemOrganizer_common( +            @TaskFragmentOperation.OperationType int opType) { +        final Task task = createTask(mDisplayContent); +        // Create a non-embedded Activity at the bottom. +        final ActivityRecord bottomActivity = new ActivityBuilder(mAtm) +                .setTask(task) +                .build(); +        final TaskFragment tf0 = createTaskFragment(task); +        final TaskFragment tf1 = createTaskFragment(task); +        // Create a non-embedded Activity at the top. +        final ActivityRecord topActivity = new ActivityBuilder(mAtm) +                .setTask(task) +                .build(); + +        // Ensure correct order of the children before the operation +        assertEquals(topActivity, task.getChildAt(3).asActivityRecord()); +        assertEquals(tf1, task.getChildAt(2).asTaskFragment()); +        assertEquals(tf0, task.getChildAt(1).asTaskFragment()); +        assertEquals(bottomActivity, task.getChildAt(0).asActivityRecord()); + +        // Apply reorder transaction, which is expected to fail for non-system organizer. +        final TaskFragmentOperation operation = new TaskFragmentOperation.Builder( +                opType).build(); +        mTransaction +                .addTaskFragmentOperation(tf0.getFragmentToken(), operation) +                .setErrorCallbackToken(mErrorToken); +        assertApplyTransactionAllowed(mTransaction); +        // The pending event will be dispatched on the handler (from requestTraversal). +        waitHandlerIdle(mWm.mAnimationHandler); + +        assertTaskFragmentErrorTransaction(opType, SecurityException.class); + +        // Ensure no change to the order of the children after the operation +        assertEquals(topActivity, task.getChildAt(3).asActivityRecord()); +        assertEquals(tf1, task.getChildAt(2).asTaskFragment()); +        assertEquals(tf0, task.getChildAt(1).asTaskFragment()); +        assertEquals(bottomActivity, task.getChildAt(0).asActivityRecord()); +    } +      /**       * Creates a {@link TaskFragment} with the {@link WindowContainerTransaction}. Calls       * {@link WindowOrganizerController#applyTransaction(WindowContainerTransaction)} to apply the @@ -1782,6 +1905,19 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {          assertEquals(activityToken, change.getActivityToken());      } +    /** Setups an embedded TaskFragment. */ +    private TaskFragment createTaskFragment(Task task) { +        final IBinder token = new Binder(); +        TaskFragment taskFragment = new TaskFragmentBuilder(mAtm) +                .setParentTask(task) +                .setFragmentToken(token) +                .setOrganizer(mOrganizer) +                .createActivityCount(1) +                .build(); +        mWindowOrganizerController.mLaunchTaskFragments.put(token, taskFragment); +        return taskFragment; +    } +      /** Setups an embedded TaskFragment in a PIP Task. */      private void setupTaskFragmentInPip() {          mTaskFragment = new TaskFragmentBuilder(mAtm) diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java index 5205bb0038c0..7822071f3933 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java @@ -37,6 +37,8 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;  import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;  import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;  import static com.android.server.wm.ActivityRecord.State.RESUMED; +import static com.android.server.wm.TaskFragment.EMBEDDED_DIM_AREA_PARENT_TASK; +import static com.android.server.wm.TaskFragment.EMBEDDED_DIM_AREA_TASK_FRAGMENT;  import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION;  import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_UNTRUSTED_HOST;  import static com.android.server.wm.WindowContainer.POSITION_TOP; @@ -687,4 +689,24 @@ public class TaskFragmentTest extends WindowTestsBase {          tf0.setIsolatedNav(true);          assertTrue(tf0.isIsolatedNav());      } + +    @Test +    public void testGetDimBounds() { +        final Task task = mTaskFragment.getTask(); +        final Rect taskBounds = task.getBounds(); +        mTaskFragment.setBounds(taskBounds.left, taskBounds.top, taskBounds.left + 10, +                taskBounds.top + 10); +        final Rect taskFragmentBounds = mTaskFragment.getBounds(); + +        // Return Task bounds if dimming on parent Task. +        final Rect dimBounds = new Rect(); +        mTaskFragment.setEmbeddedDimArea(EMBEDDED_DIM_AREA_PARENT_TASK); +        mTaskFragment.getDimBounds(dimBounds); +        assertEquals(taskBounds, dimBounds); + +        // Return TF bounds by default. +        mTaskFragment.setEmbeddedDimArea(EMBEDDED_DIM_AREA_TASK_FRAGMENT); +        mTaskFragment.getDimBounds(dimBounds); +        assertEquals(taskFragmentBounds, dimBounds); +    }  } 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 e86fc366a631..eaeb8049b81a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java @@ -100,6 +100,9 @@ import androidx.test.platform.app.InstrumentationRegistry;  import com.android.compatibility.common.util.AdoptShellPermissionsRule;  import com.android.internal.os.IResultReceiver;  import com.android.server.LocalServices; +import com.android.server.wm.WindowManagerService.WindowContainerInfo; + +import com.google.common.truth.Expect;  import org.junit.Rule;  import org.junit.Test; @@ -125,6 +128,9 @@ public class WindowManagerServiceTests extends WindowTestsBase {              InstrumentationRegistry.getInstrumentation().getUiAutomation(),              ADD_TRUSTED_DISPLAY); +    @Rule +    public Expect mExpect = Expect.create(); +      @Test      public void testIsRequestedOrientationMapped() {          mWm.setOrientationRequestPolicy(/* isIgnoreOrientationRequestDisabled*/ true, @@ -674,64 +680,68 @@ public class WindowManagerServiceTests extends WindowTestsBase {      @Test      public void testGetTaskWindowContainerTokenForLaunchCookie_nullCookie() { -        WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(null); -        assertThat(wct).isNull(); +        WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(null); +        assertThat(wci).isNull();      }      @Test      public void testGetTaskWindowContainerTokenForLaunchCookie_invalidCookie() {          Binder cookie = new Binder("test cookie"); -        WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie); -        assertThat(wct).isNull(); +        WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(cookie); +        assertThat(wci).isNull();          final ActivityRecord testActivity = new ActivityBuilder(mAtm)                  .setCreateTask(true)                  .build(); -        wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie); -        assertThat(wct).isNull(); +        wci = mWm.getTaskWindowContainerInfoForLaunchCookie(cookie); +        assertThat(wci).isNull();      }      @Test      public void testGetTaskWindowContainerTokenForLaunchCookie_validCookie() {          final Binder cookie = new Binder("ginger cookie");          final WindowContainerToken launchRootTask = mock(WindowContainerToken.class); -        setupActivityWithLaunchCookie(cookie, launchRootTask); +        final int uid = 123; +        setupActivityWithLaunchCookie(cookie, launchRootTask, uid); -        WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie); -        assertThat(wct).isEqualTo(launchRootTask); +        WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(cookie); +        mExpect.that(wci.getToken()).isEqualTo(launchRootTask); +        mExpect.that(wci.getUid()).isEqualTo(uid);      }      @Test      public void testGetTaskWindowContainerTokenForLaunchCookie_multipleCookies() {          final Binder cookie1 = new Binder("ginger cookie");          final WindowContainerToken launchRootTask1 = mock(WindowContainerToken.class); -        setupActivityWithLaunchCookie(cookie1, launchRootTask1); +        final int uid1 = 123; +        setupActivityWithLaunchCookie(cookie1, launchRootTask1, uid1);          setupActivityWithLaunchCookie(new Binder("choc chip cookie"), -                mock(WindowContainerToken.class)); +                mock(WindowContainerToken.class), /* uid= */ 456);          setupActivityWithLaunchCookie(new Binder("peanut butter cookie"), -                mock(WindowContainerToken.class)); +                mock(WindowContainerToken.class), /* uid= */ 789); -        WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie1); -        assertThat(wct).isEqualTo(launchRootTask1); +        WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(cookie1); +        mExpect.that(wci.getToken()).isEqualTo(launchRootTask1); +        mExpect.that(wci.getUid()).isEqualTo(uid1);      }      @Test      public void testGetTaskWindowContainerTokenForLaunchCookie_multipleCookies_noneValid() {          setupActivityWithLaunchCookie(new Binder("ginger cookie"), -                mock(WindowContainerToken.class)); +                mock(WindowContainerToken.class), /* uid= */ 123);          setupActivityWithLaunchCookie(new Binder("choc chip cookie"), -                mock(WindowContainerToken.class)); +                mock(WindowContainerToken.class), /* uid= */ 456);          setupActivityWithLaunchCookie(new Binder("peanut butter cookie"), -                mock(WindowContainerToken.class)); +                mock(WindowContainerToken.class), /* uid= */ 789); -        WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie( +        WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(                  new Binder("some other cookie")); -        assertThat(wct).isNull(); +        assertThat(wci).isNull();      }      @Test @@ -778,17 +788,18 @@ public class WindowManagerServiceTests extends WindowTestsBase {      }      @Test -    public void setContentRecordingSession_matchingTask_mutatesSessionWithWindowContainerToken() { +    public void setContentRecordingSession_matchingTask_mutatesSessionWithWindowContainerInfo() {          WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);          Task task = createTask(mDefaultDisplay);          ActivityRecord activityRecord = createActivityRecord(task); -        ContentRecordingSession session = ContentRecordingSession.createTaskSession( -                activityRecord.mLaunchCookie); +        ContentRecordingSession session = +                ContentRecordingSession.createTaskSession(activityRecord.mLaunchCookie);          wmInternal.setContentRecordingSession(session); -        assertThat(session.getTokenToRecord()).isEqualTo( -                task.mRemoteToken.toWindowContainerToken().asBinder()); +        mExpect.that(session.getTokenToRecord()) +                .isEqualTo(task.mRemoteToken.toWindowContainerToken().asBinder()); +        mExpect.that(session.getTargetUid()).isEqualTo(activityRecord.getUid());      }      @Test @@ -1010,12 +1021,12 @@ public class WindowManagerServiceTests extends WindowTestsBase {          }      } -    private void setupActivityWithLaunchCookie(IBinder launchCookie, WindowContainerToken wct) { +    private void setupActivityWithLaunchCookie( +            IBinder launchCookie, WindowContainerToken wct, int uid) {          final WindowContainer.RemoteToken remoteToken = mock(WindowContainer.RemoteToken.class);          when(remoteToken.toWindowContainerToken()).thenReturn(wct); -        final ActivityRecord testActivity = new ActivityBuilder(mAtm) -                .setCreateTask(true) -                .build(); +        final ActivityRecord testActivity = +                new ActivityBuilder(mAtm).setCreateTask(true).setUid(uid).build();          testActivity.mLaunchCookie = launchCookie;          testActivity.getTask().mRemoteToken = remoteToken;      } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java index 7168670f9652..0b77fd828745 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -40,6 +40,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;  import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;  import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;  import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; +import static com.android.server.wm.testing.Assert.assertThrows;  import static com.android.server.wm.ActivityRecord.State.RESUMED;  import static com.android.server.wm.WindowContainer.POSITION_TOP;  import static com.android.server.wm.WindowContainer.SYNC_STATE_READY; @@ -58,6 +59,7 @@ import static org.mockito.ArgumentMatchers.eq;  import static org.mockito.Mockito.atLeastOnce;  import static org.mockito.Mockito.clearInvocations; +import android.annotation.NonNull;  import android.app.ActivityManager;  import android.app.ActivityManager.RunningTaskInfo;  import android.app.ActivityOptions; @@ -77,11 +79,13 @@ import android.util.Rational;  import android.view.Display;  import android.view.SurfaceControl;  import android.view.WindowInsets; +import android.window.ITaskFragmentOrganizer;  import android.window.ITaskOrganizer;  import android.window.IWindowContainerTransactionCallback;  import android.window.StartingWindowInfo;  import android.window.StartingWindowRemovalInfo;  import android.window.TaskAppearedInfo; +import android.window.TaskFragmentOrganizer;  import android.window.WindowContainerToken;  import android.window.WindowContainerTransaction; @@ -579,6 +583,87 @@ public class WindowOrganizerTests extends WindowTestsBase {      }      @Test +    public void testTaskFragmentHiddenAndFocusableChanges() { +        removeGlobalMinSizeRestriction(); +        final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true) +                .setWindowingMode(WINDOWING_MODE_FULLSCREEN).build(); + +        final WindowContainerTransaction t = new WindowContainerTransaction(); +        final TaskFragmentOrganizer organizer = +                createTaskFragmentOrganizer(t, true /* isSystemOrganizer */); + +        final IBinder token = new Binder(); +        final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm) +                .setParentTask(rootTask) +                .setFragmentToken(token) +                .setOrganizer(organizer) +                .createActivityCount(1) +                .build(); + +        // Should be visible and focusable initially. +        assertTrue(rootTask.shouldBeVisible(null)); +        assertTrue(taskFragment.shouldBeVisible(null)); +        assertTrue(taskFragment.isFocusable()); +        assertTrue(taskFragment.isTopActivityFocusable()); + +        // Apply transaction to the TaskFragment hidden and not focusable. +        t.setHidden(taskFragment.mRemoteToken.toWindowContainerToken(), true); +        t.setFocusable(taskFragment.mRemoteToken.toWindowContainerToken(), false); +        mWm.mAtmService.mWindowOrganizerController.applyTaskFragmentTransactionLocked( +                t, TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE, +                false /* shouldApplyIndependently */); + +        // Should be not visible and not focusable after the transaction. +        assertFalse(taskFragment.shouldBeVisible(null)); +        assertFalse(taskFragment.isFocusable()); +        assertFalse(taskFragment.isTopActivityFocusable()); + +        // Apply transaction to the TaskFragment not hidden and focusable. +        t.setHidden(taskFragment.mRemoteToken.toWindowContainerToken(), false); +        t.setFocusable(taskFragment.mRemoteToken.toWindowContainerToken(), true); +        mWm.mAtmService.mWindowOrganizerController.applyTaskFragmentTransactionLocked( +                t, TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE, +                false /* shouldApplyIndependently */); + +        // Should be visible and focusable after the transaction. +        assertTrue(taskFragment.shouldBeVisible(null)); +        assertTrue(taskFragment.isFocusable()); +        assertTrue(taskFragment.isTopActivityFocusable()); +    } + +    @Test +    public void testTaskFragmentHiddenAndFocusableChanges_throwsWhenNotSystemOrganizer() { +        removeGlobalMinSizeRestriction(); +        final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true) +                .setWindowingMode(WINDOWING_MODE_FULLSCREEN).build(); + +        final WindowContainerTransaction t = new WindowContainerTransaction(); +        final TaskFragmentOrganizer organizer = +                createTaskFragmentOrganizer(t, false /* isSystemOrganizer */); + +        final IBinder token = new Binder(); +        final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm) +                .setParentTask(rootTask) +                .setFragmentToken(token) +                .setOrganizer(organizer) +                .createActivityCount(1) +                .build(); + +        assertTrue(rootTask.shouldBeVisible(null)); +        assertTrue(taskFragment.shouldBeVisible(null)); + +        t.setHidden(taskFragment.mRemoteToken.toWindowContainerToken(), true); +        t.setFocusable(taskFragment.mRemoteToken.toWindowContainerToken(), false); + +        // Non-system organizers are not allow to update the hidden and focusable states. +        assertThrows(SecurityException.class, () -> +                mWm.mAtmService.mWindowOrganizerController.applyTaskFragmentTransactionLocked( +                        t, TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE, +                        false /* shouldApplyIndependently */) +        ); +    } + +    @Test      public void testContainerTranslucentChanges() {          removeGlobalMinSizeRestriction();          final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true) @@ -1600,4 +1685,20 @@ public class WindowOrganizerTests extends WindowTestsBase {              assertTrue(taskIds.contains(expectedTasks[i].mTaskId));          }      } + +    @NonNull +    private TaskFragmentOrganizer createTaskFragmentOrganizer( +            @NonNull WindowContainerTransaction t, boolean isSystemOrganizer) { +        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); +        final ITaskFragmentOrganizer organizerInterface = +                ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder()); +        mWm.mAtmService.mWindowOrganizerController.mTaskFragmentOrganizerController +                .registerOrganizerInternal( +                        ITaskFragmentOrganizer.Stub.asInterface( +                                organizer.getOrganizerToken().asBinder()), +                        isSystemOrganizer); +        t.setTaskFragmentOrganizer(organizerInterface); + +        return organizer; +    }  } diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/LastCallVerifier.java b/services/tests/wmtests/src/com/android/server/wm/utils/LastCallVerifier.java new file mode 100644 index 000000000000..320d09444681 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/utils/LastCallVerifier.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.utils; + +import org.mockito.internal.verification.VerificationModeFactory; +import org.mockito.internal.verification.api.VerificationData; +import org.mockito.invocation.Invocation; +import org.mockito.invocation.MatchableInvocation; +import org.mockito.verification.VerificationMode; + +import java.util.List; + +/** + * Verifier to check that the last call of a method received the expected argument + */ +public class LastCallVerifier implements VerificationMode { + +    /** +     * Allows comparing the expected invocation with the last invocation on the same method +     */ +    public static LastCallVerifier lastCall() { +        return new LastCallVerifier(); +    } + +    @Override +    public void verify(VerificationData data) { +        List<Invocation> invocations = data.getAllInvocations(); +        MatchableInvocation target = data.getTarget(); +        for (int i = invocations.size() - 1; i >= 0; i--) { +            final Invocation invocation = invocations.get(i); +            if (target.hasSameMethod(invocation)) { +                if (target.matches(invocation)) { +                    return; +                } else { +                    throw new LastCallMismatch(target.getInvocation(), invocation, invocations); +                } +            } +        } +        throw new RuntimeException(target + " never invoked"); +    } + +    @Override +    public VerificationMode description(String description) { +        return VerificationModeFactory.description(this, description); +    } + +    static class LastCallMismatch extends RuntimeException { +        LastCallMismatch( +                Invocation expected, Invocation received, List<Invocation> allInvocations) { +            super("Expected invocation " + expected + " but received " + received +                    + " as the last invocation.\nAll registered invocations:\n" + allInvocations); +        } +    } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/MockAnimationAdapter.java b/services/tests/wmtests/src/com/android/server/wm/utils/MockAnimationAdapter.java new file mode 100644 index 000000000000..1a66970a8dc2 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/utils/MockAnimationAdapter.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.utils; + +import android.util.proto.ProtoOutputStream; +import android.view.SurfaceControl; + +import androidx.annotation.NonNull; + +import com.android.server.wm.AnimationAdapter; +import com.android.server.wm.SurfaceAnimator; + +import java.io.PrintWriter; + +/** + * An empty animation adapter which just executes the finish callback + */ +public class MockAnimationAdapter implements AnimationAdapter { + +    @Override +    public boolean getShowWallpaper() { +        return false; +    } + +    @Override +    public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t, +            int type, @NonNull SurfaceAnimator.OnAnimationFinishedCallback finishCallback) { +        // As the animation won't run, finish it immediately +        finishCallback.onAnimationFinished(0, null); +    } + +    @Override +    public void onAnimationCancelled(SurfaceControl animationLeash) {} + +    @Override +    public long getDurationHint() { +        return 0; +    } + +    @Override +    public long getStatusBarTransitionsStartTime() { +        return 0; +    } + +    @Override +    public void dump(PrintWriter pw, String prefix) {} + +    @Override +    public void dumpDebug(ProtoOutputStream proto) {} +} diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index f3bf026ddc6e..55b5d11d938a 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -53,6 +53,7 @@ import android.app.usage.AppStandbyInfo;  import android.app.usage.BroadcastResponseStatsList;  import android.app.usage.ConfigurationStats;  import android.app.usage.EventStats; +import android.app.usage.Flags;  import android.app.usage.IUsageStatsManager;  import android.app.usage.UsageEvents;  import android.app.usage.UsageEvents.Event; @@ -200,6 +201,7 @@ public class UsageStatsService extends SystemService implements      static final int MSG_ON_START = 7;      static final int MSG_HANDLE_LAUNCH_TIME_ON_USER_UNLOCK = 8;      static final int MSG_NOTIFY_ESTIMATED_LAUNCH_TIMES_CHANGED = 9; +    static final int MSG_UID_REMOVED = 10;      private final Object mLock = new Object();      private Handler mHandler; @@ -378,11 +380,10 @@ public class UsageStatsService extends SystemService implements          IntentFilter filter = new IntentFilter(Intent.ACTION_USER_REMOVED);          filter.addAction(Intent.ACTION_USER_STARTED); -        getContext().registerReceiverAsUser(new UserActionsReceiver(), UserHandle.ALL, filter, -                null, mHandler); - +        getContext().registerReceiverAsUser(new UserActionsReceiver(), UserHandle.ALL, +                filter, null, /* Handler scheduler */ null);          getContext().registerReceiverAsUser(new UidRemovedReceiver(), UserHandle.ALL, -                new IntentFilter(ACTION_UID_REMOVED), null, mHandler); +                new IntentFilter(ACTION_UID_REMOVED), null, /* Handler scheduler */ null);          mRealTimeSnapshot = SystemClock.elapsedRealtime();          mSystemTimeSnapshot = System.currentTimeMillis(); @@ -614,7 +615,6 @@ public class UsageStatsService extends SystemService implements              if (Intent.ACTION_USER_REMOVED.equals(action)) {                  if (userId >= 0) {                      mHandler.obtainMessage(MSG_REMOVE_USER, userId, 0).sendToTarget(); -                    mResponseStatsTracker.onUserRemoved(userId);                  }              } else if (Intent.ACTION_USER_STARTED.equals(action)) {                  if (userId >= 0) { @@ -632,9 +632,7 @@ public class UsageStatsService extends SystemService implements                  return;              } -            synchronized (mLock) { -                mResponseStatsTracker.onUidRemoved(uid); -            } +            mHandler.obtainMessage(MSG_UID_REMOVED, uid, 0).sendToTarget();          }      } @@ -1301,6 +1299,8 @@ public class UsageStatsService extends SystemService implements              mPendingLaunchTimeChangePackages.remove(userId);          }          mAppStandby.onUserRemoved(userId); +        mResponseStatsTracker.onUserRemoved(userId); +          // Cancel any scheduled jobs for this user since the user is being removed.          UsageStatsIdleService.cancelPruneJob(getContext(), userId);          UsageStatsIdleService.cancelUpdateMappingsJob(getContext(), userId); @@ -2037,6 +2037,9 @@ public class UsageStatsService extends SystemService implements                  case MSG_REMOVE_USER:                      onUserRemoved(msg.arg1);                      break; +                case MSG_UID_REMOVED: +                    mResponseStatsTracker.onUidRemoved(msg.arg1); +                    break;                  case MSG_PACKAGE_REMOVED:                      onPackageRemoved(msg.arg1, (String) msg.obj);                      break; @@ -2125,7 +2128,8 @@ public class UsageStatsService extends SystemService implements          private boolean canReportUsageStats() {              if (isCallingUidSystem()) { -                return true; // System UID can always report UsageStats +                // System UID can always report UsageStats +                return true;              }              return getContext().checkCallingPermission(Manifest.permission.REPORT_USAGE_STATS) @@ -2621,9 +2625,12 @@ public class UsageStatsService extends SystemService implements                  return;              } -            if (!canReportUsageStats()) { -                throw new SecurityException("Only the system or holders of the REPORT_USAGE_STATS" -                        + " permission are allowed to call reportChooserSelection"); +            if (Flags.reportUsageStatsPermission()) { +                if (!canReportUsageStats()) { +                    throw new SecurityException( +                        "Only the system or holders of the REPORT_USAGE_STATS" +                            + " permission are allowed to call reportChooserSelection"); +                }              }              // Verify if this package exists before reporting an event for it. @@ -2643,9 +2650,17 @@ public class UsageStatsService extends SystemService implements          @Override          public void reportUserInteraction(String packageName, int userId) {              Objects.requireNonNull(packageName); -            if (!canReportUsageStats()) { -                throw new SecurityException("Only the system or holders of the REPORT_USAGE_STATS" -                        + " permission are allowed to call reportUserInteraction"); +            if (Flags.reportUsageStatsPermission()) { +                if (!canReportUsageStats()) { +                    throw new SecurityException( +                        "Only the system or holders of the REPORT_USAGE_STATS" +                            + " permission are allowed to call reportUserInteraction"); +                } +            } else { +                if (!isCallingUidSystem()) { +                    throw new SecurityException("Only system is allowed to call" +                            + " reportUserInteraction"); +                }              }              final Event event = new Event(USER_INTERACTION, SystemClock.elapsedRealtime()); diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java index 5d2f27dce206..35e2fcfecb6b 100644 --- a/services/usb/java/com/android/server/usb/UsbService.java +++ b/services/usb/java/com/android/server/usb/UsbService.java @@ -37,8 +37,8 @@ import android.content.Context;  import android.content.Intent;  import android.content.IntentFilter;  import android.content.pm.PackageManager; -import android.hardware.usb.IUsbManager;  import android.hardware.usb.IDisplayPortAltModeInfoListener; +import android.hardware.usb.IUsbManager;  import android.hardware.usb.IUsbOperationInternal;  import android.hardware.usb.ParcelableUsbPort;  import android.hardware.usb.UsbAccessory; @@ -46,7 +46,6 @@ import android.hardware.usb.UsbDevice;  import android.hardware.usb.UsbManager;  import android.hardware.usb.UsbPort;  import android.hardware.usb.UsbPortStatus; -import android.hardware.usb.DisplayPortAltModeInfo;  import android.os.Binder;  import android.os.Bundle;  import android.os.ParcelFileDescriptor; @@ -1215,6 +1214,20 @@ public class UsbService extends IUsbManager.Stub {                      mPortManager.dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, "  ")),                              "", 0);                  } +            } else if ("enable-usb-data".equals(args[0]) && args.length == 3) { +                final String portId = args[1]; +                final boolean enable = Boolean.parseBoolean(args[2]); + +                if (mPortManager != null) { +                    for (UsbPort p : mPortManager.getPorts()) { +                        if (p.getId().equals(portId)) { +                            int res = p.enableUsbData(enable); +                            Slog.i(TAG, "enableUsbData " + portId + " status " + res); +                            break; +                        } +                    } +                } +              } else if ("ports".equals(args[0]) && args.length == 1) {                  if (mPortManager != null) {                      mPortManager.dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, "  ")), @@ -1293,6 +1306,11 @@ public class UsbService extends IUsbManager.Stub {                  pw.println("reset-displayport-status can also be used in order to set");                  pw.println("the DisplayPortInfo to default values.");                  pw.println(); +                pw.println("Example enableUsbData"); +                pw.println("This dumpsys command functions for both simulated and real ports."); +                pw.println("  dumpsys usb enable-usb-data \"matrix\" true"); +                pw.println("  dumpsys usb enable-usb-data \"matrix\" false"); +                pw.println();                  pw.println("Example USB device descriptors:");                  pw.println("  dumpsys usb dump-descriptors -dump-short");                  pw.println("  dumpsys usb dump-descriptors -dump-tree"); diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 138e575a6872..1c689d0d5ce3 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -58,6 +58,7 @@ import android.os.Handler;  import android.os.IBinder;  import android.os.Parcel;  import android.os.ParcelFileDescriptor; +import android.os.PermissionEnforcer;  import android.os.PersistableBundle;  import android.os.RemoteCallback;  import android.os.RemoteCallbackList; @@ -67,6 +68,7 @@ import android.os.SharedMemory;  import android.os.ShellCallback;  import android.os.Trace;  import android.os.UserHandle; +import android.permission.flags.Flags;  import android.provider.Settings;  import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;  import android.service.voice.IVisualQueryDetectionVoiceInteractionCallback; @@ -1286,6 +1288,17 @@ public class VoiceInteractionManagerService extends SystemService {              }          } +        // Enforce permissions that are flag controlled. The flag value decides if the permission +        // should be enforced. +        private void initAndVerifyDetector_enforcePermissionWithFlags() { +            PermissionEnforcer enforcer = mContext.getSystemService(PermissionEnforcer.class); +            if (Flags.voiceActivationPermissionApis()) { +                enforcer.enforcePermission( +                        android.Manifest.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO, +                        getCallingPid(), getCallingUid()); +            } +        } +          @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION)          @Override          public void initAndVerifyDetector( @@ -1295,7 +1308,13 @@ public class VoiceInteractionManagerService extends SystemService {                  @NonNull IBinder token,                  IHotwordRecognitionStatusCallback callback,                  int detectorType) { +            // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the +            // {@link #initAndVerifyDetector(Identity,  PersistableBundle, ShareMemory, IBinder, +            // IHotwordRecognitionStatusCallback, int)} +            // and replace with the permission RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully +            // launched.              super.initAndVerifyDetector_enforcePermission(); +            initAndVerifyDetector_enforcePermissionWithFlags();              synchronized (this) {                  enforceIsCurrentVoiceInteractionService(); diff --git a/telephony/java/android/telephony/BarringInfo.java b/telephony/java/android/telephony/BarringInfo.java index 971fc781a719..e20e4d200251 100644 --- a/telephony/java/android/telephony/BarringInfo.java +++ b/telephony/java/android/telephony/BarringInfo.java @@ -202,6 +202,24 @@ public final class BarringInfo implements Parcelable {                      && mConditionalBarringTimeSeconds == other.mConditionalBarringTimeSeconds;          } +        private static String barringTypeToString(@BarringType int barringType) { +            return switch (barringType) { +                case BARRING_TYPE_NONE -> "NONE"; +                case BARRING_TYPE_CONDITIONAL -> "CONDITIONAL"; +                case BARRING_TYPE_UNCONDITIONAL -> "UNCONDITIONAL"; +                case BARRING_TYPE_UNKNOWN -> "UNKNOWN"; +                default -> "UNKNOWN(" + barringType + ")"; +            }; +        } + +        @Override +        public String toString() { +            return "BarringServiceInfo {mBarringType=" + barringTypeToString(mBarringType) +                    + ", mIsConditionallyBarred=" + mIsConditionallyBarred +                    + ", mConditionalBarringFactor=" + mConditionalBarringFactor +                    + ", mConditionalBarringTimeSeconds=" + mConditionalBarringTimeSeconds + "}"; +        } +          /** @hide */          public BarringServiceInfo(Parcel p) {              mBarringType = p.readInt(); diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 90fa69ff8a15..73220c353b36 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -15033,15 +15033,6 @@ public class TelephonyManager {      @TestApi      public static final int HAL_SERVICE_IMS = 7; -    /** -     * HAL service type that supports the HAL APIs implementation of IRadioSatellite -     * {@link RadioSatelliteProxy} -     * @hide -     */ -    @TestApi -    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) -    public static final int HAL_SERVICE_SATELLITE = 8; -      /** @hide */      @Retention(RetentionPolicy.SOURCE)      @IntDef(prefix = {"HAL_SERVICE_"}, @@ -15054,7 +15045,6 @@ public class TelephonyManager {                      HAL_SERVICE_SIM,                      HAL_SERVICE_VOICE,                      HAL_SERVICE_IMS, -                    HAL_SERVICE_SATELLITE              })      public @interface HalService {} diff --git a/telephony/java/android/telephony/satellite/INtnSignalStrengthCallback.aidl b/telephony/java/android/telephony/satellite/INtnSignalStrengthCallback.aidl new file mode 100644 index 000000000000..54cab48dd1e4 --- /dev/null +++ b/telephony/java/android/telephony/satellite/INtnSignalStrengthCallback.aidl @@ -0,0 +1,31 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony.satellite; + +import android.telephony.satellite.NtnSignalStrength; + +/** + * Interface for non-terrestrial signal strength notify callback. + * @hide + */ +oneway interface INtnSignalStrengthCallback { +    /** +     * Called when NTN signal strength changes. +     * @param ntnSignalStrength The new NTN signal strength. +     */ +    void onNtnSignalStrengthChanged(in NtnSignalStrength ntnSignalStrength); +} diff --git a/telephony/java/android/telephony/satellite/NtnSignalStrength.aidl b/telephony/java/android/telephony/satellite/NtnSignalStrength.aidl new file mode 100644 index 000000000000..a79cb695ef24 --- /dev/null +++ b/telephony/java/android/telephony/satellite/NtnSignalStrength.aidl @@ -0,0 +1,19 @@ +/* + * Copyright 2023, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *     http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony.satellite; + +parcelable NtnSignalStrength; diff --git a/telephony/java/android/telephony/satellite/NtnSignalStrength.java b/telephony/java/android/telephony/satellite/NtnSignalStrength.java new file mode 100644 index 000000000000..16d765455d21 --- /dev/null +++ b/telephony/java/android/telephony/satellite/NtnSignalStrength.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony.satellite; + +import android.annotation.FlaggedApi; +import android.annotation.IntDef; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import androidx.annotation.NonNull; + +import com.android.internal.telephony.flags.Flags; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * NTN signal strength related information. + * @hide + */ +@SystemApi +@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) +public final class NtnSignalStrength implements Parcelable { +    /** Non-terrestrial network signal strength is not available. */ +    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) +    public static final int NTN_SIGNAL_STRENGTH_NONE = 0; +    /** Non-terrestrial network signal strength is poor. */ +    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) +    public static final int NTN_SIGNAL_STRENGTH_POOR = 1; +    /** Non-terrestrial network signal strength is moderate. */ +    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) +    public static final int NTN_SIGNAL_STRENGTH_MODERATE = 2; +    /** Non-terrestrial network signal strength is good. */ +    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) +    public static final int NTN_SIGNAL_STRENGTH_GOOD = 3; +    /** Non-terrestrial network signal strength is great. */ +    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) +    public static final int NTN_SIGNAL_STRENGTH_GREAT = 4; +    @NtnSignalStrengthLevel private int mLevel; + +    /** @hide */ +    @IntDef(prefix = "NTN_SIGNAL_STRENGTH_", value = { +            NTN_SIGNAL_STRENGTH_NONE, +            NTN_SIGNAL_STRENGTH_POOR, +            NTN_SIGNAL_STRENGTH_MODERATE, +            NTN_SIGNAL_STRENGTH_GOOD, +            NTN_SIGNAL_STRENGTH_GREAT +    }) +    @Retention(RetentionPolicy.SOURCE) +    public @interface NtnSignalStrengthLevel {} + +    /** +     * Create a parcelable object to inform the current non-terrestrial signal strength +     * @hide +     */ +    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) +    public NtnSignalStrength(@NtnSignalStrengthLevel int level) { +        this.mLevel = level; +    } + +    /** +     * This constructor is used to create a copy of an existing NtnSignalStrength object. +     */ +    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) +    public NtnSignalStrength(@Nullable NtnSignalStrength source) { +        this.mLevel = (source == null) ? NTN_SIGNAL_STRENGTH_NONE : source.getLevel(); +    } + +    private NtnSignalStrength(Parcel in) { +        readFromParcel(in); +    } + +    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) +    @NtnSignalStrengthLevel public int getLevel() { +        return mLevel; +    } + +    /** +     * @return 0 +     */ +    @Override +    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) +    public int describeContents() { +        return 0; +    } + +    /** +     * @param out  The Parcel in which the object should be written. +     * @param flags Additional flags about how the object should be written. +     *              May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}. +     */ +    @Override +    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) +    public void writeToParcel(@NonNull Parcel out, int flags) { +        out.writeInt(mLevel); +    } + +    private void readFromParcel(Parcel in) { +        mLevel = in.readInt(); +    } + +    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) +    @NonNull public static final Creator<NtnSignalStrength> CREATOR = +            new Creator<NtnSignalStrength>() { +                @Override public NtnSignalStrength createFromParcel(Parcel in) { +                    return new NtnSignalStrength(in); +                } + +                @Override public NtnSignalStrength[] newArray(int size) { +                    return new NtnSignalStrength[size]; +                } +            }; + +    @Override +    public int hashCode() { +        return mLevel; +    } + +    @Override +    public boolean equals(Object obj) { +        if (this == obj) return true; +        if (obj == null || getClass() != obj.getClass()) return false; + +        NtnSignalStrength that = (NtnSignalStrength) obj; +        return mLevel == that.mLevel; +    } + +    @Override public String toString() { +        return "NtnSignalStrength{" +                + "mLevel=" + mLevel +                + '}'; +    } +} diff --git a/telephony/java/android/telephony/satellite/NtnSignalStrengthCallback.java b/telephony/java/android/telephony/satellite/NtnSignalStrengthCallback.java new file mode 100644 index 000000000000..4b79590b9bc6 --- /dev/null +++ b/telephony/java/android/telephony/satellite/NtnSignalStrengthCallback.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony.satellite; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.SystemApi; + +import com.android.internal.telephony.flags.Flags; + +/** + * A callback class for notifying satellite signal strength change. + * + * @hide + */ +@SystemApi +@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) +public interface NtnSignalStrengthCallback { +    /** +     * Called when non-terrestrial network signal strength changes. +     * @param ntnSignalStrength The new non-terrestrial network signal strength. +     */ +    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) +    void onNtnSignalStrengthChanged(@NonNull NtnSignalStrength ntnSignalStrength); +} diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java index 7322aeb8bfca..21d93bd3077f 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -35,7 +35,9 @@ import android.os.OutcomeReceiver;  import android.os.RemoteException;  import android.os.ResultReceiver;  import android.telephony.SubscriptionManager; +import android.telephony.TelephonyCallback;  import android.telephony.TelephonyFrameworkInitializer; +import android.telephony.TelephonyManager;  import com.android.internal.telephony.IIntegerConsumer;  import com.android.internal.telephony.ITelephony; @@ -77,6 +79,8 @@ public final class SatelliteManager {      private static final ConcurrentHashMap<SatelliteTransmissionUpdateCallback,              ISatelliteTransmissionUpdateCallback> sSatelliteTransmissionUpdateCallbackMap =              new ConcurrentHashMap<>(); +    private static final ConcurrentHashMap<NtnSignalStrengthCallback, INtnSignalStrengthCallback> +            sNtnSignalStrengthCallbackMap = new ConcurrentHashMap<>();      private final int mSubId; @@ -192,6 +196,14 @@ public final class SatelliteManager {      public static final String KEY_SATELLITE_NEXT_VISIBILITY = "satellite_next_visibility";      /** +     * Bundle key to get the response from +     * {@link #requestNtnSignalStrength(Executor, OutcomeReceiver)}. +     * @hide +     */ + +    public static final String KEY_NTN_SIGNAL_STRENGTH = "ntn_signal_strength"; + +    /**       * The request was successfully processed.       */      @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) @@ -1866,6 +1878,165 @@ public final class SatelliteManager {          return new HashSet<>();      } +    /** +     * Request to get the signal strength of the satellite connection. +     * +     * <p> +     * Note: This API is specifically designed for OEM enabled satellite connectivity only. +     * For satellite connectivity enabled using carrier roaming, please refer to +     * {@link android.telephony.TelephonyCallback.SignalStrengthsListener}, and +     * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}. +     * </p> +     * +     * @param executor The executor on which the callback will be called. +     * @param callback The callback object to which the result will be delivered. If the request is +     * successful, {@link OutcomeReceiver#onResult(Object)} will return an instance of +     * {@link NtnSignalStrength} with a value of {@link NtnSignalStrength.NtnSignalStrengthLevel} +     * The {@link NtnSignalStrength#NTN_SIGNAL_STRENGTH_NONE} will be returned if there is no +     * signal strength data available. +     * If the request is not successful, {@link OutcomeReceiver#onError(Throwable)} will return a +     * {@link SatelliteException} with the {@link SatelliteResult}. +     * +     * @throws SecurityException if the caller doesn't have required permission. +     * @throws IllegalStateException if the Telephony process is not currently available or +     * satellite is not supported. +     */ +    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) +    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) +    @NonNull +    public void requestNtnSignalStrength(@NonNull @CallbackExecutor Executor executor, +            @NonNull OutcomeReceiver<NtnSignalStrength, SatelliteException> callback) { +        Objects.requireNonNull(executor); +        Objects.requireNonNull(callback); + +        try { +            ITelephony telephony = getITelephony(); +            if (telephony != null) { +                ResultReceiver receiver = new ResultReceiver(null) { +                    @Override +                    protected void onReceiveResult(int resultCode, Bundle resultData) { +                        if (resultCode == SATELLITE_RESULT_SUCCESS) { +                            if (resultData.containsKey(KEY_NTN_SIGNAL_STRENGTH)) { +                                NtnSignalStrength ntnSignalStrength = +                                        resultData.getParcelable(KEY_NTN_SIGNAL_STRENGTH, +                                                NtnSignalStrength.class); +                                executor.execute(() -> Binder.withCleanCallingIdentity(() -> +                                        callback.onResult(ntnSignalStrength))); +                            } else { +                                loge("KEY_NTN_SIGNAL_STRENGTH does not exist."); +                                executor.execute(() -> Binder.withCleanCallingIdentity(() -> +                                        callback.onError(new SatelliteException( +                                                SATELLITE_RESULT_REQUEST_FAILED)))); +                            } +                        } else { +                            executor.execute(() -> Binder.withCleanCallingIdentity(() -> +                                    callback.onError(new SatelliteException(resultCode)))); +                        } +                    } +                }; +                telephony.requestNtnSignalStrength(mSubId, receiver); +            } else { +                throw new IllegalStateException("Telephony service is null."); +            } +        } catch (RemoteException ex) { +            loge("requestNtnSignalStrength() RemoteException: " + ex); +            ex.rethrowFromSystemServer(); +        } +    } + +    /** +     * Registers for NTN signal strength changed from satellite modem. +     * +     * <p> +     * Note: This API is specifically designed for OEM enabled satellite connectivity only. +     * For satellite connectivity enabled using carrier roaming, please refer to +     * {@link android.telephony.TelephonyCallback.SignalStrengthsListener}, and +     * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}. +     * </p> +     * +     * @param executor The executor on which the callback will be called. +     * @param callback The callback to handle the NTN signal strength changed event. +     * +     * @return The {@link SatelliteResult} result of the operation. +     * +     * @throws SecurityException if the caller doesn't have required permission. +     * @throws IllegalStateException if the Telephony process is not currently available. +     */ +    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) +    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) +    @SatelliteResult public int registerForNtnSignalStrengthChanged( +            @NonNull @CallbackExecutor Executor executor, +            @NonNull NtnSignalStrengthCallback callback) { +        Objects.requireNonNull(executor); +        Objects.requireNonNull(callback); + +        try { +            ITelephony telephony = getITelephony(); +            if (telephony != null) { +                INtnSignalStrengthCallback internalCallback = +                        new INtnSignalStrengthCallback.Stub() { +                            @Override +                            public void onNtnSignalStrengthChanged( +                                    NtnSignalStrength ntnSignalStrength) { +                                executor.execute(() -> Binder.withCleanCallingIdentity( +                                        () -> callback.onNtnSignalStrengthChanged( +                                                ntnSignalStrength))); +                            } +                        }; +                sNtnSignalStrengthCallbackMap.put(callback, internalCallback); +                return telephony.registerForNtnSignalStrengthChanged(mSubId, internalCallback); +            } else { +                throw new IllegalStateException("Telephony service is null."); +            } +        } catch (RemoteException ex) { +            loge("registerForNtnSignalStrengthChanged() RemoteException: " + ex); +            ex.rethrowFromSystemServer(); +        } +        return SATELLITE_RESULT_REQUEST_FAILED; +    } + +    /** +     * Unregisters for NTN signal strength changed from satellite modem. +     * If callback was not registered before, the request will be ignored. +     * +     * <p> +     * Note: This API is specifically designed for OEM enabled satellite connectivity only. +     * For satellite connectivity enabled using carrier roaming, please refer to +     * {@link TelephonyManager#unregisterTelephonyCallback(TelephonyCallback)}.. +     * </p> +     * +     * @param callback The callback that was passed to +     * {@link #registerForNtnSignalStrengthChanged(Executor, NtnSignalStrengthCallback)}. +     * +     * @throws SecurityException if the caller doesn't have required permission. +     * @throws IllegalStateException if the Telephony process is not currently available. +     */ +    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) +    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) +    public void unregisterForNtnSignalStrengthChanged(@NonNull NtnSignalStrengthCallback callback) { +        Objects.requireNonNull(callback); +        INtnSignalStrengthCallback internalCallback = +                sNtnSignalStrengthCallbackMap.remove(callback); + +        try { +            ITelephony telephony = getITelephony(); +            if (telephony != null) { +                if (internalCallback != null) { +                    telephony.unregisterForNtnSignalStrengthChanged(mSubId, internalCallback); +                } else { +                    loge("unregisterForNtnSignalStrengthChanged: No internal callback."); +                } +            } else { +                throw new IllegalStateException("Telephony service is null."); +            } +        } catch (RemoteException ex) { +            loge("unregisterForNtnSignalStrengthChanged() RemoteException: " + ex); +            ex.rethrowFromSystemServer(); +        } + +    } + +      private static ITelephony getITelephony() {          ITelephony binder = ITelephony.Stub.asInterface(TelephonyFrameworkInitializer                  .getTelephonyServiceManager() diff --git a/telephony/java/android/telephony/satellite/stub/INtnSignalStrengthConsumer.aidl b/telephony/java/android/telephony/satellite/stub/INtnSignalStrengthConsumer.aidl new file mode 100644 index 000000000000..b7712bd83cf6 --- /dev/null +++ b/telephony/java/android/telephony/satellite/stub/INtnSignalStrengthConsumer.aidl @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony.satellite.stub; + +import android.telephony.satellite.NtnSignalStrength; + +/** + * Consumer pattern for a request that receives the signal strength of non-terrestrial network from + * the SatelliteService. + * @hide + */ +oneway interface INtnSignalStrengthConsumer { +    void accept(in NtnSignalStrength result); +} diff --git a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl index 7fda550c599c..6b47db1e2251 100644 --- a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl +++ b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl @@ -16,6 +16,7 @@  package android.telephony.satellite.stub; +import android.telephony.satellite.stub.INtnSignalStrengthConsumer;  import android.telephony.satellite.stub.ISatelliteCapabilitiesConsumer;  import android.telephony.satellite.stub.ISatelliteListener;  import android.telephony.satellite.stub.SatelliteDatagram; @@ -454,4 +455,44 @@ oneway interface ISatellite {       */      void requestIsSatelliteEnabledForCarrier(int simSlot, in IIntegerConsumer resultCallback,              in IBooleanConsumer callback); + +    /** +     * Request to get the signal strength of the satellite connection. +     * +     * @param resultCallback The {@link SatelliteError} result of the operation. +     * @param callback The callback to handle the NTN signal strength changed event. +     */ +    void requestSignalStrength(in IIntegerConsumer resultCallback, +            in INtnSignalStrengthConsumer callback); + +    /** +     * The satellite service should report the NTN signal strength via +     * ISatelliteListener#onNtnSignalStrengthChanged when the NTN signal strength changes. +     * +     * @param resultCallback The callback to receive the error code result of the operation. +     * +     * Valid result codes returned: +     *   SatelliteResult:SATELLITE_RESULT_SUCCESS +     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR +     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE +     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE +     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED +     */ +     void startSendingNtnSignalStrength(in IIntegerConsumer resultCallback); + +    /** +     * The satellite service should stop reporting NTN signal strength to the framework. This will +     * be called when device is screen off to save power by not letting signal strength updates to +     * wake up application processor. +     * +     * @param resultCallback The callback to receive the error code result of the operation. +     * +     * Valid result codes returned: +     *   SatelliteResult:SATELLITE_RESULT_SUCCESS +     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR +     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE +     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE +     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED +     */ +     void stopSendingNtnSignalStrength(in IIntegerConsumer resultCallback);  } diff --git a/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl b/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl index d68716291b8e..d44ddfa1ee7f 100644 --- a/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl +++ b/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl @@ -16,6 +16,7 @@  package android.telephony.satellite.stub; +import android.telephony.satellite.stub.NtnSignalStrength;  import android.telephony.satellite.stub.NTRadioTechnology;  import android.telephony.satellite.stub.PointingInfo;  import android.telephony.satellite.stub.SatelliteDatagram; @@ -58,4 +59,10 @@ oneway interface ISatelliteListener {       * @param state The current satellite modem state.       */      void onSatelliteModemStateChanged(in SatelliteModemState state); + +    /** +     * Called when NTN signal strength changes. +     * @param ntnSignalStrength The new NTN signal strength. +     */ +    void onNtnSignalStrengthChanged(in NtnSignalStrength ntnSignalStrength);  } diff --git a/telephony/java/android/telephony/satellite/stub/NtnSignalStrength.aidl b/telephony/java/android/telephony/satellite/stub/NtnSignalStrength.aidl new file mode 100644 index 000000000000..f48900583167 --- /dev/null +++ b/telephony/java/android/telephony/satellite/stub/NtnSignalStrength.aidl @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *     http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony.satellite.stub; + +import android.telephony.satellite.stub.NtnSignalStrengthLevel; + +/** + * @hide + */ +parcelable NtnSignalStrength { +    /** +     * Non-terrestrial signal strength. The value represents the level of signal strength which can +     * be translated to the number of signal bars. +     */ +    NtnSignalStrengthLevel signalStrengthLevel; +} diff --git a/telephony/java/android/telephony/satellite/stub/NtnSignalStrengthLevel.aidl b/telephony/java/android/telephony/satellite/stub/NtnSignalStrengthLevel.aidl new file mode 100644 index 000000000000..53b13733941c --- /dev/null +++ b/telephony/java/android/telephony/satellite/stub/NtnSignalStrengthLevel.aidl @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony.satellite.stub; + +/** + * {@hide} + */ +@Backing(type="int") +enum NtnSignalStrengthLevel { +    NTN_SIGNAL_STRENGTH_NONE = 0, +    NTN_SIGNAL_STRENGTH_POOR = 1, +    NTN_SIGNAL_STRENGTH_MODERATE = 2, +    NTN_SIGNAL_STRENGTH_GOOD = 3, +    NTN_SIGNAL_STRENGTH_GREAT = 4 +} diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java index 4cee01e8bd39..a636a6128e61 100644 --- a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java +++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java @@ -241,6 +241,30 @@ public class SatelliteImplBase extends SatelliteService {                      "requestIsSatelliteEnabledForCarrier");          } +        @Override +        public void requestSignalStrength(IIntegerConsumer resultCallback, +                INtnSignalStrengthConsumer callback) throws RemoteException { +            executeMethodAsync( +                    () -> SatelliteImplBase.this.requestSignalStrength(resultCallback, callback), +                    "requestSignalStrength"); +        } + +        @Override +        public void startSendingNtnSignalStrength(IIntegerConsumer resultCallback) +                throws RemoteException { +            executeMethodAsync( +                    () -> SatelliteImplBase.this.startSendingNtnSignalStrength(resultCallback), +                    "startSendingNtnSignalStrength"); +        } + +        @Override +        public void stopSendingNtnSignalStrength(IIntegerConsumer resultCallback) +                throws RemoteException { +            executeMethodAsync( +                    () -> SatelliteImplBase.this.stopSendingNtnSignalStrength(resultCallback), +                    "stopSendingNtnSignalStrength"); +        } +          // Call the methods with a clean calling identity on the executor and wait indefinitely for          // the future to return.          private void executeMethodAsync(Runnable r, String errorLogName) throws RemoteException { @@ -728,4 +752,35 @@ public class SatelliteImplBase extends SatelliteService {              @NonNull IIntegerConsumer resultCallback, @NonNull IBooleanConsumer callback) {          // stub implementation      } + +    /** +     * Request to get the signal strength of the satellite connection. +     * +     * @param resultCallback The {@link SatelliteError} result of the operation. +     * @param callback The callback to handle the NTN signal strength changed event. +     */ +    public void requestSignalStrength(@NonNull IIntegerConsumer resultCallback, +            INtnSignalStrengthConsumer callback) { +        // stub implementation +    } + +    /** +     * Requests to deliver signal strength changed events through the +     * {@link ISatelliteListener#onNtnSignalStrengthChanged(NtnSignalStrength ntnSignalStrength)} +     * callback. +     * +     * @param resultCallback The {@link SatelliteError} result of the operation. +     */ +    public void startSendingNtnSignalStrength(@NonNull IIntegerConsumer resultCallback) { +        // stub implementation +    } + +    /** +     * Requests to stop signal strength changed events +     * +     * @param resultCallback The {@link SatelliteError} result of the operation. +     */ +    public void stopSendingNtnSignalStrength(@NonNull IIntegerConsumer resultCallback){ +        // stub implementation +    }  } diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 3aa5a5a14bc8..58e702688bd3 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -67,10 +67,12 @@ import android.telephony.ims.aidl.IImsRcsFeature;  import android.telephony.ims.aidl.IImsRegistration;  import android.telephony.ims.aidl.IImsRegistrationCallback;  import android.telephony.ims.aidl.IRcsConfigCallback; +import android.telephony.satellite.INtnSignalStrengthCallback;  import android.telephony.satellite.ISatelliteDatagramCallback;  import android.telephony.satellite.ISatelliteTransmissionUpdateCallback;  import android.telephony.satellite.ISatelliteProvisionStateCallback;  import android.telephony.satellite.ISatelliteStateCallback; +import android.telephony.satellite.NtnSignalStrength;  import android.telephony.satellite.SatelliteCapabilities;  import android.telephony.satellite.SatelliteDatagram;  import com.android.ims.internal.IImsServiceFeatureCallback; @@ -2837,7 +2839,6 @@ interface ITelephony {              + "android.Manifest.permission.SATELLITE_COMMUNICATION)")      void deprovisionSatelliteService(int subId, in String token, in IIntegerConsumer callback); -      /**       * Registers for provision state changed from satellite modem.       * @@ -3071,4 +3072,40 @@ interface ITelephony {      @JavaPassthrough(annotation="@android.annotation.RequiresPermission("              + "android.Manifest.permission.SATELLITE_COMMUNICATION)")      int[] getSatelliteAttachRestrictionReasonsForCarrier(int subId); + +    /** +     * Request to get the signal strength of the satellite connection. +     * +     * @param subId The subId of the subscription to request for. +     * @param receiver Result receiver to get the error code of the request and the current signal +     * strength of the satellite connection. +     */ +    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(" +            + "android.Manifest.permission.SATELLITE_COMMUNICATION)") +    void requestNtnSignalStrength(int subId, in ResultReceiver receiver); + +    /** +     * Registers for NTN signal strength changed from satellite modem. +     * +     * @param subId The subId of the subscription to request for. +     * @param callback The callback to handle the NTN signal strength changed event. +     * +     * @return The {@link SatelliteResult} result of the operation. +     */ +    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(" +            + "android.Manifest.permission.SATELLITE_COMMUNICATION)") +    int registerForNtnSignalStrengthChanged(int subId, in INtnSignalStrengthCallback callback); + +    /** +     * Unregisters for NTN signal strength changed from satellite modem. +     * If callback was not registered before, the request will be ignored. +     * +     * @param subId The subId of the subscription to unregister for provision state changed. +     * @param callback The callback that was passed to +     * {@link #registerForNtnSignalStrengthChanged(Executor, NtnSignalStrengthCallback)}. +     */ +    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(" +            + "android.Manifest.permission.SATELLITE_COMMUNICATION)") +    void unregisterForNtnSignalStrengthChanged(int subId, +            in INtnSignalStrengthCallback callback);  } diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java index 72e4389e0788..a20f26c1807b 100644 --- a/telephony/java/com/android/internal/telephony/RILConstants.java +++ b/telephony/java/com/android/internal/telephony/RILConstants.java @@ -120,31 +120,6 @@ public interface RILConstants {      int BLOCKED_DUE_TO_CALL = 69;                   /* SMS is blocked due to call control */      int RF_HARDWARE_ISSUE = 70;                     /* RF HW issue is detected */      int NO_RF_CALIBRATION_INFO = 71;                /* No RF calibration in device */ -    int ENCODING_NOT_SUPPORTED = 72;                /* The encoding scheme is not supported by -                                                       either the network or the MS. */ -    int FEATURE_NOT_SUPPORTED = 73;                 /* The requesting feature is not supported by -                                                       the service provider. */ -    int INVALID_CONTACT = 74;                       /* The contact to be added is either not -                                                       existing or not valid. */ -    int MODEM_INCOMPATIBLE = 75;                    /* The modem of the MS is not compatible with -                                                       the service provider. */ -    int NETWORK_TIMEOUT = 76;                       /* Modem timeout to receive ACK or response from -                                                       network after sending a request to it. */ -    int NO_SATELLITE_SIGNAL = 77;                   /* Modem fails to communicate with the satellite -                                                       network since there is no satellite signal.*/ -    int NOT_SUFFICIENT_ACCOUNT_BALANCE = 78;        /* The request cannot be performed since the -                                                       subscriber's account balance is not -                                                       sufficient. */ -    int RADIO_TECHNOLOGY_NOT_SUPPORTED = 79;        /* The radio technology is not supported by the -                                                       service provider. */ -    int SUBSCRIBER_NOT_AUTHORIZED = 80;             /* The subscription is not authorized to -                                                       register with the service provider. */ -    int SWITCHED_FROM_SATELLITE_TO_TERRESTRIAL = 81; /* While processing a request from the -                                                       Framework the satellite modem detects -                                                       terrestrial signal, aborts the request, and -                                                       switches to the terrestrial network. */ -    int UNIDENTIFIED_SUBSCRIBER = 82;               /* The subscriber is not registered with the -                                                       service provider */      // Below is list of OEM specific error codes which can by used by OEMs in case they don't want to      // reveal particular replacement for Generic failure @@ -568,22 +543,7 @@ public interface RILConstants {      int RIL_REQUEST_IS_N1_MODE_ENABLED = 242;      int RIL_REQUEST_SET_LOCATION_PRIVACY_SETTING = 243;      int RIL_REQUEST_GET_LOCATION_PRIVACY_SETTING = 244; -    int RIL_REQUEST_GET_SATELLITE_CAPABILITIES = 245; -    int RIL_REQUEST_SET_SATELLITE_POWER = 246; -    int RIL_REQUEST_GET_SATELLITE_POWER = 247; -    int RIL_REQUEST_PROVISION_SATELLITE_SERVICE = 248; -    int RIL_REQUEST_ADD_ALLOWED_SATELLITE_CONTACTS = 249; -    int RIL_REQUEST_REMOVE_ALLOWED_SATELLITE_CONTACTS = 250; -    int RIL_REQUEST_SEND_SATELLITE_MESSAGES = 251; -    int RIL_REQUEST_GET_PENDING_SATELLITE_MESSAGES = 252; -    int RIL_REQUEST_GET_SATELLITE_MODE = 253; -    int RIL_REQUEST_SET_SATELLITE_INDICATION_FILTER = 254; -    int RIL_REQUEST_START_SENDING_SATELLITE_POINTING_INFO = 255; -    int RIL_REQUEST_STOP_SENDING_SATELLITE_POINTING_INFO = 256; -    int RIL_REQUEST_GET_MAX_CHARACTERS_PER_SATELLITE_TEXT_MESSAGE = 257; -    int RIL_REQUEST_GET_TIME_FOR_NEXT_SATELLITE_VISIBILITY = 258; -    int RIL_REQUEST_IS_NULL_CIPHER_AND_INTEGRITY_ENABLED = 259; -    int RIL_REQUEST_SET_SATELLITE_PLMN = 260; +    int RIL_REQUEST_IS_NULL_CIPHER_AND_INTEGRITY_ENABLED = 245;      /* Responses begin */      int RIL_RESPONSE_ACKNOWLEDGEMENT = 800; @@ -645,13 +605,6 @@ public interface RILConstants {      int RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_CHANGED = 1053;      int RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_RECORDS_RECEIVED = 1054;      int RIL_UNSOL_SLICING_CONFIG_CHANGED = 1055; -    int RIL_UNSOL_PENDING_SATELLITE_MESSAGE_COUNT = 1056; -    int RIL_UNSOL_NEW_SATELLITE_MESSAGES = 1057; -    int RIL_UNSOL_SATELLITE_MESSAGES_TRANSFER_COMPLETE = 1058; -    int RIL_UNSOL_SATELLITE_POINTING_INFO_CHANGED = 1059; -    int RIL_UNSOL_SATELLITE_MODE_CHANGED = 1060; -    int RIL_UNSOL_SATELLITE_RADIO_TECHNOLOGY_CHANGED = 1061; -    int RIL_UNSOL_SATELLITE_PROVISION_STATE_CHANGED = 1062;      /* The following unsols are not defined in RIL.h */      int RIL_UNSOL_HAL_NON_RIL_BASE = 1100; diff --git a/test-mock/api/current.txt b/test-mock/api/current.txt index 241e69106718..f61cce666cca 100644 --- a/test-mock/api/current.txt +++ b/test-mock/api/current.txt @@ -1,6 +1,4 @@  // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704  package android.test.mock {    @Deprecated public class MockAccountManager { @@ -202,7 +200,7 @@ package android.test.mock {      method @Deprecated public int checkPermission(String, String);      method @Deprecated public int checkSignatures(String, String);      method @Deprecated public int checkSignatures(int, int); -    method public void clearInstantAppCookie(); +    method @Deprecated public void clearInstantAppCookie();      method @Deprecated public void clearPackagePreferredActivities(String);      method @Deprecated public String[] currentToCanonicalPackageNames(String[]);      method @Deprecated public void extendVerificationTimeout(int, int, long); @@ -224,15 +222,15 @@ package android.test.mock {      method @Deprecated public CharSequence getApplicationLabel(android.content.pm.ApplicationInfo);      method @Deprecated public android.graphics.drawable.Drawable getApplicationLogo(android.content.pm.ApplicationInfo);      method @Deprecated public android.graphics.drawable.Drawable getApplicationLogo(String) throws android.content.pm.PackageManager.NameNotFoundException; -    method public android.content.pm.ChangedPackages getChangedPackages(int); +    method @Deprecated public android.content.pm.ChangedPackages getChangedPackages(int);      method @Deprecated public int getComponentEnabledSetting(android.content.ComponentName);      method @Deprecated public android.graphics.drawable.Drawable getDefaultActivityIcon();      method @Deprecated public android.graphics.drawable.Drawable getDrawable(String, int, android.content.pm.ApplicationInfo);      method @Deprecated public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);      method @Deprecated public java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);      method @Deprecated public String getInstallerPackageName(String); -    method public byte[] getInstantAppCookie(); -    method public int getInstantAppCookieMaxBytes(); +    method @Deprecated public byte[] getInstantAppCookie(); +    method @Deprecated public int getInstantAppCookieMaxBytes();      method @Deprecated public android.content.pm.InstrumentationInfo getInstrumentationInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;      method @Deprecated public android.content.Intent getLaunchIntentForPackage(String);      method @Deprecated public android.content.Intent getLeanbackLaunchIntentForPackage(String); @@ -241,7 +239,7 @@ package android.test.mock {      method @Deprecated public int[] getPackageGids(String, int) throws android.content.pm.PackageManager.NameNotFoundException;      method @Deprecated public android.content.pm.PackageInfo getPackageInfo(String, int) throws android.content.pm.PackageManager.NameNotFoundException;      method @Deprecated public android.content.pm.PackageInfo getPackageInfo(android.content.pm.VersionedPackage, int) throws android.content.pm.PackageManager.NameNotFoundException; -    method public android.content.pm.PackageInstaller getPackageInstaller(); +    method @Deprecated public android.content.pm.PackageInstaller getPackageInstaller();      method @Deprecated public int getPackageUid(String, int) throws android.content.pm.PackageManager.NameNotFoundException;      method @Deprecated public String[] getPackagesForUid(int);      method @Deprecated public java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions(String[], int); @@ -265,8 +263,8 @@ package android.test.mock {      method @Deprecated public android.content.res.XmlResourceParser getXml(String, int, android.content.pm.ApplicationInfo);      method @Deprecated public boolean hasSystemFeature(String);      method @Deprecated public boolean hasSystemFeature(String, int); -    method public boolean isInstantApp(); -    method public boolean isInstantApp(String); +    method @Deprecated public boolean isInstantApp(); +    method @Deprecated public boolean isInstantApp(String);      method @Deprecated public boolean isPermissionRevokedByPolicy(String, String);      method @Deprecated public boolean isSafeMode();      method @Deprecated public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int); @@ -283,11 +281,12 @@ package android.test.mock {      method @Deprecated public android.content.pm.ProviderInfo resolveContentProvider(String, int);      method @Deprecated public android.content.pm.ResolveInfo resolveService(android.content.Intent, int);      method @Deprecated public android.content.pm.ResolveInfo resolveServiceAsUser(android.content.Intent, int, int); -    method public void setApplicationCategoryHint(String, int); +    method @Deprecated public void setApplicationCategoryHint(String, int);      method @Deprecated public void setApplicationEnabledSetting(String, int, int);      method @Deprecated public void setComponentEnabledSetting(android.content.ComponentName, int, int);      method @Deprecated public void setInstallerPackageName(String, String); -    method public void updateInstantAppCookie(@NonNull byte[]); +    method @Deprecated public boolean setInstantAppCookie(@NonNull byte[]); +    method @Deprecated public void updateInstantAppCookie(@NonNull byte[]);      method @Deprecated public void verifyPendingInstall(int, int);    } diff --git a/test-mock/api/removed.txt b/test-mock/api/removed.txt index fa2fbd276e9a..0c800b6f82b5 100644 --- a/test-mock/api/removed.txt +++ b/test-mock/api/removed.txt @@ -1,6 +1,4 @@  // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704  package android.test.mock {    public class MockContext extends android.content.Context { @@ -11,7 +9,7 @@ package android.test.mock {    @Deprecated public class MockPackageManager extends android.content.pm.PackageManager {      method @Deprecated public String getDefaultBrowserPackageName(int);      method @Deprecated public boolean setDefaultBrowserPackageName(String, int); -    method public boolean setInstantAppCookie(@NonNull byte[]); +    method @Deprecated public boolean setInstantAppCookie(@NonNull byte[]);    }  } diff --git a/test-mock/api/system-current.txt b/test-mock/api/system-current.txt index 2b5132ecd14f..f35095743738 100644 --- a/test-mock/api/system-current.txt +++ b/test-mock/api/system-current.txt @@ -1,6 +1,4 @@  // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704  package android.test.mock {    public class MockContext extends android.content.Context { @@ -11,30 +9,30 @@ package android.test.mock {    }    @Deprecated public class MockPackageManager extends android.content.pm.PackageManager { -    method public void addOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener); -    method public boolean arePermissionsIndividuallyControlled(); +    method @Deprecated public void addOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener); +    method @Deprecated public boolean arePermissionsIndividuallyControlled();      method @Deprecated public java.util.List<android.content.IntentFilter> getAllIntentFilters(String); -    method public String getDefaultBrowserPackageNameAsUser(int); -    method public java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(int, int); -    method public android.graphics.drawable.Drawable getInstantAppIcon(String); -    method public android.content.ComponentName getInstantAppInstallerComponent(); -    method public android.content.ComponentName getInstantAppResolverSettingsComponent(); -    method public java.util.List<android.content.pm.InstantAppInfo> getInstantApps(); -    method public java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(String); -    method public int getIntentVerificationStatusAsUser(String, int); -    method public int getPermissionFlags(String, String, android.os.UserHandle); -    method public void grantRuntimePermission(String, String, android.os.UserHandle); -    method public int installExistingPackage(String) throws android.content.pm.PackageManager.NameNotFoundException; -    method public int installExistingPackage(String, int) throws android.content.pm.PackageManager.NameNotFoundException; -    method public void registerDexModule(String, @Nullable android.content.pm.PackageManager.DexModuleRegisterCallback); -    method public void removeOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener); -    method public void revokeRuntimePermission(String, String, android.os.UserHandle); -    method public boolean setDefaultBrowserPackageNameAsUser(String, int); +    method @Deprecated public String getDefaultBrowserPackageNameAsUser(int); +    method @Deprecated public java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(int, int); +    method @Deprecated public android.graphics.drawable.Drawable getInstantAppIcon(String); +    method @Deprecated public android.content.ComponentName getInstantAppInstallerComponent(); +    method @Deprecated public android.content.ComponentName getInstantAppResolverSettingsComponent(); +    method @Deprecated public java.util.List<android.content.pm.InstantAppInfo> getInstantApps(); +    method @Deprecated public java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(String); +    method @Deprecated public int getIntentVerificationStatusAsUser(String, int); +    method @Deprecated public int getPermissionFlags(String, String, android.os.UserHandle); +    method @Deprecated public void grantRuntimePermission(String, String, android.os.UserHandle); +    method @Deprecated public int installExistingPackage(String) throws android.content.pm.PackageManager.NameNotFoundException; +    method @Deprecated public int installExistingPackage(String, int) throws android.content.pm.PackageManager.NameNotFoundException; +    method @Deprecated public void registerDexModule(String, @Nullable android.content.pm.PackageManager.DexModuleRegisterCallback); +    method @Deprecated public void removeOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener); +    method @Deprecated public void revokeRuntimePermission(String, String, android.os.UserHandle); +    method @Deprecated public boolean setDefaultBrowserPackageNameAsUser(String, int);      method public String[] setPackagesSuspended(String[], boolean, android.os.PersistableBundle, android.os.PersistableBundle, String); -    method public void setUpdateAvailable(String, boolean); -    method public boolean updateIntentVerificationStatusAsUser(String, int, int); -    method public void updatePermissionFlags(String, String, int, int, android.os.UserHandle); -    method public void verifyIntentFilter(int, int, java.util.List<java.lang.String>); +    method @Deprecated public void setUpdateAvailable(String, boolean); +    method @Deprecated public boolean updateIntentVerificationStatusAsUser(String, int, int); +    method @Deprecated public void updatePermissionFlags(String, String, int, int, android.os.UserHandle); +    method @Deprecated public void verifyIntentFilter(int, int, java.util.List<java.lang.String>);    }  } diff --git a/test-mock/api/system-removed.txt b/test-mock/api/system-removed.txt index 14191ebcb080..d802177e249b 100644 --- a/test-mock/api/system-removed.txt +++ b/test-mock/api/system-removed.txt @@ -1,3 +1 @@  // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704 diff --git a/test-mock/api/test-current.txt b/test-mock/api/test-current.txt index 1752edcd469e..9ed010881067 100644 --- a/test-mock/api/test-current.txt +++ b/test-mock/api/test-current.txt @@ -1,6 +1,4 @@  // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704  package android.test.mock {    public class MockContext extends android.content.Context { @@ -8,13 +6,13 @@ package android.test.mock {    }    @Deprecated public class MockPackageManager extends android.content.pm.PackageManager { -    method public void addCrossProfileIntentFilter(android.content.IntentFilter, int, int, int); -    method public void clearCrossProfileIntentFilters(int); -    method public int getInstallReason(String, android.os.UserHandle); -    method public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplicationsAsUser(int, int); -    method public String[] getNamesForUids(int[]); -    method @NonNull public String getServicesSystemSharedLibraryPackageName(); -    method @NonNull public String getSharedSystemSharedLibraryPackageName(); +    method @Deprecated public void addCrossProfileIntentFilter(android.content.IntentFilter, int, int, int); +    method @Deprecated public void clearCrossProfileIntentFilters(int); +    method @Deprecated public int getInstallReason(String, android.os.UserHandle); +    method @Deprecated public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplicationsAsUser(int, int); +    method @Deprecated public String[] getNamesForUids(int[]); +    method @Deprecated @NonNull public String getServicesSystemSharedLibraryPackageName(); +    method @Deprecated @NonNull public String getSharedSystemSharedLibraryPackageName();    }  } diff --git a/test-mock/api/test-removed.txt b/test-mock/api/test-removed.txt index 14191ebcb080..d802177e249b 100644 --- a/test-mock/api/test-removed.txt +++ b/test-mock/api/test-removed.txt @@ -1,3 +1 @@  // Signature format: 2.0 -// - add-additional-overrides=no -// - migrating=Migration in progress see b/299366704 diff --git a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java index 79348d104745..ae7c2a99b808 100644 --- a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java +++ b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java @@ -247,7 +247,7 @@ public class GraphicsActivity extends Activity {              int rc = 0;              try (SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) { -                transaction.setFrameRateCategory(mSurfaceControl, category); +                transaction.setFrameRateCategory(mSurfaceControl, category, false);                  transaction.apply();              }              return rc; diff --git a/tests/FlickerTests/AndroidTestTemplate.xml b/tests/FlickerTests/AndroidTestTemplate.xml index 63acddf25e02..0f4798090da8 100644 --- a/tests/FlickerTests/AndroidTestTemplate.xml +++ b/tests/FlickerTests/AndroidTestTemplate.xml @@ -61,9 +61,7 @@          <option name="shell-timeout" value="6600s"/>          <option name="test-timeout" value="6600s"/>          <option name="hidden-api-checks" value="false"/> -        <!-- TODO(b/288396763): re-enable when PerfettoListener is fixed          <option name="device-listeners" value="android.device.collectors.PerfettoListener"/> -        -->          <!-- PerfettoListener related arguments -->          <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true"/>          <option name="instrumentation-arg" diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt index 359845dc0de6..47d6d235848f 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt @@ -36,7 +36,8 @@ import org.junit.runners.Parameterized  /**   * Test launching a secondary Activity into Picture-In-Picture mode.   * - * Setup: Start from a split A|B. Transition: B enters PIP, observe the window shrink to the bottom + * Setup: Start from a split A|B. + * Transition: B enters PIP, observe the window first goes fullscreen then shrink to the bottom   * right corner on screen.   *   * To run this test: `atest FlickerTestsOther:SecondaryActivityEnterPipTest` @@ -63,7 +64,16 @@ class SecondaryActivityEnterPipTest(flicker: LegacyFlickerTest) :          }      } -    /** Main and secondary activity start from a split each taking half of the screen. */ +    /** +     * We expect the background layer to be visible during this transition. +     */ +    @Presubmit +    @Test +    override fun backgroundLayerNeverVisible(): Unit {} + +    /** +     * Main and secondary activity start from a split each taking half of the screen. +     */      @Presubmit      @Test      fun layersStartFromEqualSplit() { @@ -109,7 +119,7 @@ class SecondaryActivityEnterPipTest(flicker: LegacyFlickerTest) :                  .isVisible(TRANSITION_SNAPSHOT)                  .isInvisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)                  .then() -                .isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) +                .isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT, isOptional = true)          }          flicker.assertLayersEnd {              visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) diff --git a/tools/aapt2/integration-tests/AutoVersionTest/Android.bp b/tools/aapt2/integration-tests/AutoVersionTest/Android.bp index bfd35083366e..c901efa707f4 100644 --- a/tools/aapt2/integration-tests/AutoVersionTest/Android.bp +++ b/tools/aapt2/integration-tests/AutoVersionTest/Android.bp @@ -26,4 +26,5 @@ package {  android_test {      name: "AaptAutoVersionTest",      sdk_version: "current", +    use_resource_processor: false,  } diff --git a/tools/aapt2/integration-tests/BasicTest/Android.bp b/tools/aapt2/integration-tests/BasicTest/Android.bp index 7db9d2698cc7..d0649ea4ef9c 100644 --- a/tools/aapt2/integration-tests/BasicTest/Android.bp +++ b/tools/aapt2/integration-tests/BasicTest/Android.bp @@ -26,4 +26,5 @@ package {  android_test {      name: "AaptBasicTest",      sdk_version: "current", +    use_resource_processor: false,  } diff --git a/tools/aapt2/integration-tests/StaticLibTest/App/Android.bp b/tools/aapt2/integration-tests/StaticLibTest/App/Android.bp index 80404eeb8d8e..ebb4e9f479d6 100644 --- a/tools/aapt2/integration-tests/StaticLibTest/App/Android.bp +++ b/tools/aapt2/integration-tests/StaticLibTest/App/Android.bp @@ -24,9 +24,9 @@ package {  }  android_test { -      name: "AaptTestStaticLib_App",      sdk_version: "current", +    use_resource_processor: false,      srcs: ["src/**/*.java"],      asset_dirs: [          "assets", diff --git a/tools/aapt2/integration-tests/StaticLibTest/LibOne/Android.bp b/tools/aapt2/integration-tests/StaticLibTest/LibOne/Android.bp index a84da43c70c8..ee12a92906a8 100644 --- a/tools/aapt2/integration-tests/StaticLibTest/LibOne/Android.bp +++ b/tools/aapt2/integration-tests/StaticLibTest/LibOne/Android.bp @@ -26,6 +26,7 @@ package {  android_library {      name: "AaptTestStaticLib_LibOne",      sdk_version: "current", +    use_resource_processor: false,      srcs: ["src/**/*.java"],      resource_dirs: ["res"],  } diff --git a/tools/aapt2/integration-tests/StaticLibTest/LibTwo/Android.bp b/tools/aapt2/integration-tests/StaticLibTest/LibTwo/Android.bp index d386c3a35d20..83b2362496fc 100644 --- a/tools/aapt2/integration-tests/StaticLibTest/LibTwo/Android.bp +++ b/tools/aapt2/integration-tests/StaticLibTest/LibTwo/Android.bp @@ -26,6 +26,7 @@ package {  android_library {      name: "AaptTestStaticLib_LibTwo",      sdk_version: "current", +    use_resource_processor: false,      srcs: ["src/**/*.java"],      resource_dirs: ["res"],      libs: ["AaptTestStaticLib_LibOne"], diff --git a/tools/aapt2/integration-tests/SymlinkTest/Android.bp b/tools/aapt2/integration-tests/SymlinkTest/Android.bp index 1e8cf86ed811..15a6a207d6d1 100644 --- a/tools/aapt2/integration-tests/SymlinkTest/Android.bp +++ b/tools/aapt2/integration-tests/SymlinkTest/Android.bp @@ -26,4 +26,5 @@ package {  android_test {      name: "AaptSymlinkTest",      sdk_version: "current", +    use_resource_processor: false,  } diff --git a/tools/hoststubgen/hoststubgen/Android.bp b/tools/hoststubgen/hoststubgen/Android.bp index a617876a6da0..226e2fadf735 100644 --- a/tools/hoststubgen/hoststubgen/Android.bp +++ b/tools/hoststubgen/hoststubgen/Android.bp @@ -7,9 +7,38 @@ package {      default_applicable_licenses: ["frameworks_base_license"],  } +// Visibility only for ravenwood prototype uses. +genrule_defaults { +    name: "hoststubgen-for-prototype-only-genrule", +    visibility: [ +        ":__subpackages__", +        "//frameworks/base/ravenwood:__subpackages__", +    ], +} + +// Visibility only for ravenwood prototype uses. +java_defaults { +    name: "hoststubgen-for-prototype-only-java", +    visibility: [ +        ":__subpackages__", +        "//frameworks/base/ravenwood:__subpackages__", +    ], +} + +// Visibility only for ravenwood prototype uses. +filegroup_defaults { +    name: "hoststubgen-for-prototype-only-filegroup", +    visibility: [ +        ":__subpackages__", +        "//frameworks/base/ravenwood:__subpackages__", +    ], +} +  // This library contains the standard hoststubgen annotations. +// This is only for the prototype. The productionized version is "ravenwood-annotations".  java_library {      name: "hoststubgen-annotations", +    defaults: ["hoststubgen-for-prototype-only-java"],      srcs: [          "annotations-src/**/*.java",      ], @@ -18,7 +47,6 @@ java_library {      // Seems like we need it to avoid circular deps.      // Copied it from "app-compat-annotations".      sdk_version: "core_current", -    visibility: ["//visibility:public"],  }  // This library contains helper classes used in the host side test environment at runtime. @@ -30,11 +58,6 @@ java_library_host {      ],      libs: [          "junit", -        "ow2-asm", -        "ow2-asm-analysis", -        "ow2-asm-commons", -        "ow2-asm-tree", -        "ow2-asm-util",      ],      static_libs: [          "guava", @@ -60,12 +83,13 @@ java_binary_host {  }  // File that contains the standard command line argumetns to hoststubgen. +// This is only for the prototype. The productionized version is "ravenwood-standard-options".  filegroup {      name: "hoststubgen-standard-options", +    defaults: ["hoststubgen-for-prototype-only-filegroup"],      srcs: [          "hoststubgen-standard-options.txt",      ], -    visibility: ["//visibility:public"],  }  hoststubgen_common_options = "$(location hoststubgen) " + @@ -98,7 +122,6 @@ genrule_defaults {          "hoststubgen_keep_all.txt",          "hoststubgen_dump.txt",      ], -    // visibility:  ["//visibility:public"],  }  // Generate the stub/impl from framework-all, with hidden APIs. @@ -116,8 +139,10 @@ java_genrule_host {  }  // Extract the stub jar from "framework-all-host" for subsequent build rules. +// This is only for the prototype. Do not use it in "productionized" build rules.  java_genrule_host {      name: "framework-all-hidden-api-host-stub", +    defaults: ["hoststubgen-for-prototype-only-genrule"],      cmd: "cp $(in) $(out)",      srcs: [          ":framework-all-hidden-api-host{host_stub.jar}", @@ -125,12 +150,13 @@ java_genrule_host {      out: [          "host_stub.jar",      ], -    visibility: ["//visibility:public"],  }  // Extract the impl jar from "framework-all-host" for subsequent build rules. +// This is only for the prototype. Do not use it in "productionized" build rules.  java_genrule_host {      name: "framework-all-hidden-api-host-impl", +    defaults: ["hoststubgen-for-prototype-only-genrule"],      cmd: "cp $(in) $(out)",      srcs: [          ":framework-all-hidden-api-host{host_impl.jar}", @@ -138,11 +164,11 @@ java_genrule_host {      out: [          "host_impl.jar",      ], -    visibility: ["//visibility:public"],  }  // Generate the stub/impl from framework-all, with only public/system/test APIs, without  // hidden APIs. +// This is only for the prototype. Do not use it in "productionized" build rules.  java_genrule_host {      name: "framework-all-test-api-host",      defaults: ["hoststubgen-command-defaults"], @@ -159,8 +185,10 @@ java_genrule_host {  }  // Extract the stub jar from "framework-all-test-api-host" for subsequent build rules. +// This is only for the prototype. Do not use it in "productionized" build rules.  java_genrule_host {      name: "framework-all-test-api-host-stub", +    defaults: ["hoststubgen-for-prototype-only-genrule"],      cmd: "cp $(in) $(out)",      srcs: [          ":framework-all-test-api-host{host_stub.jar}", @@ -168,12 +196,13 @@ java_genrule_host {      out: [          "host_stub.jar",      ], -    visibility: ["//visibility:public"],  }  // Extract the impl jar from "framework-all-test-api-host" for subsequent build rules. +// This is only for the prototype. Do not use it in "productionized" build rules.  java_genrule_host {      name: "framework-all-test-api-host-impl", +    defaults: ["hoststubgen-for-prototype-only-genrule"],      cmd: "cp $(in) $(out)",      srcs: [          ":framework-all-test-api-host{host_impl.jar}", @@ -181,7 +210,6 @@ java_genrule_host {      out: [          "host_impl.jar",      ], -    visibility: ["//visibility:public"],  }  // This library contains helper classes to build hostside tests/targets. @@ -191,6 +219,7 @@ java_genrule_host {  // Ideally this library should be empty.  java_library_host {      name: "hoststubgen-helper-framework-buildtime", +    defaults: ["hoststubgen-for-prototype-only-java"],      srcs: [          "helper-framework-buildtime-src/**/*.java",      ], @@ -200,13 +229,13 @@ java_library_host {          "framework-all-hidden-api-host-impl",          "junit",      ], -    visibility: ["//visibility:public"],  }  // This module contains "fake" libcore/dalvik classes, framework native substitution, etc,  // that are needed at runtime.  java_library_host {      name: "hoststubgen-helper-framework-runtime", +    defaults: ["hoststubgen-for-prototype-only-java"],      srcs: [          "helper-framework-runtime-src/**/*.java",      ], @@ -214,7 +243,6 @@ java_library_host {          "hoststubgen-helper-runtime",          "framework-all-hidden-api-host-impl",      ], -    visibility: ["//visibility:public"],  }  // Defaults for host side test modules. diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedKeepClass.java b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedKeepClass.java index 4c37579ac917..7ada961d710f 100644 --- a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedKeepClass.java +++ b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedKeepClass.java @@ -17,8 +17,6 @@ package com.android.hoststubgen.hosthelper;  import static java.lang.annotation.ElementType.TYPE; -import org.objectweb.asm.Type; -  import java.lang.annotation.Retention;  import java.lang.annotation.RetentionPolicy;  import java.lang.annotation.Target; @@ -29,6 +27,6 @@ import java.lang.annotation.Target;  @Target({TYPE})  @Retention(RetentionPolicy.RUNTIME)  public @interface HostStubGenProcessedKeepClass { -    String CLASS_INTERNAL_NAME = Type.getInternalName(HostStubGenProcessedKeepClass.class); +    String CLASS_INTERNAL_NAME = HostTestUtils.getInternalName(HostStubGenProcessedKeepClass.class);      String CLASS_DESCRIPTOR = "L" + CLASS_INTERNAL_NAME + ";";  } diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedStubClass.java b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedStubClass.java index 34e0030f548a..e598da0a3cb9 100644 --- a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedStubClass.java +++ b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedStubClass.java @@ -17,8 +17,6 @@ package com.android.hoststubgen.hosthelper;  import static java.lang.annotation.ElementType.TYPE; -import org.objectweb.asm.Type; -  import java.lang.annotation.Retention;  import java.lang.annotation.RetentionPolicy;  import java.lang.annotation.Target; @@ -29,6 +27,6 @@ import java.lang.annotation.Target;  @Target({TYPE})  @Retention(RetentionPolicy.RUNTIME)  public @interface HostStubGenProcessedStubClass { -    String CLASS_INTERNAL_NAME = Type.getInternalName(HostStubGenProcessedStubClass.class); +    String CLASS_INTERNAL_NAME = HostTestUtils.getInternalName(HostStubGenProcessedStubClass.class);      String CLASS_DESCRIPTOR = "L" + CLASS_INTERNAL_NAME + ";";  } diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java index d176b5e97b0b..9f835979b238 100644 --- a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java +++ b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java @@ -15,8 +15,6 @@   */  package com.android.hoststubgen.hosthelper; -import org.objectweb.asm.Type; -  import java.io.PrintStream;  import java.lang.StackWalker.Option;  import java.lang.reflect.Method; @@ -32,7 +30,15 @@ public class HostTestUtils {      private HostTestUtils() {      } -    public static final String CLASS_INTERNAL_NAME = Type.getInternalName(HostTestUtils.class); +    /** +     * Same as ASM's Type.getInternalName(). Copied here, to avoid having a reference to ASM +     * in this JAR. +     */ +    public static String getInternalName(final Class<?> clazz) { +        return clazz.getName().replace('.', '/'); +    } + +    public static final String CLASS_INTERNAL_NAME = getInternalName(HostTestUtils.class);      /** If true, we won't print method call log. */      private static final boolean SKIP_METHOD_LOG = "1".equals(System.getenv( diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh b/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh index 722905f15b41..6bf074b1bf9f 100755 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh @@ -16,14 +16,8 @@  source "${0%/*}"/../../common.sh -#********************************************************************************************** -#This script is broken because it relies on soong intermediate files, which seem to have moved. -#********************************************************************************************** -  # This scripts run the "tiny-framework" test, but does most stuff from the command line, using  # the native java and javac commands. -# This is useful to -  debug=0  while getopts "d" opt; do @@ -61,7 +55,7 @@ framework_compile_classpaths=(  test_compile_classpaths=(    $SOONG_INT/external/junit/junit/android_common/combined/junit.jar -  $ANDROID_BUILD_TOP/out/target/common/obj/JAVA_LIBRARIES/truth-prebuilt_intermediates/classes.jar +  $ANDROID_HOST_OUT/framework/truth-prebuilt.jar  )  test_runtime_classpaths=( 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 246d06526f5b..cd604ff97f73 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 @@ -23,6 +23,10 @@ import org.junit.Rule;  import org.junit.Test;  import org.junit.rules.ExpectedException; +import java.io.FileDescriptor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +  public class TinyFrameworkClassTest {      @Rule      public ExpectedException thrown = ExpectedException.none(); @@ -140,4 +144,42 @@ public class TinyFrameworkClassTest {          assertThat(TinyFrameworkClassLoadHook.sLoadedClasses)                  .doesNotContain(TinyFrameworkNestedClasses.class);      } + +    /** +     * Test to try accessing JDK private fields using reflections + setAccessible(true), +     * which is now disallowed due to Java Modules, unless you run the javacommand with. +     *   --add-opens=java.base/java.io=ALL-UNNAMED +     * +     * You can try it from the command line, like: +     * $ JAVA_OPTS="--add-opens=java.base/java.io=ALL-UNNAMED" ./run-test-manually.sh +     * +     * @throws Exception +     */ +    @Test +    public void testFileDescriptor() throws Exception { +        var fd = FileDescriptor.out; + +        // Get the FD value directly from the private field. +        // This is now prohibited due to Java Modules. +        // It throws: +        // java.lang.reflect.InaccessibleObjectException: Unable to make field private int java.io.FileDescriptor.fd accessible: module java.base does not "opens java.io" to unnamed module @3bb50eaa + +        thrown.expect(java.lang.reflect.InaccessibleObjectException.class); + +        // Access the private field. +        final Field f = FileDescriptor.class.getDeclaredField("fd"); +        final Method m = FileDescriptor.class.getDeclaredMethod("set", int.class); +        f.setAccessible(true); +        m.setAccessible(true); + +        assertThat(f.get(fd)).isEqualTo(1); + +        // Set +        f.set(fd, 2); +        assertThat(f.get(fd)).isEqualTo(2); + +        // Call the package private method, set(int). +        m.invoke(fd, 0); +        assertThat(f.get(fd)).isEqualTo(0); +    }  } diff --git a/tools/hoststubgen/scripts/run-all-tests.sh b/tools/hoststubgen/scripts/run-all-tests.sh index 2e9cf428354a..8bc88de608e5 100755 --- a/tools/hoststubgen/scripts/run-all-tests.sh +++ b/tools/hoststubgen/scripts/run-all-tests.sh @@ -34,8 +34,7 @@ run ./hoststubgen/test-tiny-framework/diff-and-update-golden.sh  run ./hoststubgen/test-framework/run-test-without-atest.sh -#This script is broken because it relies on soong intermediate files, which seem to have moved. -#run ./hoststubgen/test-tiny-framework/run-test-manually.sh +run ./hoststubgen/test-tiny-framework/run-test-manually.sh  run atest tiny-framework-dump-test  run ./scripts/build-framework-hostside-jars-and-extract.sh diff --git a/tools/lint/global/integration_tests/Android.bp b/tools/lint/global/integration_tests/Android.bp index ca96559ac016..40281d263a4c 100644 --- a/tools/lint/global/integration_tests/Android.bp +++ b/tools/lint/global/integration_tests/Android.bp @@ -12,25 +12,58 @@  // See the License for the specific language governing permissions and  // limitations under the License. -java_library { -    name: "AndroidGlobalLintTestNoAidl", -    srcs: ["TestNoAidl/**/*.java"], +// Integration tests for @EnforcePermission linters. +// Each test defines its own java_library. The XML lint report from this +// java_library is wrapped under a Python library with a unique pkg_path (this +// is to avoid a name conflict for the report file). All the tests are +// referenced and executed by AndroidGlobalLintCheckerIntegrationTest. + +java_defaults { +    name: "AndroidGlobalLintIntegrationTestDefault",      libs: [          "framework-annotations-lib",      ],      lint: { -        // It is expected that lint returns an error when processing this +        // It is expected that lint returns an error when processing the          // library. Silence it here, the lint output is verified in tests.py.          suppress_exit_code: true,      },  } +java_library { +    name: "AndroidGlobalLintTestNoAidl", +    srcs: ["TestNoAidl/**/*.java"], +    defaults: ["AndroidGlobalLintIntegrationTestDefault"], +} + +python_library_host { +    name: "AndroidGlobalLintTestNoAidl_py", +    data: [":AndroidGlobalLintTestNoAidl{.lint}"], +    pkg_path: "no_aidl", +} + +java_library { +    name: "AndroidGlobalLintTestMissingAnnotation", +    srcs: [ +        "TestMissingAnnotation/**/*.java", +        "TestMissingAnnotation/**/*.aidl", +    ], +    defaults: ["AndroidGlobalLintIntegrationTestDefault"], +} + +python_library_host { +    name: "AndroidGlobalLintTestMissingAnnotation_py", +    data: [":AndroidGlobalLintTestMissingAnnotation{.lint}"], +    pkg_path: "missing_annotation", +} +  python_test_host {      name: "AndroidGlobalLintCheckerIntegrationTest",      srcs: ["tests.py"],      main: "tests.py", -    data: [ -        ":AndroidGlobalLintTestNoAidl{.lint}", +    libs: [ +        "AndroidGlobalLintTestNoAidl_py", +        "AndroidGlobalLintTestMissingAnnotation_py",      ],      version: {          py3: { diff --git a/tools/lint/global/integration_tests/TestMissingAnnotation/TestMissingAnnotation.java b/tools/lint/global/integration_tests/TestMissingAnnotation/TestMissingAnnotation.java new file mode 100644 index 000000000000..9e4854c61f96 --- /dev/null +++ b/tools/lint/global/integration_tests/TestMissingAnnotation/TestMissingAnnotation.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + *      http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.lint.integration_tests; + +/** + * A class that implements an AIDL interface, but is missing the @EnforcePermission annotation. + */ +class TestMissingAnnotation extends IFoo.Stub { + +    @Override +    public void Method() { +    } + +} diff --git a/tools/lint/global/integration_tests/TestMissingAnnotation/com/google/android/lint/integration_tests/IFoo.aidl b/tools/lint/global/integration_tests/TestMissingAnnotation/com/google/android/lint/integration_tests/IFoo.aidl new file mode 100644 index 000000000000..95ec2c230599 --- /dev/null +++ b/tools/lint/global/integration_tests/TestMissingAnnotation/com/google/android/lint/integration_tests/IFoo.aidl @@ -0,0 +1,7 @@ +package com.google.android.lint.integration_tests; + +interface IFoo { + +    @EnforcePermission("INTERNET") +    void Method(); +} diff --git a/tools/lint/global/integration_tests/tests.py b/tools/lint/global/integration_tests/tests.py index fc3eeb4f8ed9..cdb16b8ba25f 100644 --- a/tools/lint/global/integration_tests/tests.py +++ b/tools/lint/global/integration_tests/tests.py @@ -19,16 +19,28 @@ import xml.etree.ElementTree  class TestLinterReports(unittest.TestCase):      """Integration tests for the linters used by @EnforcePermission.""" -    def test_no_aidl(self): -        report = pkgutil.get_data("lint", "lint-report.xml").decode() +    def _read_report(self, pkg_path): +        report = pkgutil.get_data(pkg_path, "lint/lint-report.xml").decode()          issues = xml.etree.ElementTree.fromstring(report)          self.assertEqual(issues.tag, "issues") +        return issues + +    def test_no_aidl(self): +        issues = self._read_report("no_aidl")          self.assertEqual(len(issues), 1)          issue = issues[0]          self.assertEqual(issue.attrib["id"], "MisusingEnforcePermissionAnnotation")          self.assertEqual(issue.attrib["severity"], "Error") +    def test_missing_annotation(self): +        issues = self._read_report("missing_annotation") +        self.assertEqual(len(issues), 1) + +        issue = issues[0] +        self.assertEqual(issue.attrib["id"], "MissingEnforcePermissionAnnotation") +        self.assertEqual(issue.attrib["severity"], "Error") +  if __name__ == '__main__':      unittest.main(verbosity=2)  |