diff options
799 files changed, 21897 insertions, 10580 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 864caf4da9fd..a1375d79846d 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -14,6 +14,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.hardware.radio.flags-aconfig-java{.generated_srcjars}", ":android.nfc.flags-aconfig-java{.generated_srcjars}", @@ -35,14 +36,18 @@ aconfig_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}", ":android.app.flags-aconfig-java{.generated_srcjars}", ":android.credentials.flags-aconfig-java{.generated_srcjars}", ":android.view.contentprotection.flags-aconfig-java{.generated_srcjars}", ":android.service.voice.flags-aconfig-java{.generated_srcjars}", ":android.service.autofill.flags-aconfig-java{.generated_srcjars}", + ":com.android.net.flags-aconfig-java{.generated_srcjars}", ] filegroup { @@ -292,6 +297,13 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +java_aconfig_library { + name: "android.content.pm.flags-aconfig-java-host", + aconfig_declarations: "android.content.pm.flags-aconfig", + host_supported: true, + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + // Media BetterTogether aconfig_declarations { name: "com.android.media.flags.bettertogether-aconfig", @@ -316,6 +328,24 @@ java_aconfig_library { name: "android.permission.flags-aconfig-java", aconfig_declarations: "android.permission.flags-aconfig", defaults: ["framework-minus-apex-aconfig-java-defaults"], + min_sdk_version: "30", + apex_available: [ + "com.android.permission", + ], + +} + +// 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 @@ -338,6 +368,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", @@ -345,6 +381,12 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +java_aconfig_library { + name: "com.android.internal.foldables.flags-aconfig-java", + aconfig_declarations: "fold_lock_setting_flags", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + // Multi user aconfig_declarations { name: "android.multiuser.flags-aconfig", @@ -429,7 +471,7 @@ aconfig_declarations { package: "android.service.autofill", srcs: [ "services/autofill/bugfixes.aconfig", - "services/autofill/features.aconfig" + "services/autofill/features.aconfig", ], } @@ -438,3 +480,23 @@ java_aconfig_library { aconfig_declarations: "android.service.autofill.flags-aconfig", defaults: ["framework-minus-apex-aconfig-java-defaults"], } + +// Companion +aconfig_declarations { + name: "android.companion.flags-aconfig", + package: "android.companion", + srcs: ["core/java/android/companion/*.aconfig"], +} + +java_aconfig_library { + name: "android.companion.flags-aconfig-java", + 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 b1b332a9a2b5..a507465aa419 100644 --- a/Android.bp +++ b/Android.bp @@ -64,6 +64,7 @@ filegroup { srcs: [ // Java/AIDL sources under frameworks/base ":framework-annotations", + ":ravenwood-annotations", ":framework-blobstore-sources", ":framework-core-sources", ":framework-drm-sources", @@ -284,6 +285,7 @@ java_defaults { enforce_permissions_exceptions: [ // Do not add entries to this list. ":framework-annotations", + ":ravenwood-annotations", ":framework-blobstore-sources", ":framework-core-sources", ":framework-drm-sources", @@ -409,7 +411,6 @@ java_defaults { "audiopolicy-aidl-java", "sounddose-aidl-java", "modules-utils-expresslog", - "hoststubgen-annotations", ], } @@ -838,4 +839,5 @@ build = [ "AconfigFlags.bp", "ProtoLibraries.bp", "TestProtoLibraries.bp", + "Ravenwood.bp", ] diff --git a/Android.mk b/Android.mk index d9e202c9305c..e2c1ed8e9ddb 100644 --- a/Android.mk +++ b/Android.mk @@ -69,9 +69,6 @@ $(SDK_METADATA): $(TARGET_OUT_COMMON_INTERMEDIATES)/PACKAGING/framework-doc-stub .PHONY: framework-doc-stubs framework-doc-stubs: $(SDK_METADATA) -# Run this for checkbuild -checkbuild: doc-comment-check-docs - # Include subdirectory makefiles # ============================================================ @@ -34,3 +34,6 @@ per-file TestProtoLibraries.bp = file:platform/tools/tradefederation:/OWNERS per-file ZYGOTE_OWNERS = file:/ZYGOTE_OWNERS per-file SQLITE_OWNERS = file:/SQLITE_OWNERS + +per-file *ravenwood* = file:ravenwood/OWNERS +per-file *Ravenwood* = file:ravenwood/OWNERS diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg index bded26a8748f..015487d20f8d 100644 --- a/PREUPLOAD.cfg +++ b/PREUPLOAD.cfg @@ -25,6 +25,6 @@ hidden_api_txt_checksorted_hook = ${REPO_ROOT}/tools/platform-compat/hiddenapi/c hidden_api_txt_exclude_hook = ${REPO_ROOT}/frameworks/base/tools/hiddenapi/exclude.sh ${PREUPLOAD_COMMIT} ${REPO_ROOT} -ktfmt_hook = ${REPO_ROOT}/external/ktfmt/ktfmt.py --check -i ${REPO_ROOT}/frameworks/base/packages/SystemUI/ktfmt_includes.txt ${PREUPLOAD_FILES} +ktfmt_hook = ${REPO_ROOT}/external/ktfmt/ktfmt.py --check -i ${REPO_ROOT}/frameworks/base/ktfmt_includes.txt ${PREUPLOAD_FILES} ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py --no-verify-format -f ${PREUPLOAD_FILES} diff --git a/Ravenwood.bp b/Ravenwood.bp new file mode 100644 index 000000000000..9218cc9bc3f8 --- /dev/null +++ b/Ravenwood.bp @@ -0,0 +1,70 @@ +// Copyright (C) 2023 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// We need this "trampoline" rule to force soong to give a host-side jar to +// framework-minus-apex.ravenwood. Otherwise, soong would mix up the arch (?) and we'd get +// a dex jar. +java_library { + name: "framework-minus-apex-for-hoststubgen", + installable: false, // host only jar. + static_libs: [ + "framework-minus-apex", + ], + sdk_version: "core_platform", + visibility: ["//visibility:private"], +} + +// Generate the stub/impl from framework-all, with hidden APIs. +java_genrule_host { + name: "framework-minus-apex.ravenwood-base", + tools: ["hoststubgen"], + cmd: "$(location hoststubgen) " + + "@$(location :ravenwood-standard-options) " + + + "--out-stub-jar $(location ravenwood_stub.jar) " + + "--out-impl-jar $(location ravenwood.jar) " + + + "--gen-keep-all-file $(location hoststubgen_keep_all.txt) " + + "--gen-input-dump-file $(location hoststubgen_dump.txt) " + + + "--in-jar $(location :framework-minus-apex-for-hoststubgen) " + + "--policy-override-file $(location framework-minus-apex-ravenwood-policies.txt) ", + srcs: [ + ":framework-minus-apex-for-hoststubgen", + "framework-minus-apex-ravenwood-policies.txt", + ":ravenwood-standard-options", + ], + out: [ + "ravenwood.jar", + "ravenwood_stub.jar", // It's not used. TODO: Update hoststubgen to make it optional. + + // Following files are created just as FYI. + "hoststubgen_keep_all.txt", + "hoststubgen_dump.txt", + ], + visibility: ["//visibility:private"], +} + +// Extract the impl jar from "framework-minus-apex.ravenwood-base" for subsequent build rules. +java_genrule_host { + name: "framework-minus-apex.ravenwood", + cmd: "cp $(in) $(out)", + srcs: [ + ":framework-minus-apex.ravenwood-base{ravenwood.jar}", + ], + out: [ + "framework-minus-apex.ravenwood.jar", + ], + visibility: ["//visibility:public"], +} 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/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java index 2b7438c862bd..fdeb072cacb3 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java @@ -536,10 +536,13 @@ public class PrefetchController extends StateController { static final String KEY_LAUNCH_TIME_ALLOWANCE_MS = PC_CONSTANT_PREFIX + "launch_time_allowance_ms"; - private static final long DEFAULT_LAUNCH_TIME_THRESHOLD_MS = 7 * HOUR_IN_MILLIS; - private static final long DEFAULT_LAUNCH_TIME_ALLOWANCE_MS = 20 * MINUTE_IN_MILLIS; + private static final long DEFAULT_LAUNCH_TIME_THRESHOLD_MS = HOUR_IN_MILLIS; + private static final long DEFAULT_LAUNCH_TIME_ALLOWANCE_MS = 30 * MINUTE_IN_MILLIS; - /** How much time each app will have to run jobs within their standby bucket window. */ + /** + * The earliest amount of time before the next estimated app launch time that we may choose + * to run a prefetch job for the app. + */ public long LAUNCH_TIME_THRESHOLD_MS = DEFAULT_LAUNCH_TIME_THRESHOLD_MS; /** diff --git a/api/ApiDocs.bp b/api/ApiDocs.bp index 8d8fc12ebc4d..30b442336148 100644 --- a/api/ApiDocs.bp +++ b/api/ApiDocs.bp @@ -20,41 +20,6 @@ // The API doc generation is done by the various droiddoc modules each of which // is for different format. -///////////////////////////////////////////////////////////////////// -// stub source files are generated using metalava -///////////////////////////////////////////////////////////////////// - -framework_docs_only_libs = [ - "voip-common", - "android.test.mock", - "android-support-annotations", - "android-support-compat", - "android-support-core-ui", - "android-support-core-utils", - "android-support-design", - "android-support-dynamic-animation", - "android-support-exifinterface", - "android-support-fragment", - "android-support-media-compat", - "android-support-percent", - "android-support-transition", - "android-support-v7-cardview", - "android-support-v7-gridlayout", - "android-support-v7-mediarouter", - "android-support-v7-palette", - "android-support-v7-preference", - "android-support-v13", - "android-support-v14-preference", - "android-support-v17-leanback", - "android-support-vectordrawable", - "android-support-animatedvectordrawable", - "android-support-v7-appcompat", - "android-support-v7-recyclerview", - "android-support-v8-renderscript", - "android-support-multidex", - "android-support-multidex-instrumentation", -] - // These defaults enable doc-stub generation, api lint database generation and sdk value generation. stubs_defaults { name: "android-non-updatable-doc-stubs-defaults", @@ -65,7 +30,6 @@ stubs_defaults { ":android-test-mock-sources", ":android-test-runner-sources", ], - libs: framework_docs_only_libs, create_doc_stubs: true, write_sdk_values: true, } @@ -197,7 +161,7 @@ doc_defaults { name: "framework-docs-default", sdk_version: "none", system_modules: "none", - libs: framework_docs_only_libs + [ + libs: [ "stub-annotations", "unsupportedappusage", ], @@ -236,20 +200,6 @@ doc_defaults { }, } -doc_defaults { - name: "framework-dokka-docs-default", -} - -droiddoc { - name: "doc-comment-check-docs", - defaults: ["framework-docs-default"], - srcs: [ - ":framework-doc-stubs", - ], - args: framework_docs_only_args + " -referenceonly -parsecomments", - installable: false, -} - droiddoc { name: "offline-sdk-docs", defaults: ["framework-docs-default"], @@ -303,70 +253,6 @@ droiddoc { } droiddoc { - name: "online-sdk-docs", - defaults: ["framework-docs-default"], - srcs: [ - ":framework-doc-stubs", - ], - hdf: [ - "android.whichdoc online", - "android.hasSamples true", - ], - proofread_file: "online-sdk-docs-proofread.txt", - args: framework_docs_only_args + - " -toroot / -samplegroup Admin " + - " -samplegroup Background " + - " -samplegroup Connectivity " + - " -samplegroup Content " + - " -samplegroup Input " + - " -samplegroup Media " + - " -samplegroup Notification " + - " -samplegroup RenderScript " + - " -samplegroup Security " + - " -samplegroup Sensors " + - " -samplegroup System " + - " -samplegroup Testing " + - " -samplegroup UI " + - " -samplegroup Views " + - " -samplegroup Wearable -samplesdir development/samples/browseable ", -} - -droiddoc { - name: "online-system-api-sdk-docs", - defaults: ["framework-docs-default"], - srcs: [ - ":framework-doc-system-stubs", - ], - hdf: [ - "android.whichdoc online", - "android.hasSamples true", - ], - proofread_file: "online-system-api-sdk-docs-proofread.txt", - args: framework_docs_only_args + - " -referenceonly " + - " -title \"Android SDK - Including system APIs.\" " + - " -hide 101 " + - " -hide 104 " + - " -hide 108 " + - " -toroot / -samplegroup Admin " + - " -samplegroup Background " + - " -samplegroup Connectivity " + - " -samplegroup Content " + - " -samplegroup Input " + - " -samplegroup Media " + - " -samplegroup Notification " + - " -samplegroup RenderScript " + - " -samplegroup Security " + - " -samplegroup Sensors " + - " -samplegroup System " + - " -samplegroup Testing " + - " -samplegroup UI " + - " -samplegroup Views " + - " -samplegroup Wearable -samplesdir development/samples/browseable ", - installable: false, -} - -droiddoc { name: "ds-docs-java", defaults: ["framework-docs-default"], srcs: [ @@ -397,7 +283,6 @@ droiddoc { droiddoc { name: "ds-docs-kt", - defaults: ["framework-dokka-docs-default"], srcs: [ ":framework-doc-stubs", ], @@ -476,44 +361,3 @@ droiddoc { " -atLinksNavtree " + " -navtreeonly ", } - -droiddoc { - name: "online-sdk-dev-docs", - defaults: ["framework-docs-default"], - srcs: [ - ":framework-doc-stubs", - ], - hdf: [ - "android.whichdoc online", - "android.hasSamples true", - ], - proofread_file: "online-sdk-dev-docs-proofread.txt", - args: framework_docs_only_args + - " -toroot / -samplegroup Admin " + - " -samplegroup Background " + - " -samplegroup Connectivity " + - " -samplegroup Content " + - " -samplegroup Input " + - " -samplegroup Media " + - " -samplegroup Notification " + - " -samplegroup RenderScript " + - " -samplegroup Security " + - " -samplegroup Sensors " + - " -samplegroup System " + - " -samplegroup Testing " + - " -samplegroup UI " + - " -samplegroup Views " + - " -samplegroup Wearable -samplesdir development/samples/browseable ", -} - -droiddoc { - name: "hidden-docs", - defaults: ["framework-docs-default"], - srcs: [ - ":framework-doc-stubs", - ], - proofread_file: "hidden-docs-proofread.txt", - args: framework_docs_only_args + - " -referenceonly " + - " -title \"Android SDK - Including hidden APIs.\"", -} diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp index 2d9c988556ec..fa4bc0f98fa5 100644 --- a/api/StubLibraries.bp +++ b/api/StubLibraries.bp @@ -438,6 +438,26 @@ java_api_library { full_api_surface_stub: "android_module_lib_stubs_current_full.from-text", } +// This module generates a stub jar that is a union of the test and module lib +// non-updatable api contributions. Modules should not depend on the stub jar +// generated from this module, as this module is strictly used for hiddenapi only. +java_api_library { + name: "android-non-updatable.stubs.test_module_lib", + api_surface: "module_lib", + api_contributions: [ + "api-stubs-docs-non-updatable.api.contribution", + "system-api-stubs-docs-non-updatable.api.contribution", + "test-api-stubs-docs-non-updatable.api.contribution", + "module-lib-api-stubs-docs-non-updatable.api.contribution", + ], + defaults: ["android-non-updatable_from_text_defaults"], + full_api_surface_stub: "android_test_module_lib_stubs_current.from-text", + + // This module is only used for hiddenapi, and other modules should not + // depend on this module. + visibility: ["//visibility:private"], +} + java_defaults { name: "android_stubs_dists_default", dist: { @@ -757,6 +777,30 @@ java_api_library { } java_api_library { + name: "android_test_module_lib_stubs_current.from-text", + api_surface: "module-lib", + defaults: [ + "android_stubs_current_contributions", + "android_system_stubs_current_contributions", + "android_test_stubs_current_contributions", + "android_module_lib_stubs_current_contributions", + ], + libs: [ + "android_module_lib_stubs_current_full.from-text", + "stub-annotations", + ], + api_contributions: [ + "test-api-stubs-docs-non-updatable.api.contribution", + ], + + // This module is only used to build android-non-updatable.stubs.test_module_lib + // and other modules should not depend on this module. + visibility: [ + "//visibility:private", + ], +} + +java_api_library { name: "android_system_server_stubs_current.from-text", api_surface: "system-server", api_contributions: [ diff --git a/api/javadoc-lint-baseline b/api/javadoc-lint-baseline index 2cc5078c8844..c28480e4f11a 100644 --- a/api/javadoc-lint-baseline +++ b/api/javadoc-lint-baseline @@ -86,35 +86,12 @@ android/app/appsearch/SearchSpec.java:929: lint: Unresolved link/see tag "Featur 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/companion/CompanionDeviceService.java:273: lint: Unresolved link/see tag "android.companion.AssociationInfo#isSelfManaged() self-managed" in android.companion.CompanionDeviceService [101] -android/companion/CompanionDeviceService.java:282: lint: Unresolved link/see tag "android.companion.AssociationInfo#isSelfManaged() self-managed" in android.companion.CompanionDeviceService [101] -android/companion/virtual/VirtualDevice.java:15: lint: Unresolved link/see tag "android.companion.virtual.VirtualDeviceManager.VirtualDevice VirtualDeviceManager.VirtualDevice" in android.companion.virtual.VirtualDevice [101] -android/companion/virtual/VirtualDevice.java:70: lint: Unresolved link/see tag "android.companion.virtual.VirtualDeviceParams.Builder#setName(String)" in android.companion.virtual.VirtualDevice [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/content/om/OverlayIdentifier.java:20: lint: Unresolved link/see tag "android.content.om.OverlayManagerTransaction.Builder#unregisterFabricatedOverlay(OverlayIdentifier)" in android.content.om.OverlayIdentifier [101] -android/content/om/OverlayInfo.java:78: lint: Unresolved link/see tag "android.content.om.OverlayManagerTransaction.Builder#unregisterFabricatedOverlay(OverlayIdentifier)" in android.content.om.OverlayInfo [101] -android/content/om/OverlayManager.java:9: lint: Unresolved link/see tag "android.content.om.OverlayManagerTransaction#commit()" in android.content.om.OverlayManager [101] -android/content/pm/PackageInstaller.java:2232: lint: Unresolved link/see tag "android.Manifest.permission#INSTALL_GRANT_RUNTIME_PERMISSIONS INSTALL_GRANT_RUNTIME_PERMISSIONS" in android.content.pm.PackageInstaller.SessionParams [101] -android/content/pm/ServiceInfo.java:176: lint: Unresolved link/see tag "android.app.job.JobInfo.Builder#setDataTransfer" in android.content.pm.ServiceInfo [101] -android/content/pm/verify/domain/DomainVerificationUserState.java:82: lint: Unresolved link/see tag "android.content.pm.verify.domain.DomainVerificationUserState.DomainState DomainState" in android.content.pm.verify.domain.DomainVerificationUserState [101] -android/content/res/Resources.java:958: lint: Unresolved link/see tag "android.annotation.UiContext" in android.content.res.Resources [101] -android/credentials/CreateCredentialException.java:22: lint: Unresolved link/see tag "android.credentials.CredentialManager#createCredential(android.credentials.CreateCredentialRequest,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver) CredentialManager#createCredential(CreateCredentialRequest, Activity, CancellationSignal, Executor, OutcomeReceiver)" in android.credentials.CreateCredentialException [101] -android/credentials/CreateCredentialException.java:101: lint: Unresolved link/see tag "android.credentials.CredentialManager#createCredential(android.credentials.CreateCredentialRequest,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver) CredentialManager#createCredential(CreateCredentialRequest, Activity, CancellationSignal, Executor, OutcomeReceiver)" in android.credentials.CreateCredentialException [101] -android/credentials/CreateCredentialRequest.java:107: lint: Unresolved link/see tag "androidx.credentials.CreateCredentialRequest" in android.credentials.CreateCredentialRequest.Builder [101] -android/credentials/CredentialDescription.java:89: lint: Unresolved link/see tag "android.credentials.CredentialDescription#mSupportedElementKeys CredentialDescription#mSupportedElementKeys" in android.credentials.CredentialDescription [101] -android/credentials/CredentialDescription.java:89: lint: Unresolved link/see tag "android.credentials.CredentialDescription#mType CredentialDescription#mType" in android.credentials.CredentialDescription [101] -android/credentials/CredentialDescription.java:101: lint: Unresolved link/see tag "android.credentials.CredentialDescription#mSupportedElementKeys CredentialDescription#mSupportedElementKeys" in android.credentials.CredentialDescription [101] -android/credentials/CredentialDescription.java:101: lint: Unresolved link/see tag "android.credentials.CredentialDescription#mType CredentialDescription#mType" in android.credentials.CredentialDescription [101] -android/credentials/GetCredentialException.java:22: lint: Unresolved link/see tag "android.credentials.CredentialManager#getCredential(android.credentials.GetCredentialRequest,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver) CredentialManager#getCredential(GetCredentialRequest, Activity, CancellationSignal, Executor, OutcomeReceiver)" in android.credentials.GetCredentialException [101] -android/credentials/GetCredentialException.java:103: lint: Unresolved link/see tag "android.credentials.CredentialManager#getCredential(android.credentials.GetCredentialRequest,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver)" in android.credentials.GetCredentialException [101] -android/credentials/PrepareGetCredentialResponse.java:20: lint: Unresolved link/see tag "android.credentials.CredentialManager#getCredential(android.credentials.PrepareGetCredentialResponse.PendingGetCredentialHandle,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver) CredentialManager#getCredential(PendingGetCredentialHandle, Activity, CancellationSignal, Executor, OutcomeReceiver)" in android.credentials.PrepareGetCredentialResponse [101] -android/credentials/PrepareGetCredentialResponse.java:68: lint: Unresolved link/see tag "android.credentials.CredentialManager#getCredential(android.credentials.PrepareGetCredentialResponse.PendingGetCredentialHandle,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver) CredentialManager#getCredential(PendingGetCredentialHandle, Activity, CancellationSignal, Executor, OutcomeReceiver)" in android.credentials.PrepareGetCredentialResponse [101] -android/credentials/PrepareGetCredentialResponse.java:83: lint: Unresolved link/see tag "android.credentials.CredentialManager#getCredential(android.credentials.PrepareGetCredentialResponse.PendingGetCredentialHandle,android.app.Activity,android.os.CancellationSignal,java.util.concurrent.Executor,android.os.OutcomeReceiver)" in android.credentials.PrepareGetCredentialResponse.PendingGetCredentialHandle [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] @@ -165,8 +142,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] @@ -212,17 +187,6 @@ android/provider/Settings.java:2195: lint: Unresolved link/see tag "android.app. 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/credentials/Action.java:3: lint: Unresolved link/see tag "androidx.credentials.provider" in android.service.credentials.Action [101] -android/service/credentials/Action.java:3: lint: Unresolved link/see tag "androidx.credentials.provider.Action" in android.service.credentials.Action [101] -android/service/credentials/BeginCreateCredentialResponse.java:85: lint: Unresolved link/see tag "Manifest.permission.PROVIDE_REMOTE_CREDENTIALS" in android.service.credentials.BeginCreateCredentialResponse.Builder [101] -android/service/credentials/BeginGetCredentialResponse.java:80: lint: Unresolved link/see tag "Manifest.permission.PROVIDE_REMOTE_CREDENTIALS" in android.service.credentials.BeginGetCredentialResponse.Builder [101] -android/service/credentials/CallingAppInfo.java:73: lint: Unresolved link/see tag "android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN" in android.service.credentials.CallingAppInfo [101] -android/service/credentials/CreateEntry.java:6: lint: Unresolved link/see tag "androidx.credentials.provider" in android.service.credentials.CreateEntry [101] -android/service/credentials/CreateEntry.java:6: lint: Unresolved link/see tag "androidx.credentials.provider.CreateEntry" in android.service.credentials.CreateEntry [101] -android/service/credentials/CredentialEntry.java:11: lint: Unresolved link/see tag "androidx.credentials.provider" in android.service.credentials.CredentialEntry [101] -android/service/credentials/CredentialEntry.java:11: lint: Unresolved link/see tag "androidx.credentials.provider.CredentialEntry" in android.service.credentials.CredentialEntry [101] -android/service/credentials/RemoteEntry.java:13: lint: Unresolved link/see tag "androidx.credentials.provider" in android.service.credentials.RemoteEntry [101] -android/service/credentials/RemoteEntry.java:13: lint: Unresolved link/see tag "androidx.credentials.provider.RemoteEntry" in android.service.credentials.RemoteEntry [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] @@ -231,37 +195,10 @@ android/service/quickaccesswallet/WalletCard.java:285: lint: Unresolved link/see 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/content/pm/ActivityInfo.java:1197: lint: Unresolved link/see tag "android.view.ViewRootImpl" in android.content.pm.ActivityInfo [101] android/os/UserManager.java:2384: lint: Unresolved link/see tag "android.annotation.UserHandleAware @UserHandleAware" in android.os.UserManager [101] android/os/UserManager.java:2384: lint: Unresolved link/see tag "android.annotation.UserHandleAware#enabledSinceTargetSdkVersion" in android.os.UserManager [101] android/service/voice/AlwaysOnHotwordDetector.java:269: lint: Unresolved link/see tag "#initialize( PersistableBundle, SharedMemory, SoundTrigger.ModuleProperties)" in android.service.voice.AlwaysOnHotwordDetector [101] @@ -269,21 +206,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/companion/virtual/VirtualDeviceImpl.java:134: lint: Unresolved link/see tag "DisplayManager" in android [101] -com/android/server/companion/virtual/VirtualDeviceImpl.java:134: lint: Unresolved link/see tag "VirtualDeviceManager.VirtualDevice" in android [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] @@ -292,12 +219,7 @@ com/android/server/pm/PackageInstallerSession.java:327: lint: Unresolved link/se 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/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java:-4: lint: Invalid tag: @Override [131] -android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java:-1: lint: Invalid tag: @Override [131] -android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java:2: lint: Invalid tag: @Override [131] 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/config/preloaded-classes b/config/preloaded-classes index aa34bad29b26..7f8f5e3f362b 100644 --- a/config/preloaded-classes +++ b/config/preloaded-classes @@ -6519,12 +6519,6 @@ android.security.Scrypt android.security.attestationverification.AttestationVerificationManager android.security.keymaster.ExportResult$1 android.security.keymaster.ExportResult -android.security.keymaster.IKeyAttestationApplicationIdProvider$Stub -android.security.keymaster.IKeyAttestationApplicationIdProvider -android.security.keymaster.KeyAttestationApplicationId$1 -android.security.keymaster.KeyAttestationApplicationId -android.security.keymaster.KeyAttestationPackageInfo$1 -android.security.keymaster.KeyAttestationPackageInfo android.security.keymaster.KeyCharacteristics$1 android.security.keymaster.KeyCharacteristics android.security.keymaster.KeymasterArgument$1 @@ -6549,7 +6543,13 @@ android.security.keystore.AttestationUtils android.security.keystore.BackendBusyException android.security.keystore.DelegatingX509Certificate android.security.keystore.DeviceIdAttestationException +android.security.keystore.IKeyAttestationApplicationIdProvider$Stub +android.security.keystore.IKeyAttestationApplicationIdProvider +android.security.keystore.KeyAttestationApplicationId$Stub +android.security.keystore.KeyAttestationApplicationId android.security.keystore.KeyAttestationException +android.security.keystore.KeyAttestationPackageInfo$Stub +android.security.keystore.KeyAttestationPackageInfo android.security.keystore.KeyExpiredException android.security.keystore.KeyGenParameterSpec$Builder android.security.keystore.KeyGenParameterSpec @@ -6572,6 +6572,8 @@ android.security.keystore.KeystoreResponse$1 android.security.keystore.KeystoreResponse android.security.keystore.ParcelableKeyGenParameterSpec$1 android.security.keystore.ParcelableKeyGenParameterSpec +android.security.keystore.Signature$Stub +android.security.keystore.Signature android.security.keystore.SecureKeyImportUnavailableException android.security.keystore.StrongBoxUnavailableException android.security.keystore.UserAuthArgs diff --git a/core/api/current.txt b/core/api/current.txt index 5a1561ad80da..36cdd7fb84bb 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -1,4 +1,6 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 package android { public final class Manifest { @@ -9683,7 +9685,7 @@ package android.companion.virtual { method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") @NonNull public int[] getDisplayIds(); method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") @Nullable public CharSequence getDisplayName(); method @Nullable public String getName(); - method @Nullable public String getPersistentDeviceId(); + method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") @Nullable public String getPersistentDeviceId(); method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") public boolean hasCustomSensorSupport(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.VirtualDevice> CREATOR; @@ -14350,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(); @@ -14368,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); @@ -14596,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; @@ -15669,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(); @@ -16088,10 +16090,12 @@ package android.graphics { method public String getFontFeatureSettings(); method public float getFontMetrics(android.graphics.Paint.FontMetrics); method public android.graphics.Paint.FontMetrics getFontMetrics(); + method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") public void getFontMetricsForLocale(@NonNull android.graphics.Paint.FontMetrics); method public void getFontMetricsInt(@NonNull CharSequence, @IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int, boolean, @NonNull android.graphics.Paint.FontMetricsInt); method public void getFontMetricsInt(@NonNull char[], @IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0) int, boolean, @NonNull android.graphics.Paint.FontMetricsInt); method public int getFontMetricsInt(android.graphics.Paint.FontMetricsInt); method public android.graphics.Paint.FontMetricsInt getFontMetricsInt(); + method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") public void getFontMetricsIntForLocale(@NonNull android.graphics.Paint.FontMetricsInt); method public float getFontSpacing(); method public String getFontVariationSettings(); method public int getHinting(); @@ -16305,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(); @@ -28565,6 +28569,8 @@ package android.nfc { method @Nullable public android.nfc.NfcAntennaInfo getNfcAntennaInfo(); method public boolean ignore(android.nfc.Tag, int, android.nfc.NfcAdapter.OnTagRemovedListener, android.os.Handler); method public boolean isEnabled(); + method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionEnabled(); + method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionSupported(); method public boolean isSecureNfcEnabled(); method public boolean isSecureNfcSupported(); field public static final String ACTION_ADAPTER_STATE_CHANGED = "android.nfc.action.ADAPTER_STATE_CHANGED"; @@ -31974,6 +31980,7 @@ package android.os { field public static final int BATTERY_PROPERTY_CURRENT_AVERAGE = 3; // 0x3 field public static final int BATTERY_PROPERTY_CURRENT_NOW = 2; // 0x2 field public static final int BATTERY_PROPERTY_ENERGY_COUNTER = 5; // 0x5 + field @FlaggedApi("android.os.state_of_health_public") public static final int BATTERY_PROPERTY_STATE_OF_HEALTH = 10; // 0xa field public static final int BATTERY_PROPERTY_STATUS = 6; // 0x6 field public static final int BATTERY_STATUS_CHARGING = 2; // 0x2 field public static final int BATTERY_STATUS_DISCHARGING = 3; // 0x3 @@ -41557,7 +41564,7 @@ package android.telecom { method public android.telecom.GatewayInfo getGatewayInfo(); method public android.net.Uri getHandle(); method public int getHandlePresentation(); - method @NonNull public String getId(); + method @FlaggedApi("com.android.server.telecom.flags.call_details_id_changes") @NonNull public String getId(); method public android.os.Bundle getIntentExtras(); method public final int getState(); method public android.telecom.StatusHints getStatusHints(); @@ -44216,7 +44223,7 @@ package android.telephony { field public static final int OUT_OF_NETWORK = 11; // 0xb field public static final int OUT_OF_SERVICE = 18; // 0x12 field public static final int POWER_OFF = 17; // 0x11 - field public static final int SATELLITE_ENABLED = 82; // 0x52 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_ENABLED = 82; // 0x52 field public static final int SERVER_ERROR = 12; // 0xc field public static final int SERVER_UNREACHABLE = 9; // 0x9 field public static final int TIMED_OUT = 13; // 0xd @@ -44325,7 +44332,7 @@ package android.telephony { method public boolean isNetworkRegistered(); method public boolean isNetworkRoaming(); method public boolean isNetworkSearching(); - method public boolean isNonTerrestrialNetwork(); + method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public boolean isNonTerrestrialNetwork(); method @Deprecated public boolean isRegistered(); method @Deprecated public boolean isRoaming(); method @Deprecated public boolean isSearching(); @@ -44341,7 +44348,7 @@ package android.telephony { field public static final int NR_STATE_RESTRICTED = 1; // 0x1 field public static final int SERVICE_TYPE_DATA = 2; // 0x2 field public static final int SERVICE_TYPE_EMERGENCY = 5; // 0x5 - field public static final int SERVICE_TYPE_MMS = 6; // 0x6 + field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int SERVICE_TYPE_MMS = 6; // 0x6 field public static final int SERVICE_TYPE_SMS = 3; // 0x3 field public static final int SERVICE_TYPE_UNKNOWN = 0; // 0x0 field public static final int SERVICE_TYPE_VIDEO = 4; // 0x4 @@ -44556,7 +44563,7 @@ package android.telephony { method public boolean getRoaming(); method public int getState(); method public boolean isSearching(); - method public boolean isUsingNonTerrestrialNetwork(); + method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public boolean isUsingNonTerrestrialNetwork(); method public void setIsManualSelection(boolean); method public void setOperatorName(String, String, String); method public void setRoaming(boolean); @@ -45342,7 +45349,7 @@ package android.telephony { field public static final int ERI_FLASH = 2; // 0x2 field public static final int ERI_OFF = 1; // 0x1 field public static final int ERI_ON = 0; // 0x0 - field public static final String EVENT_DISPLAY_SOS_MESSAGE = "android.telephony.event.DISPLAY_SOS_MESSAGE"; + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final String EVENT_DISPLAY_SOS_MESSAGE = "android.telephony.event.DISPLAY_SOS_MESSAGE"; field public static final String EXTRA_ACTIVE_SIM_SUPPORTED_COUNT = "android.telephony.extra.ACTIVE_SIM_SUPPORTED_COUNT"; field public static final String EXTRA_APN_PROTOCOL = "android.telephony.extra.APN_PROTOCOL"; field public static final String EXTRA_APN_TYPE = "android.telephony.extra.APN_TYPE"; @@ -53122,6 +53129,7 @@ package android.view { method @Nullable public abstract android.view.View getCurrentFocus(); method @NonNull public abstract android.view.View getDecorView(); method public static int getDefaultFeatures(android.content.Context); + method @FlaggedApi("com.android.graphics.hwui.flags.limited_hdr") public float getDesiredHdrHeadroom(); method public android.transition.Transition getEnterTransition(); method public android.transition.Transition getExitTransition(); method protected final int getFeatures(); @@ -53191,6 +53199,7 @@ package android.view { method public abstract void setDecorCaptionShade(int); method public void setDecorFitsSystemWindows(boolean); method protected void setDefaultWindowFormat(int); + method @FlaggedApi("com.android.graphics.hwui.flags.limited_hdr") public void setDesiredHdrHeadroom(@FloatRange(from=0.0f, to=10000.0) float); method public void setDimAmount(float); method public void setElevation(float); method public void setEnterTransition(android.transition.Transition); @@ -53540,6 +53549,7 @@ package android.view { method public int describeContents(); method public int getBlurBehindRadius(); method public int getColorMode(); + method @FlaggedApi("com.android.graphics.hwui.flags.limited_hdr") public float getDesiredHdrHeadroom(); method public int getFitInsetsSides(); method public int getFitInsetsTypes(); method public final CharSequence getTitle(); @@ -53549,6 +53559,7 @@ package android.view { method public void setBlurBehindRadius(@IntRange(from=0) int); method public void setCanPlayMoveAnimation(boolean); method public void setColorMode(int); + method @FlaggedApi("com.android.graphics.hwui.flags.limited_hdr") public void setDesiredHdrHeadroom(@FloatRange(from=0.0f, to=10000.0f) float); method public void setFitInsetsIgnoringVisibility(boolean); method public void setFitInsetsSides(int); method public void setFitInsetsTypes(int); @@ -54659,7 +54670,6 @@ package android.view.autofill { public final class AutofillManager { method public void cancel(); - method public void clearAutofillRequestCallback(); method public void commit(); method public void disableAutofillServices(); method @Nullable public android.content.ComponentName getAutofillServiceComponentName(); @@ -54686,7 +54696,6 @@ package android.view.autofill { method public void registerCallback(@Nullable android.view.autofill.AutofillManager.AutofillCallback); method public void requestAutofill(@NonNull android.view.View); method public void requestAutofill(@NonNull android.view.View, int, @NonNull android.graphics.Rect); - method @RequiresPermission(android.Manifest.permission.PROVIDE_OWN_AUTOFILL_SUGGESTIONS) public void setAutofillRequestCallback(@NonNull java.util.concurrent.Executor, @NonNull android.view.autofill.AutofillRequestCallback); method public void setUserData(@Nullable android.service.autofill.UserData); method public boolean showAutofillDialog(@NonNull android.view.View); method public boolean showAutofillDialog(@NonNull android.view.View, int); @@ -54707,10 +54716,6 @@ package android.view.autofill { field public static final int EVENT_INPUT_UNAVAILABLE = 3; // 0x3 } - public interface AutofillRequestCallback { - method public void onFillRequest(@Nullable android.view.inputmethod.InlineSuggestionsRequest, @NonNull android.os.CancellationSignal, @NonNull android.service.autofill.FillCallback); - } - public final class AutofillValue implements android.os.Parcelable { method public int describeContents(); method public static android.view.autofill.AutofillValue forDate(long); @@ -55162,12 +55167,10 @@ package android.view.inputmethod { ctor public InlineSuggestionsRequest.Builder(@NonNull java.util.List<android.widget.inline.InlinePresentationSpec>); method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder addInlinePresentationSpecs(@NonNull android.widget.inline.InlinePresentationSpec); method @NonNull public android.view.inputmethod.InlineSuggestionsRequest build(); - method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setClientSupported(boolean); method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setExtras(@NonNull android.os.Bundle); method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(@NonNull java.util.List<android.widget.inline.InlinePresentationSpec>); method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setInlineTooltipPresentationSpec(@NonNull android.widget.inline.InlinePresentationSpec); method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setMaxSuggestionCount(int); - method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setServiceSupported(boolean); method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setSupportedLocales(@NonNull android.os.LocaleList); } diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index 052d614fa5fc..500a12cacc3b 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -1,4 +1,6 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 package android { public static final class Manifest.permission { @@ -6,9 +8,7 @@ package android { field public static final String CONTROL_AUTOMOTIVE_GNSS = "android.permission.CONTROL_AUTOMOTIVE_GNSS"; field public static final String GET_INTENT_SENDER_INTENT = "android.permission.GET_INTENT_SENDER_INTENT"; field public static final String MAKE_UID_VISIBLE = "android.permission.MAKE_UID_VISIBLE"; - field public static final String MANAGE_REMOTE_AUTH = "android.permission.MANAGE_REMOTE_AUTH"; field public static final String USE_COMPANION_TRANSPORTS = "android.permission.USE_COMPANION_TRANSPORTS"; - field public static final String USE_REMOTE_AUTH = "android.permission.USE_REMOTE_AUTH"; } } diff --git a/core/api/module-lib-removed.txt b/core/api/module-lib-removed.txt index d802177e249b..14191ebcb080 100644 --- a/core/api/module-lib-removed.txt +++ b/core/api/module-lib-removed.txt @@ -1 +1,3 @@ // 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 5a4be65ef559..e2b4e4dfc6c4 100644 --- a/core/api/removed.txt +++ b/core/api/removed.txt @@ -1,4 +1,6 @@ // 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 a99eeb0c36d8..a1da9e02f23d 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -1,4 +1,6 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 package android { public static final class Manifest.permission { @@ -297,6 +299,8 @@ 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"; field public static final String RECOVERY = "android.permission.RECOVERY"; @@ -652,7 +656,6 @@ package android.app { field public static final String OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO = "android:receive_ambient_trigger_audio"; field public static final String OPSTR_RECEIVE_EMERGENCY_BROADCAST = "android:receive_emergency_broadcast"; field public static final String OPSTR_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO = "android:receive_explicit_user_interaction_audio"; - field public static final String OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO = "android:receive_sandbox_trigger_audio"; field public static final String OPSTR_REQUEST_DELETE_PACKAGES = "android:request_delete_packages"; field public static final String OPSTR_REQUEST_INSTALL_PACKAGES = "android:request_install_packages"; field public static final String OPSTR_RUN_ANY_IN_BACKGROUND = "android:run_any_in_background"; @@ -3209,7 +3212,7 @@ package android.companion.virtual { method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.input.VirtualTouchscreenConfig); method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int); method public int getDeviceId(); - method @Nullable public String getPersistentDeviceId(); + method @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") @Nullable public String getPersistentDeviceId(); method @NonNull public java.util.List<android.companion.virtual.sensor.VirtualSensor> getVirtualSensorList(); method public void launchPendingIntent(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer); method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void registerIntentInterceptor(@NonNull android.content.IntentFilter, @NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback); @@ -4529,7 +4532,7 @@ package android.hardware.display { method @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public void setBrightnessConfiguration(android.hardware.display.BrightnessConfiguration); method @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public void setBrightnessConfigurationForDisplay(@NonNull android.hardware.display.BrightnessConfiguration, @NonNull String); method @Deprecated @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_SATURATION) public void setSaturationLevel(float); - field public static final int VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT = 128; // 0x80 + field @FlaggedApi("android.companion.virtual.flags.vdm_public_apis") public static final int VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT = 128; // 0x80 field public static final int VIRTUAL_DISPLAY_FLAG_STEAL_TOP_FOCUS_DISABLED = 65536; // 0x10000 field public static final int VIRTUAL_DISPLAY_FLAG_TRUSTED = 1024; // 0x400 } @@ -9605,6 +9608,7 @@ package android.nfc { method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable(); method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable(boolean); method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enable(); + method @FlaggedApi("android.nfc.enable_nfc_reader_option") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableReaderOption(boolean); method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableSecureNfc(boolean); method @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public java.util.Map<java.lang.String,java.lang.Boolean> getTagIntentAppPreferenceForUser(int); method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOn(); @@ -9704,7 +9708,6 @@ package android.os { field @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_CHARGING_POLICY = 9; // 0x9 field @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_FIRST_USAGE_DATE = 8; // 0x8 field @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_MANUFACTURING_DATE = 7; // 0x7 - field @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_STATE_OF_HEALTH = 10; // 0xa field public static final int CHARGING_POLICY_ADAPTIVE_AC = 3; // 0x3 field public static final int CHARGING_POLICY_ADAPTIVE_AON = 2; // 0x2 field public static final int CHARGING_POLICY_ADAPTIVE_LONGLIFE = 4; // 0x4 @@ -9772,8 +9775,8 @@ package android.os { method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportBleScanResults(@NonNull android.os.WorkSource, int); method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportBleScanStarted(@NonNull android.os.WorkSource, boolean); method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportBleScanStopped(@NonNull android.os.WorkSource, boolean); - method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void reportBluetoothOff(int, int, @NonNull String); - method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void reportBluetoothOn(int, int, @NonNull String); + method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void reportBluetoothOff(int, int, @NonNull String); + method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void reportBluetoothOn(int, int, @NonNull String); method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportFullWifiLockAcquiredFromSource(@NonNull android.os.WorkSource); method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportFullWifiLockReleasedFromSource(@NonNull android.os.WorkSource); method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void reportMobileRadioPowerState(boolean, int); @@ -13228,7 +13231,7 @@ package android.telecom { method public void requestStreamingState(int); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.telecom.StreamingCall> CREATOR; - field public static final String EXTRA_CALL_ID = "android.telecom.extra.CALL_ID"; + field @FlaggedApi("com.android.server.telecom.flags.call_details_id_changes") public static final String EXTRA_CALL_ID = "android.telecom.extra.CALL_ID"; field public static final int STATE_DISCONNECTED = 3; // 0x3 field public static final int STATE_HOLDING = 2; // 0x2 field public static final int STATE_STREAMING = 1; // 0x1 @@ -13709,7 +13712,7 @@ package android.telephony { method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setCellIdentity(@Nullable android.telephony.CellIdentity); method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setDomain(int); method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setEmergencyOnly(boolean); - method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setIsNonTerrestrialNetwork(boolean); + method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull public android.telephony.NetworkRegistrationInfo.Builder setIsNonTerrestrialNetwork(boolean); method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setRegisteredPlmn(@Nullable String); method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setRegistrationState(int); method @NonNull public android.telephony.NetworkRegistrationInfo.Builder setRejectCause(int); @@ -16679,157 +16682,157 @@ package android.telephony.mbms.vendor { package android.telephony.satellite { - public final class AntennaDirection implements android.os.Parcelable { - method public int describeContents(); - method public float getX(); - method public float getY(); - method public float getZ(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.AntennaDirection> CREATOR; + @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class AntennaDirection 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 getX(); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public float getY(); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public float getZ(); + 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.AntennaDirection> CREATOR; } - public final class AntennaPosition implements android.os.Parcelable { - method public int describeContents(); - method @NonNull public android.telephony.satellite.AntennaDirection getAntennaDirection(); - method public int getSuggestedHoldPosition(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @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 AntennaPosition 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") @NonNull public android.telephony.satellite.AntennaDirection getAntennaDirection(); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int getSuggestedHoldPosition(); + 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.AntennaPosition> CREATOR; } - public final class PointingInfo implements android.os.Parcelable { - method public int describeContents(); - method public float getSatelliteAzimuthDegrees(); - method public float getSatelliteElevationDegrees(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.PointingInfo> CREATOR; + @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(); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public float getSatelliteElevationDegrees(); + 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.PointingInfo> CREATOR; } - public final class SatelliteCapabilities implements android.os.Parcelable { - method public int describeContents(); - method @NonNull public java.util.Map<java.lang.Integer,android.telephony.satellite.AntennaPosition> getAntennaPositionMap(); - method public int getMaxBytesPerOutgoingDatagram(); - method @NonNull public java.util.Set<java.lang.Integer> getSupportedRadioTechnologies(); - method public boolean isPointingRequired(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.SatelliteCapabilities> CREATOR; + @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class SatelliteCapabilities 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") @NonNull public java.util.Map<java.lang.Integer,android.telephony.satellite.AntennaPosition> getAntennaPositionMap(); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int getMaxBytesPerOutgoingDatagram(); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public java.util.Set<java.lang.Integer> getSupportedRadioTechnologies(); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public boolean isPointingRequired(); + 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.SatelliteCapabilities> CREATOR; } - public final class SatelliteDatagram implements android.os.Parcelable { - method public int describeContents(); - method @NonNull public byte[] getSatelliteDatagram(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.SatelliteDatagram> CREATOR; + @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class SatelliteDatagram 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") @NonNull public byte[] getSatelliteDatagram(); + 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.SatelliteDatagram> CREATOR; } - public interface SatelliteDatagramCallback { - method public void onSatelliteDatagramReceived(long, @NonNull android.telephony.satellite.SatelliteDatagram, int, @NonNull java.util.function.Consumer<java.lang.Void>); + @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public interface SatelliteDatagramCallback { + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSatelliteDatagramReceived(long, @NonNull android.telephony.satellite.SatelliteDatagram, int, @NonNull java.util.function.Consumer<java.lang.Void>); } - public final class SatelliteManager { + @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class SatelliteManager { method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void addSatelliteAttachRestrictionForCarrier(int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void deprovisionSatelliteService(@NonNull String, @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 deprovisionSatelliteService(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); 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 @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void pollPendingSatelliteDatagrams(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); - method @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 @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteDatagram(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteDatagramCallback); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteModemStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteStateCallback); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteProvisionStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteProvisionStateCallback); + 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 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); method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void removeSatelliteAttachRestrictionForCarrier(int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsDemoModeEnabled(@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 requestIsDemoModeEnabled(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteAttachEnabledForCarrier(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteCommunicationAllowedForCurrentLocation(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>); - method @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 @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 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") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteCommunicationAllowedForCurrentLocation(@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 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.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 @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 @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteEnabled(boolean, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestTimeForNextSatelliteVisibility(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.time.Duration,android.telephony.satellite.SatelliteManager.SatelliteException>); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void sendSatelliteDatagram(int, @NonNull android.telephony.satellite.SatelliteDatagram, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void setDeviceAlignedWithSatellite(boolean); - method @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 @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 @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteDatagram(@NonNull android.telephony.satellite.SatelliteDatagramCallback); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteModemStateChanged(@NonNull android.telephony.satellite.SatelliteStateCallback); - method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteProvisionStateChanged(@NonNull android.telephony.satellite.SatelliteProvisionStateCallback); - field public static final int DATAGRAM_TYPE_LOCATION_SHARING = 2; // 0x2 - field public static final int DATAGRAM_TYPE_SOS_MESSAGE = 1; // 0x1 - field public static final int DATAGRAM_TYPE_UNKNOWN = 0; // 0x0 - field public static final int DEVICE_HOLD_POSITION_LANDSCAPE_LEFT = 2; // 0x2 - field public static final int DEVICE_HOLD_POSITION_LANDSCAPE_RIGHT = 3; // 0x3 - field public static final int DEVICE_HOLD_POSITION_PORTRAIT = 1; // 0x1 - field public static final int DEVICE_HOLD_POSITION_UNKNOWN = 0; // 0x0 - field public static final int DISPLAY_MODE_CLOSED = 3; // 0x3 - field public static final int DISPLAY_MODE_FIXED = 1; // 0x1 - field public static final int DISPLAY_MODE_OPENED = 2; // 0x2 - field public static final int DISPLAY_MODE_UNKNOWN = 0; // 0x0 - field public static final int NT_RADIO_TECHNOLOGY_EMTC_NTN = 3; // 0x3 - field public static final int NT_RADIO_TECHNOLOGY_NB_IOT_NTN = 1; // 0x1 - field public static final int NT_RADIO_TECHNOLOGY_NR_NTN = 2; // 0x2 - field public static final int NT_RADIO_TECHNOLOGY_PROPRIETARY = 4; // 0x4 - field public static final int NT_RADIO_TECHNOLOGY_UNKNOWN = 0; // 0x0 + 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>); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestTimeForNextSatelliteVisibility(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.time.Duration,android.telephony.satellite.SatelliteManager.SatelliteException>); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void sendSatelliteDatagram(int, @NonNull android.telephony.satellite.SatelliteDatagram, 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 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 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); + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DATAGRAM_TYPE_LOCATION_SHARING = 2; // 0x2 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DATAGRAM_TYPE_SOS_MESSAGE = 1; // 0x1 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DATAGRAM_TYPE_UNKNOWN = 0; // 0x0 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DEVICE_HOLD_POSITION_LANDSCAPE_LEFT = 2; // 0x2 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DEVICE_HOLD_POSITION_LANDSCAPE_RIGHT = 3; // 0x3 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DEVICE_HOLD_POSITION_PORTRAIT = 1; // 0x1 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DEVICE_HOLD_POSITION_UNKNOWN = 0; // 0x0 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DISPLAY_MODE_CLOSED = 3; // 0x3 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DISPLAY_MODE_FIXED = 1; // 0x1 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DISPLAY_MODE_OPENED = 2; // 0x2 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DISPLAY_MODE_UNKNOWN = 0; // 0x0 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_EMTC_NTN = 3; // 0x3 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_NB_IOT_NTN = 1; // 0x1 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_NR_NTN = 2; // 0x2 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_PROPRIETARY = 4; // 0x4 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_UNKNOWN = 0; // 0x0 field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION = 1; // 0x1 - field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE = 0; // 0x0 - field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED = 7; // 0x7 - field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_NONE = 6; // 0x6 - field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS = 5; // 0x5 - field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING = 4; // 0x4 - field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING = 1; // 0x1 - field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED = 3; // 0x3 - field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS = 2; // 0x2 - field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_UNKNOWN = -1; // 0xffffffff + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE = 0; // 0x0 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED = 7; // 0x7 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_NONE = 6; // 0x6 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS = 5; // 0x5 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING = 4; // 0x4 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING = 1; // 0x1 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED = 3; // 0x3 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS = 2; // 0x2 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_UNKNOWN = -1; // 0xffffffff field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT = 8; // 0x8 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_CONNECTED = 7; // 0x7 - field public static final int SATELLITE_MODEM_STATE_DATAGRAM_RETRYING = 3; // 0x3 - field public static final int SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING = 2; // 0x2 - field public static final int SATELLITE_MODEM_STATE_IDLE = 0; // 0x0 - field public static final int SATELLITE_MODEM_STATE_LISTENING = 1; // 0x1 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_DATAGRAM_RETRYING = 3; // 0x3 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING = 2; // 0x2 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_IDLE = 0; // 0x0 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_LISTENING = 1; // 0x1 field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_NOT_CONNECTED = 6; // 0x6 - field public static final int SATELLITE_MODEM_STATE_OFF = 4; // 0x4 - field public static final int SATELLITE_MODEM_STATE_UNAVAILABLE = 5; // 0x5 - field public static final int SATELLITE_MODEM_STATE_UNKNOWN = -1; // 0xffffffff - field public static final int SATELLITE_RESULT_ACCESS_BARRED = 16; // 0x10 - field public static final int SATELLITE_RESULT_ERROR = 1; // 0x1 - field public static final int SATELLITE_RESULT_INVALID_ARGUMENTS = 8; // 0x8 - field public static final int SATELLITE_RESULT_INVALID_MODEM_STATE = 7; // 0x7 - field public static final int SATELLITE_RESULT_INVALID_TELEPHONY_STATE = 6; // 0x6 - field public static final int SATELLITE_RESULT_MODEM_BUSY = 22; // 0x16 - field public static final int SATELLITE_RESULT_MODEM_ERROR = 4; // 0x4 - field public static final int SATELLITE_RESULT_NETWORK_ERROR = 5; // 0x5 - field public static final int SATELLITE_RESULT_NETWORK_TIMEOUT = 17; // 0x11 - field public static final int SATELLITE_RESULT_NOT_AUTHORIZED = 19; // 0x13 - field public static final int SATELLITE_RESULT_NOT_REACHABLE = 18; // 0x12 - field public static final int SATELLITE_RESULT_NOT_SUPPORTED = 20; // 0x14 - field public static final int SATELLITE_RESULT_NO_RESOURCES = 12; // 0xc - field public static final int SATELLITE_RESULT_RADIO_NOT_AVAILABLE = 10; // 0xa - field public static final int SATELLITE_RESULT_REQUEST_ABORTED = 15; // 0xf - field public static final int SATELLITE_RESULT_REQUEST_FAILED = 9; // 0x9 - field public static final int SATELLITE_RESULT_REQUEST_IN_PROGRESS = 21; // 0x15 - field public static final int SATELLITE_RESULT_REQUEST_NOT_SUPPORTED = 11; // 0xb - field public static final int SATELLITE_RESULT_SERVER_ERROR = 2; // 0x2 - field public static final int SATELLITE_RESULT_SERVICE_ERROR = 3; // 0x3 - field public static final int SATELLITE_RESULT_SERVICE_NOT_PROVISIONED = 13; // 0xd - field public static final int SATELLITE_RESULT_SERVICE_PROVISION_IN_PROGRESS = 14; // 0xe - field public static final int SATELLITE_RESULT_SUCCESS = 0; // 0x0 - } - - public static class SatelliteManager.SatelliteException extends java.lang.Exception { - ctor public SatelliteManager.SatelliteException(int); - method public int getErrorCode(); - } - - public interface SatelliteProvisionStateCallback { - method public void onSatelliteProvisionStateChanged(boolean); - } - - public interface SatelliteStateCallback { - method public void onSatelliteModemStateChanged(int); - } - - public interface SatelliteTransmissionUpdateCallback { - method public void onReceiveDatagramStateChanged(int, int, int); - method public void onSatellitePositionChanged(@NonNull android.telephony.satellite.PointingInfo); - method public void onSendDatagramStateChanged(int, int, int); + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_OFF = 4; // 0x4 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_UNAVAILABLE = 5; // 0x5 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_UNKNOWN = -1; // 0xffffffff + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_ACCESS_BARRED = 16; // 0x10 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_ERROR = 1; // 0x1 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_INVALID_ARGUMENTS = 8; // 0x8 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_INVALID_MODEM_STATE = 7; // 0x7 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_INVALID_TELEPHONY_STATE = 6; // 0x6 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_MODEM_BUSY = 22; // 0x16 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_MODEM_ERROR = 4; // 0x4 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NETWORK_ERROR = 5; // 0x5 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NETWORK_TIMEOUT = 17; // 0x11 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NOT_AUTHORIZED = 19; // 0x13 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NOT_REACHABLE = 18; // 0x12 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NOT_SUPPORTED = 20; // 0x14 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_NO_RESOURCES = 12; // 0xc + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_RADIO_NOT_AVAILABLE = 10; // 0xa + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_REQUEST_ABORTED = 15; // 0xf + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_REQUEST_FAILED = 9; // 0x9 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_REQUEST_IN_PROGRESS = 21; // 0x15 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_REQUEST_NOT_SUPPORTED = 11; // 0xb + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_SERVER_ERROR = 2; // 0x2 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_SERVICE_ERROR = 3; // 0x3 + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_SERVICE_NOT_PROVISIONED = 13; // 0xd + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_SERVICE_PROVISION_IN_PROGRESS = 14; // 0xe + field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_SUCCESS = 0; // 0x0 + } + + @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static class SatelliteManager.SatelliteException extends java.lang.Exception { + ctor @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public SatelliteManager.SatelliteException(int); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int getErrorCode(); + } + + @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public interface SatelliteProvisionStateCallback { + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSatelliteProvisionStateChanged(boolean); + } + + @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public interface SatelliteStateCallback { + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSatelliteModemStateChanged(int); + } + + @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public interface SatelliteTransmissionUpdateCallback { + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onReceiveDatagramStateChanged(int, int, int); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSatellitePositionChanged(@NonNull android.telephony.satellite.PointingInfo); + method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSendDatagramStateChanged(int, int, int); } } diff --git a/core/api/system-removed.txt b/core/api/system-removed.txt index aa17df3471d7..1fa2718dc6d2 100644 --- a/core/api/system-removed.txt +++ b/core/api/system-removed.txt @@ -1,4 +1,6 @@ // 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 b87a6407a195..eeddeb21aa9d 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1,4 +1,6 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 package android { public static final class Manifest.permission { @@ -28,7 +30,6 @@ package android { field public static final String MANAGE_APP_OPS_MODES = "android.permission.MANAGE_APP_OPS_MODES"; field public static final String MANAGE_CRATES = "android.permission.MANAGE_CRATES"; field public static final String MANAGE_NOTIFICATION_LISTENERS = "android.permission.MANAGE_NOTIFICATION_LISTENERS"; - field public static final String MANAGE_REMOTE_AUTH = "android.permission.MANAGE_REMOTE_AUTH"; field public static final String MANAGE_ROLLBACKS = "android.permission.MANAGE_ROLLBACKS"; field public static final String MANAGE_TOAST_RATE_LIMITING = "android.permission.MANAGE_TOAST_RATE_LIMITING"; field public static final String MODIFY_HDR_CONVERSION_MODE = "android.permission.MODIFY_HDR_CONVERSION_MODE"; @@ -56,7 +57,6 @@ package android { field public static final String TEST_INPUT_METHOD = "android.permission.TEST_INPUT_METHOD"; field public static final String TEST_MANAGE_ROLLBACKS = "android.permission.TEST_MANAGE_ROLLBACKS"; field public static final String UPGRADE_RUNTIME_PERMISSIONS = "android.permission.UPGRADE_RUNTIME_PERMISSIONS"; - field public static final String USE_REMOTE_AUTH = "android.permission.USE_REMOTE_AUTH"; field public static final String WRITE_ALLOWLISTED_DEVICE_CONFIG = "android.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG"; field public static final String WRITE_DEVICE_CONFIG = "android.permission.WRITE_DEVICE_CONFIG"; field @Deprecated public static final String WRITE_MEDIA_STORAGE = "android.permission.WRITE_MEDIA_STORAGE"; @@ -840,7 +840,7 @@ package android.appwidget { package android.companion { - public static final class AssociationInfo.Builder { + @FlaggedApi("android.companion.new_association_builder") public static final class AssociationInfo.Builder { ctor public AssociationInfo.Builder(int, int, @NonNull String); ctor public AssociationInfo.Builder(@NonNull android.companion.AssociationInfo); method @NonNull public android.companion.AssociationInfo build(); @@ -1181,6 +1181,7 @@ package android.credentials { method @Nullable public CharSequence getLabel(@NonNull android.content.Context); method @Nullable public android.graphics.drawable.Drawable getServiceIcon(@NonNull android.content.Context); method @NonNull public android.content.pm.ServiceInfo getServiceInfo(); + method @FlaggedApi("android.credentials.flags.settings_activity_enabled") @Nullable public CharSequence getSettingsActivity(); method @Nullable public CharSequence getSettingsSubtitle(); method @NonNull public boolean hasCapability(@NonNull String); method public boolean isEnabled(); @@ -2957,10 +2958,6 @@ package android.service.notification { method @Deprecated public boolean isBound(); } - public class NotificationRankingUpdate implements android.os.Parcelable { - method public final boolean isFdNotNullAndClosed(); - } - } package android.service.quickaccesswallet { @@ -3190,7 +3187,7 @@ 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 public static final int HAL_SERVICE_SATELLITE = 8; // 0x8 + 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; @@ -3410,7 +3407,7 @@ package android.view { public final class Choreographer { method public static long getFrameDelay(); - method public long getFrameTimeNanos(); + method @FlaggedApi("android.view.flags.expected_presentation_time_api") public long getFrameTimeNanos(); method public void postCallback(int, Runnable, Object); method public void postCallbackDelayed(int, Runnable, Object, long); method public void removeCallbacks(int, Runnable, Object); @@ -3571,8 +3568,8 @@ package android.view { method public default void holdLock(android.os.IBinder, int); method public default boolean isGlobalKey(int); method public default boolean isTaskSnapshotSupported(); - method @RequiresPermission(android.Manifest.permission.ACCESS_SURFACE_FLINGER) public default boolean replaceContentOnDisplayWithMirror(int, @NonNull android.view.Window); - method @RequiresPermission(android.Manifest.permission.ACCESS_SURFACE_FLINGER) public default boolean replaceContentOnDisplayWithSc(int, @NonNull android.view.SurfaceControl); + method @FlaggedApi("REPLACE_CONTENT_WITH_MIRROR") @RequiresPermission(android.Manifest.permission.ACCESS_SURFACE_FLINGER) public default boolean replaceContentOnDisplayWithMirror(int, @NonNull android.view.Window); + method @FlaggedApi("REPLACE_CONTENT_WITH_MIRROR") @RequiresPermission(android.Manifest.permission.ACCESS_SURFACE_FLINGER) public default boolean replaceContentOnDisplayWithSc(int, @NonNull android.view.SurfaceControl); method public default void setDisplayImePolicy(int, int); method public default void setShouldShowSystemDecors(int, boolean); method public default void setShouldShowWithInsecureKeyguard(int, boolean); @@ -3629,7 +3626,7 @@ package android.view.accessibility { package android.view.animation { public class AnimationUtils { - method public static void lockAnimationClock(long, long); + method @FlaggedApi("android.view.flags.expected_presentation_time_api") public static void lockAnimationClock(long, long); method public static void unlockAnimationClock(); } diff --git a/core/api/test-removed.txt b/core/api/test-removed.txt index d802177e249b..14191ebcb080 100644 --- a/core/api/test-removed.txt +++ b/core/api/test-removed.txt @@ -1 +1,3 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 diff --git a/core/java/Android.bp b/core/java/Android.bp index c3f3d875b0e0..0293f66061c1 100644 --- a/core/java/Android.bp +++ b/core/java/Android.bp @@ -23,11 +23,6 @@ filegroup { visibility: ["//frameworks/base"], } -filegroup { - name: "IKeyAttestationApplicationIdProvider.aidl", - srcs: ["android/security/keymaster/IKeyAttestationApplicationIdProvider.aidl"], -} - aidl_library { name: "IDropBoxManagerService_aidl", srcs: [ diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 68589456dec3..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 {} @@ -2258,18 +2258,17 @@ public class AppOpsManager { * * @hide */ - @SystemApi public static final String OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO = "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; @@ -2382,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[]{ @@ -2815,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/NotificationChannel.java b/core/java/android/app/NotificationChannel.java index 7ee1332ea76a..15d692ab1673 100644 --- a/core/java/android/app/NotificationChannel.java +++ b/core/java/android/app/NotificationChannel.java @@ -272,6 +272,10 @@ public final class NotificationChannel implements Parcelable { private boolean mDemoted = false; private boolean mImportantConvo = false; private long mDeletedTime = DEFAULT_DELETION_TIME_MS; + /** Do not (de)serialize this value: it only affects logic in system_server and that logic + * is reset on each boot {@link NotificationAttentionHelper#buzzBeepBlinkLocked}. + */ + private long mLastNotificationUpdateTimeMs = 0; /** * Creates a notification channel. @@ -932,6 +936,23 @@ public final class NotificationChannel implements Parcelable { } /** + * Returns the time of the notification post or last update for this channel. + * @return time of post / last update + * @hide + */ + public long getLastNotificationUpdateTimeMs() { + return mLastNotificationUpdateTimeMs; + } + + /** + * Sets the time of the notification post or last update for this channel. + * @hide + */ + public void setLastNotificationUpdateTimeMs(long updateTimeMs) { + mLastNotificationUpdateTimeMs = updateTimeMs; + } + + /** * @hide */ public void populateFromXmlForRestore(XmlPullParser parser, boolean pkgInstalled, @@ -1408,7 +1429,8 @@ public final class NotificationChannel implements Parcelable { + ", mParent=" + mParentId + ", mConversationId=" + mConversationId + ", mDemoted=" + mDemoted - + ", mImportantConvo=" + mImportantConvo; + + ", mImportantConvo=" + mImportantConvo + + ", mLastNotificationUpdateTimeMs=" + mLastNotificationUpdateTimeMs; } /** @hide */ diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 79b68c1456c7..b8bea9d102e1 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -25,8 +25,12 @@ import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; +import android.annotation.UserHandleAware; import android.annotation.WorkerThread; import android.app.Notification.Builder; +import android.app.compat.CompatChanges; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledSince; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; @@ -1659,23 +1663,42 @@ public class NotificationManager { } /** + * For apps targeting {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above, the + * {@code setNotificationListenerAccessGranted} method will use the user contained within the + * context. + * For apps targeting an SDK version <em>below</em> this, the user of the calling process will + * be used (Process.myUserHandle()). + * + * @hide + */ + @ChangeId + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) + public static final long SET_LISTENER_ACCESS_GRANTED_IS_USER_AWARE = 302563478L; + + /** * Grants/revokes Notification Listener access to the given component for current user. * To grant access for a particular user, obtain this service by using the {@link Context} * provided by {@link Context#createPackageContextAsUser} * * @param listener Name of component to grant/revoke access - * @param granted Grant/revoke access - * @param userSet Whether the action was triggered explicitly by user + * @param granted Grant/revoke access + * @param userSet Whether the action was triggered explicitly by user * @hide */ @SystemApi @TestApi + @UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) @RequiresPermission(android.Manifest.permission.MANAGE_NOTIFICATION_LISTENERS) public void setNotificationListenerAccessGranted( @NonNull ComponentName listener, boolean granted, boolean userSet) { INotificationManager service = getService(); try { - service.setNotificationListenerAccessGranted(listener, granted, userSet); + if (CompatChanges.isChangeEnabled(SET_LISTENER_ACCESS_GRANTED_IS_USER_AWARE)) { + service.setNotificationListenerAccessGrantedForUser(listener, mContext.getUserId(), + granted, userSet); + } else { + service.setNotificationListenerAccessGranted(listener, granted, userSet); + } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/admin/DevicePolicyResources.java b/core/java/android/app/admin/DevicePolicyResources.java index 456c6af134cf..7c69d376817c 100644 --- a/core/java/android/app/admin/DevicePolicyResources.java +++ b/core/java/android/app/admin/DevicePolicyResources.java @@ -1172,6 +1172,12 @@ public final class DevicePolicyResources { public static final String WORK_CATEGORY_HEADER = PREFIX + "WORK_CATEGORY_HEADER"; /** + * Header for items under the private user + */ + public static final String PRIVATE_CATEGORY_HEADER = + PREFIX + "PRIVATE_CATEGORY_HEADER"; + + /** * Header for items under the personal user */ public static final String PERSONAL_CATEGORY_HEADER = @@ -1208,6 +1214,12 @@ public final class DevicePolicyResources { public static final String AUTO_SYNC_WORK_DATA = PREFIX + "AUTO_SYNC_WORK_DATA"; /** + * Text for toggle to enable auto-sycing private data + */ + public static final String AUTO_SYNC_PRIVATE_DATA = PREFIX + + "AUTO_SYNC_PRIVATE_DATA"; + + /** * Summary for "More security settings" section when a work profile is on the device. */ public static final String MORE_SECURITY_SETTINGS_WORK_PROFILE_SUMMARY = PREFIX diff --git a/core/java/android/app/servertransaction/WindowStateResizeItem.java b/core/java/android/app/servertransaction/WindowStateResizeItem.java new file mode 100644 index 000000000000..98281338872b --- /dev/null +++ b/core/java/android/app/servertransaction/WindowStateResizeItem.java @@ -0,0 +1,193 @@ +/* + * 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.app.servertransaction; + +import static android.view.Display.INVALID_DISPLAY; + +import static java.util.Objects.requireNonNull; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ClientTransactionHandler; +import android.os.Parcel; +import android.os.RemoteException; +import android.util.MergedConfiguration; +import android.view.IWindow; +import android.view.InsetsState; +import android.window.ClientWindowFrames; + +import java.util.Objects; + +/** + * Message to deliver window resize info. + * @hide + */ +public class WindowStateResizeItem extends ClientTransactionItem { + + private IWindow mWindow; + private ClientWindowFrames mFrames; + private boolean mReportDraw; + private MergedConfiguration mConfiguration; + private InsetsState mInsetsState; + private boolean mForceLayout; + private boolean mAlwaysConsumeSystemBars; + private int mDisplayId; + private int mSyncSeqId; + private boolean mDragResizing; + + @Override + public void execute(@NonNull ClientTransactionHandler client, + @NonNull PendingTransactionActions pendingActions) { + try { + mWindow.resized(mFrames, mReportDraw, mConfiguration, mInsetsState, mForceLayout, + mAlwaysConsumeSystemBars, mDisplayId, mSyncSeqId, mDragResizing); + } catch (RemoteException e) { + // Should be a local call. + throw new RuntimeException(e); + } + } + + // ObjectPoolItem implementation + + private WindowStateResizeItem() {} + + /** Obtains an instance initialized with provided params. */ + public static WindowStateResizeItem obtain(@NonNull IWindow window, + @NonNull ClientWindowFrames frames, boolean reportDraw, + @NonNull MergedConfiguration configuration, @NonNull InsetsState insetsState, + boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int syncSeqId, + boolean dragResizing) { + WindowStateResizeItem instance = + ObjectPool.obtain(WindowStateResizeItem.class); + if (instance == null) { + instance = new WindowStateResizeItem(); + } + instance.mWindow = requireNonNull(window); + instance.mFrames = requireNonNull(frames); + instance.mReportDraw = reportDraw; + instance.mConfiguration = requireNonNull(configuration); + instance.mInsetsState = requireNonNull(insetsState); + instance.mForceLayout = forceLayout; + instance.mAlwaysConsumeSystemBars = alwaysConsumeSystemBars; + instance.mDisplayId = displayId; + instance.mSyncSeqId = syncSeqId; + instance.mDragResizing = dragResizing; + + return instance; + } + + @Override + public void recycle() { + mWindow = null; + mFrames = null; + mReportDraw = false; + mConfiguration = null; + mInsetsState = null; + mForceLayout = false; + mAlwaysConsumeSystemBars = false; + mDisplayId = INVALID_DISPLAY; + mSyncSeqId = -1; + mDragResizing = false; + ObjectPool.recycle(this); + } + + // Parcelable implementation + + /** Writes to Parcel. */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeStrongBinder(mWindow.asBinder()); + dest.writeTypedObject(mFrames, flags); + dest.writeBoolean(mReportDraw); + dest.writeTypedObject(mConfiguration, flags); + dest.writeTypedObject(mInsetsState, flags); + dest.writeBoolean(mForceLayout); + dest.writeBoolean(mAlwaysConsumeSystemBars); + dest.writeInt(mDisplayId); + dest.writeInt(mSyncSeqId); + dest.writeBoolean(mDragResizing); + } + + /** Reads from Parcel. */ + private WindowStateResizeItem(@NonNull Parcel in) { + mWindow = IWindow.Stub.asInterface(in.readStrongBinder()); + mFrames = in.readTypedObject(ClientWindowFrames.CREATOR); + mReportDraw = in.readBoolean(); + mConfiguration = in.readTypedObject(MergedConfiguration.CREATOR); + mInsetsState = in.readTypedObject(InsetsState.CREATOR); + mForceLayout = in.readBoolean(); + mAlwaysConsumeSystemBars = in.readBoolean(); + mDisplayId = in.readInt(); + mSyncSeqId = in.readInt(); + mDragResizing = in.readBoolean(); + } + + public static final @NonNull Creator<WindowStateResizeItem> CREATOR = new Creator<>() { + public WindowStateResizeItem createFromParcel(@NonNull Parcel in) { + return new WindowStateResizeItem(in); + } + + public WindowStateResizeItem[] newArray(int size) { + return new WindowStateResizeItem[size]; + } + }; + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final WindowStateResizeItem other = (WindowStateResizeItem) o; + return Objects.equals(mWindow, other.mWindow) + && Objects.equals(mFrames, other.mFrames) + && mReportDraw == other.mReportDraw + && Objects.equals(mConfiguration, other.mConfiguration) + && Objects.equals(mInsetsState, other.mInsetsState) + && mForceLayout == other.mForceLayout + && mAlwaysConsumeSystemBars == other.mAlwaysConsumeSystemBars + && mDisplayId == other.mDisplayId + && mSyncSeqId == other.mSyncSeqId + && mDragResizing == other.mDragResizing; + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + Objects.hashCode(mWindow); + result = 31 * result + Objects.hashCode(mFrames); + result = 31 * result + (mReportDraw ? 1 : 0); + result = 31 * result + Objects.hashCode(mConfiguration); + result = 31 * result + Objects.hashCode(mInsetsState); + result = 31 * result + (mForceLayout ? 1 : 0); + result = 31 * result + (mAlwaysConsumeSystemBars ? 1 : 0); + result = 31 * result + mDisplayId; + result = 31 * result + mSyncSeqId; + result = 31 * result + (mDragResizing ? 1 : 0); + return result; + } + + @Override + public String toString() { + return "WindowStateResizeItem{window=" + mWindow + + ", reportDrawn=" + mReportDraw + + ", configuration=" + mConfiguration + + "}"; + } +} diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java index 083fa0041b26..6393c456bdcd 100644 --- a/core/java/android/companion/AssociationInfo.java +++ b/core/java/android/companion/AssociationInfo.java @@ -15,6 +15,7 @@ */ package android.companion; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; @@ -412,6 +413,7 @@ public final class AssociationInfo implements Parcelable { * * @hide */ + @FlaggedApi(Flags.FLAG_NEW_ASSOCIATION_BUILDER) @TestApi public static final class Builder { private final int mId; diff --git a/core/java/android/companion/CompanionDeviceService.java b/core/java/android/companion/CompanionDeviceService.java index 03e75e9fc483..570ecaa47b4e 100644 --- a/core/java/android/companion/CompanionDeviceService.java +++ b/core/java/android/companion/CompanionDeviceService.java @@ -161,16 +161,16 @@ public abstract class CompanionDeviceService extends Service { public static final int DEVICE_EVENT_BT_DISCONNECTED = 3; /** - * A companion app for a {@link AssociationInfo#isSelfManaged() self-managed} device will - * receive the callback {@link #onDeviceEvent(AssociationInfo, int)} if it reports that a - * device has appeared on its own. + * A companion app for a self-managed device will receive the callback + * {@link #onDeviceEvent(AssociationInfo, int)} if it reports that a device has appeared on its + * own. */ public static final int DEVICE_EVENT_SELF_MANAGED_APPEARED = 4; /** - * A companion app for a {@link AssociationInfo#isSelfManaged() self-managed} device will - * receive the callback {@link #onDeviceEvent(AssociationInfo, int)} if it reports that a - * device has disappeared on its own. + * A companion app for a self-managed device will receive the callback + * {@link #onDeviceEvent(AssociationInfo, int)} if it reports that a device has disappeared on + * its own. */ public static final int DEVICE_EVENT_SELF_MANAGED_DISAPPEARED = 5; diff --git a/core/java/android/companion/flags.aconfig b/core/java/android/companion/flags.aconfig new file mode 100644 index 000000000000..b9e5609171c3 --- /dev/null +++ b/core/java/android/companion/flags.aconfig @@ -0,0 +1,8 @@ +package: "android.companion" + +flag { + name: "new_association_builder" + namespace: "companion" + description: "Controls if the new Builder is exposed to test apis." + bug: "296251481" +}
\ No newline at end of file diff --git a/core/java/android/companion/virtual/VirtualDevice.java b/core/java/android/companion/virtual/VirtualDevice.java index ce883cddc952..93a3e7822888 100644 --- a/core/java/android/companion/virtual/VirtualDevice.java +++ b/core/java/android/companion/virtual/VirtualDevice.java @@ -33,9 +33,6 @@ import android.os.RemoteException; * * <p>Read-only device representation exposing the properties of an existing virtual device. * - * <p class="note">Not to be confused with {@link VirtualDeviceManager.VirtualDevice}, which is used - * by the virtual device creator and allows them to manage the device. - * * @see VirtualDeviceManager#registerVirtualDeviceListener */ public final class VirtualDevice implements Parcelable { @@ -113,14 +110,13 @@ public final class VirtualDevice implements Parcelable { * <p class="note">This identifier may not be unique across virtual devices, in case there are * more than one virtual devices corresponding to the same physical device. */ + @FlaggedApi(Flags.FLAG_VDM_PUBLIC_APIS) public @Nullable String getPersistentDeviceId() { return mPersistentId; } /** * Returns the name of the virtual device (optionally) provided during its creation. - * - * @see VirtualDeviceParams.Builder#setName(String) */ public @Nullable String getName() { return mName; diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java index 39800f73058b..25693660e1ab 100644 --- a/core/java/android/companion/virtual/VirtualDeviceManager.java +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -476,6 +476,7 @@ public final class VirtualDeviceManager { /** * Returns the persistent ID of this virtual device. */ + @FlaggedApi(Flags.FLAG_VDM_PUBLIC_APIS) public @Nullable String getPersistentDeviceId() { return mVirtualDeviceInternal.getPersistentDeviceId(); } diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java b/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java index bf78dd09e7c2..b9451a74f9e9 100644 --- a/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java +++ b/core/java/android/companion/virtual/sensor/VirtualSensorDirectChannelWriter.java @@ -46,15 +46,15 @@ import java.util.concurrent.atomic.AtomicLong; * <pre> * VirtualSensorDirectChannelWriter writer = new VirtualSensorDirectChannelWriter(); * VirtualSensorDirectChannelCallback callback = new VirtualSensorDirectChannelCallback() { - * @Override + * {@literal @}Override * public void onDirectChannelCreated(int channelHandle, SharedMemory sharedMemory) { * writer.addChannel(channelHandle, sharedMemory); * } - * @Override + * {@literal @}Override * public void onDirectChannelDestroyed(int channelHandle); * writer.removeChannel(channelHandle); * } - * @Override + * {@literal @}Override * public void onDirectChannelConfigured(int channelHandle, VirtualSensor sensor, int rateLevel, * int reportToken) * if (!writer.configureChannel(channelHandle, sensor, rateLevel, reportToken)) { diff --git a/core/java/android/content/ContentCaptureOptions.java b/core/java/android/content/ContentCaptureOptions.java index 36e0529e3566..3fbcd704308b 100644 --- a/core/java/android/content/ContentCaptureOptions.java +++ b/core/java/android/content/ContentCaptureOptions.java @@ -30,6 +30,11 @@ import android.view.contentcapture.ContentCaptureManager.ContentCaptureClient; import com.android.internal.annotations.VisibleForTesting; import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; /** * Content capture options for a given package. @@ -119,7 +124,10 @@ public final class ContentCaptureOptions implements Parcelable { /* enableReceiver= */ false, new ContentProtectionOptions( /* enableReceiver= */ false, - /* bufferSize= */ 0), + /* bufferSize= */ 0, + /* requiredGroups= */ Collections.emptyList(), + /* optionalGroups= */ Collections.emptyList(), + /* optionalGroupsThreshold= */ 0), /* whitelistedComponents= */ null); } @@ -141,9 +149,7 @@ public final class ContentCaptureOptions implements Parcelable { logHistorySize, ContentCaptureManager.DEFAULT_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING, ContentCaptureManager.DEFAULT_ENABLE_CONTENT_CAPTURE_RECEIVER, - new ContentProtectionOptions( - ContentCaptureManager.DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER, - ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE), + new ContentProtectionOptions(), whitelistedComponents); } @@ -183,9 +189,7 @@ public final class ContentCaptureOptions implements Parcelable { ContentCaptureManager.DEFAULT_LOG_HISTORY_SIZE, ContentCaptureManager.DEFAULT_DISABLE_FLUSH_FOR_VIEW_TREE_APPEARING, ContentCaptureManager.DEFAULT_ENABLE_CONTENT_CAPTURE_RECEIVER, - new ContentProtectionOptions( - ContentCaptureManager.DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER, - ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE), + new ContentProtectionOptions(), whitelistedComponents); } @@ -386,9 +390,58 @@ public final class ContentCaptureOptions implements Parcelable { */ public final int bufferSize; - public ContentProtectionOptions(boolean enableReceiver, int bufferSize) { + /** + * The list of required groups of strings to match. + * + * @hide + */ + @NonNull public final List<List<String>> requiredGroups; + + /** + * The list of optional groups of strings to match. + * + * @hide + */ + @NonNull public final List<List<String>> optionalGroups; + + /** + * The minimal number of optional groups that have to be matched. This is the threshold + * value and comparison is done with greater than or equals. + * + * @hide + */ + public final int optionalGroupsThreshold; + + /** + * Empty constructor with default values. + * + * @hide + */ + public ContentProtectionOptions() { + this( + ContentCaptureManager.DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER, + ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE, + ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_REQUIRED_GROUPS, + ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS, + ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD); + } + + /** + * Full primary constructor. + * + * @hide + */ + public ContentProtectionOptions( + boolean enableReceiver, + int bufferSize, + @NonNull List<List<String>> requiredGroups, + @NonNull List<List<String>> optionalGroups, + int optionalGroupsThreshold) { this.enableReceiver = enableReceiver; this.bufferSize = bufferSize; + this.requiredGroups = requiredGroups; + this.optionalGroups = optionalGroups; + this.optionalGroupsThreshold = optionalGroupsThreshold; } @Override @@ -398,7 +451,14 @@ public final class ContentCaptureOptions implements Parcelable { .append("enableReceiver=") .append(enableReceiver) .append(", bufferSize=") - .append(bufferSize); + .append(bufferSize) + .append(", requiredGroupsSize=") + .append(requiredGroups.size()) + .append(", optionalGroupsSize=") + .append(optionalGroups.size()) + .append(", optionalGroupsThreshold=") + .append(optionalGroupsThreshold); + return stringBuilder.append(']').toString(); } @@ -407,17 +467,50 @@ public final class ContentCaptureOptions implements Parcelable { pw.print(enableReceiver); pw.print(", bufferSize="); pw.print(bufferSize); + pw.print(", requiredGroupsSize="); + pw.print(requiredGroups.size()); + pw.print(", optionalGroupsSize="); + pw.print(optionalGroups.size()); + pw.print(", optionalGroupsThreshold="); + pw.print(optionalGroupsThreshold); } - private void writeToParcel(Parcel parcel) { + private void writeToParcel(@NonNull Parcel parcel) { parcel.writeBoolean(enableReceiver); parcel.writeInt(bufferSize); + writeGroupsToParcel(requiredGroups, parcel); + writeGroupsToParcel(optionalGroups, parcel); + parcel.writeInt(optionalGroupsThreshold); } - private static ContentProtectionOptions createFromParcel(Parcel parcel) { + @NonNull + private static ContentProtectionOptions createFromParcel(@NonNull Parcel parcel) { boolean enableReceiver = parcel.readBoolean(); int bufferSize = parcel.readInt(); - return new ContentProtectionOptions(enableReceiver, bufferSize); + List<List<String>> requiredGroups = createGroupsFromParcel(parcel); + List<List<String>> optionalGroups = createGroupsFromParcel(parcel); + int optionalGroupsThreshold = parcel.readInt(); + return new ContentProtectionOptions( + enableReceiver, + bufferSize, + requiredGroups, + optionalGroups, + optionalGroupsThreshold); + } + + private static void writeGroupsToParcel( + @NonNull List<List<String>> groups, @NonNull Parcel parcel) { + parcel.writeInt(groups.size()); + groups.forEach(parcel::writeStringList); + } + + @NonNull + private static List<List<String>> createGroupsFromParcel(@NonNull Parcel parcel) { + int size = parcel.readInt(); + return IntStream.range(0, size) + .mapToObj(i -> new ArrayList<String>()) + .peek(parcel::readStringList) + .collect(Collectors.toUnmodifiableList()); } } } diff --git a/core/java/android/content/om/OverlayIdentifier.java b/core/java/android/content/om/OverlayIdentifier.java index f256372964f2..30875aa6a100 100644 --- a/core/java/android/content/om/OverlayIdentifier.java +++ b/core/java/android/content/om/OverlayIdentifier.java @@ -39,7 +39,6 @@ import java.util.Objects; * --> * * @see OverlayInfo#getOverlayIdentifier() - * @see OverlayManagerTransaction.Builder#unregisterFabricatedOverlay(OverlayIdentifier) */ @DataClass(genConstructor = false, genBuilder = false, genHiddenBuilder = false, genEqualsHashCode = true, genToString = false) diff --git a/core/java/android/content/om/OverlayInfo.java b/core/java/android/content/om/OverlayInfo.java index ff1c08801dd7..2e898562655b 100644 --- a/core/java/android/content/om/OverlayInfo.java +++ b/core/java/android/content/om/OverlayInfo.java @@ -385,7 +385,6 @@ public final class OverlayInfo implements CriticalOverlayInfo, Parcelable { * <p>The return value of this function can be used to unregister the related overlay. * * @return an identifier representing the current overlay. - * @see OverlayManagerTransaction.Builder#unregisterFabricatedOverlay(OverlayIdentifier) */ @Override @NonNull diff --git a/core/java/android/content/om/OverlayManager.java b/core/java/android/content/om/OverlayManager.java index 0fcc72a1f688..ed965b3d1777 100644 --- a/core/java/android/content/om/OverlayManager.java +++ b/core/java/android/content/om/OverlayManager.java @@ -50,7 +50,7 @@ import java.util.List; * <li>register overlays * <li>unregister overlays * <li>execute multiple operations in one commitment by calling {@link - * OverlayManagerTransaction#commit()} + * #commit(OverlayManagerTransaction)} * </ul> * * @see OverlayManagerTransaction diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 036a4eb22ba6..aefa55f30826 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -1185,8 +1185,8 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { * <p>For {@link android.view.View#getWindowVisibleDisplayFrame} and * {@link android.view.View}#getWindowDisplayFrame this sandboxing is happening indirectly * through - * {@link android.view.ViewRootImpl}#getWindowVisibleDisplayFrame, - * {@link android.view.ViewRootImpl}#getDisplayFrame respectively. + * {@code android.view.ViewRootImpl#getWindowVisibleDisplayFrame}, + * {@code android.view.ViewRootImpl#getDisplayFrame} respectively. * * <p>Some applications assume that they occupy the whole screen and therefore use the display * coordinates in their calculations as if an activity is positioned in the top-left corner of diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 673a8a5edcba..d837aae35096 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -371,6 +371,13 @@ public class PackageInstaller { "android.content.pm.extra.UNARCHIVE_ALL_USERS"; /** + * A list of warnings that occurred during installation. + * + * @hide + */ + public static final String EXTRA_WARNINGS = "android.content.pm.extra.WARNINGS"; + + /** * Streaming installation pending. * Caller should make sure DataLoader is able to prepare image and reinitiate the operation. * @@ -2723,8 +2730,8 @@ public class PackageInstaller { * Sets the state of permissions for the package at installation. * <p/> * Granting any runtime permissions require the - * {@link android.Manifest.permission#INSTALL_GRANT_RUNTIME_PERMISSIONS - * INSTALL_GRANT_RUNTIME_PERMISSIONS} permission to be held by the caller. Revoking runtime + * {@code android.Manifest.permission#INSTALL_GRANT_RUNTIME_PERMISSIONS} + * permission to be held by the caller. Revoking runtime * permissions is not allowed, even during app update sessions. * <p/> * Holders without the permission are allowed to change the following special permissions: diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 45338bb2c0a2..4d5d05611d7a 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -4605,6 +4605,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/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java index 65f56f68ed3f..9869179d9686 100644 --- a/core/java/android/content/pm/ServiceInfo.java +++ b/core/java/android/content/pm/ServiceInfo.java @@ -132,11 +132,6 @@ public class ServiceInfo extends ComponentInfo * the {@link android.R.attr#foregroundServiceType} attribute. * Data(photo, file, account) upload/download, backup/restore, import/export, fetch, * transfer over network between device and cloud. - * - * <p class="note"> - * Use the {@link android.app.job.JobInfo.Builder#setDataTransfer} API for data transfers - * that can be deferred until conditions are ideal for the app or device. - * </p> */ @RequiresPermission( value = Manifest.permission.FOREGROUND_SERVICE_DATA_SYNC, diff --git a/core/java/android/content/pm/Signature.aidl b/core/java/android/content/pm/Signature.aidl deleted file mode 100644 index 36c127ad0384..000000000000 --- a/core/java/android/content/pm/Signature.aidl +++ /dev/null @@ -1,32 +0,0 @@ -/* //device/java/android/android/view/WindowManager.aidl -** -** Copyright 2007, The Android Open Source Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -*/ - -package android.content.pm; - -/* For the key attestation application id provider service we needed a native implementation - * of the Signature parcelable because the service is used by the native keystore. - * The native implementation is now located at - * system/security/keystore/Signature.cpp - * and - * system/security/keystore/include/keystore/Signature.h. - * and can be used by linking against libkeystore_binder. - * - * This is not the best arrangement. If you, dear reader, happen to implement native implementations - * for the package manager's parcelables, consider moving Signature.cpp/.h to your library and - * adjust keystore's dependencies accordingly. Thank you. - */ -parcelable Signature cpp_header "keystore/Signature.h"; diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java index 96af2b6caf9f..884d463e929d 100644 --- a/core/java/android/content/pm/UserProperties.java +++ b/core/java/android/content/pm/UserProperties.java @@ -49,6 +49,7 @@ public final class UserProperties implements Parcelable { private static final String ATTR_SHOW_IN_LAUNCHER = "showInLauncher"; private static final String ATTR_START_WITH_PARENT = "startWithParent"; private static final String ATTR_SHOW_IN_SETTINGS = "showInSettings"; + private static final String ATTR_HIDE_IN_SETTINGS_IN_QUIET_MODE = "hideInSettingsInQuietMode"; private static final String ATTR_INHERIT_DEVICE_POLICY = "inheritDevicePolicy"; private static final String ATTR_USE_PARENTS_CONTACTS = "useParentsContacts"; private static final String ATTR_UPDATE_CROSS_PROFILE_INTENT_FILTERS_ON_OTA = @@ -78,6 +79,7 @@ public final class UserProperties implements Parcelable { INDEX_CREDENTIAL_SHAREABLE_WITH_PARENT, INDEX_DELETE_APP_WITH_PARENT, INDEX_ALWAYS_VISIBLE, + INDEX_HIDE_IN_SETTINGS_IN_QUIET_MODE, }) @Retention(RetentionPolicy.SOURCE) private @interface PropertyIndex { @@ -94,6 +96,7 @@ public final class UserProperties implements Parcelable { private static final int INDEX_CREDENTIAL_SHAREABLE_WITH_PARENT = 9; private static final int INDEX_DELETE_APP_WITH_PARENT = 10; private static final int INDEX_ALWAYS_VISIBLE = 11; + private static final int INDEX_HIDE_IN_SETTINGS_IN_QUIET_MODE = 12; /** A bit set, mapping each PropertyIndex to whether it is present (1) or absent (0). */ private long mPropertiesPresent = 0; @@ -324,6 +327,7 @@ public final class UserProperties implements Parcelable { if (hasManagePermission) { // Add items that require MANAGE_USERS or stronger. setShowInSettings(orig.getShowInSettings()); + setHideInSettingsInQuietMode(orig.getHideInSettingsInQuietMode()); setUseParentsContacts(orig.getUseParentsContacts()); } if (hasQueryOrManagePermission) { @@ -409,6 +413,42 @@ public final class UserProperties implements Parcelable { private @ShowInSettings int mShowInSettings; /** + * Returns whether a user should be shown in the Settings app depending on the quiet mode. + * This is generally inapplicable for non-profile users. + * + * <p> {@link #getShowInSettings()} returns whether / how a user should be shown in Settings. + * However, if this behaviour should be changed based on the quiet mode of the user, then this + * property can be used. If the property is not set then the user is shown in the Settings app + * irrespective of whether the user is in quiet mode or not. If the property is set, then the + * user is shown in the Settings app only if the user is not in the quiet mode. Please note that + * this property takes effect only if {@link #getShowInSettings()} does not return + * {@link #SHOW_IN_SETTINGS_NO}. + * + * <p> The caller must have {@link android.Manifest.permission#MANAGE_USERS} to query this + * property. + * + * @return true if a profile should be shown in the Settings only when the user is not in the + * quiet mode. + * + * See also {@link #getShowInSettings()}, {@link #setShowInSettings(int)}, + * {@link ShowInSettings} + * + * @hide + */ + public boolean getHideInSettingsInQuietMode() { + if (isPresent(INDEX_HIDE_IN_SETTINGS_IN_QUIET_MODE)) return mHideInSettingsInQuietMode; + if (mDefaultProperties != null) return mDefaultProperties.mHideInSettingsInQuietMode; + throw new SecurityException( + "You don't have permission to query HideInSettingsInQuietMode"); + } + /** @hide */ + public void setHideInSettingsInQuietMode(boolean hideInSettingsInQuietMode) { + this.mHideInSettingsInQuietMode = hideInSettingsInQuietMode; + setPresent(INDEX_HIDE_IN_SETTINGS_IN_QUIET_MODE); + } + private boolean mHideInSettingsInQuietMode; + + /** * Returns whether a profile should be started when its parent starts (unless in quiet mode). * This only applies for users that have parents (i.e. for profiles). * @hide @@ -724,6 +764,9 @@ public final class UserProperties implements Parcelable { case ATTR_SHOW_IN_SETTINGS: setShowInSettings(parser.getAttributeInt(i)); break; + case ATTR_HIDE_IN_SETTINGS_IN_QUIET_MODE: + setHideInSettingsInQuietMode(parser.getAttributeBoolean(i)); + break; case ATTR_INHERIT_DEVICE_POLICY: setInheritDevicePolicy(parser.getAttributeInt(i)); break; @@ -777,6 +820,10 @@ public final class UserProperties implements Parcelable { if (isPresent(INDEX_SHOW_IN_SETTINGS)) { serializer.attributeInt(null, ATTR_SHOW_IN_SETTINGS, mShowInSettings); } + if (isPresent(INDEX_HIDE_IN_SETTINGS_IN_QUIET_MODE)) { + serializer.attributeBoolean(null, ATTR_HIDE_IN_SETTINGS_IN_QUIET_MODE, + mHideInSettingsInQuietMode); + } if (isPresent(INDEX_INHERIT_DEVICE_POLICY)) { serializer.attributeInt(null, ATTR_INHERIT_DEVICE_POLICY, mInheritDevicePolicy); @@ -823,6 +870,7 @@ public final class UserProperties implements Parcelable { dest.writeInt(mShowInLauncher); dest.writeBoolean(mStartWithParent); dest.writeInt(mShowInSettings); + dest.writeBoolean(mHideInSettingsInQuietMode); dest.writeInt(mInheritDevicePolicy); dest.writeBoolean(mUseParentsContacts); dest.writeBoolean(mUpdateCrossProfileIntentFiltersOnOTA); @@ -845,6 +893,7 @@ public final class UserProperties implements Parcelable { mShowInLauncher = source.readInt(); mStartWithParent = source.readBoolean(); mShowInSettings = source.readInt(); + mHideInSettingsInQuietMode = source.readBoolean(); mInheritDevicePolicy = source.readInt(); mUseParentsContacts = source.readBoolean(); mUpdateCrossProfileIntentFiltersOnOTA = source.readBoolean(); @@ -881,6 +930,7 @@ public final class UserProperties implements Parcelable { private @ShowInLauncher int mShowInLauncher = SHOW_IN_LAUNCHER_WITH_PARENT; private boolean mStartWithParent = false; private @ShowInSettings int mShowInSettings = SHOW_IN_SETTINGS_WITH_PARENT; + private boolean mHideInSettingsInQuietMode = false; private @InheritDevicePolicy int mInheritDevicePolicy = INHERIT_DEVICE_POLICY_NO; private boolean mUseParentsContacts = false; private boolean mUpdateCrossProfileIntentFiltersOnOTA = false; @@ -910,6 +960,12 @@ public final class UserProperties implements Parcelable { return this; } + /** Sets the value for {@link #mHideInSettingsInQuietMode} */ + public Builder setHideInSettingsInQuietMode(boolean hideInSettingsInQuietMode) { + mHideInSettingsInQuietMode = hideInSettingsInQuietMode; + return this; + } + /** Sets the value for {@link #mInheritDevicePolicy}*/ public Builder setInheritDevicePolicy( @InheritDevicePolicy int inheritRestrictionsDevicePolicy) { @@ -972,6 +1028,7 @@ public final class UserProperties implements Parcelable { mShowInLauncher, mStartWithParent, mShowInSettings, + mHideInSettingsInQuietMode, mInheritDevicePolicy, mUseParentsContacts, mUpdateCrossProfileIntentFiltersOnOTA, @@ -989,6 +1046,7 @@ public final class UserProperties implements Parcelable { @ShowInLauncher int showInLauncher, boolean startWithParent, @ShowInSettings int showInSettings, + boolean hideInSettingsInQuietMode, @InheritDevicePolicy int inheritDevicePolicy, boolean useParentsContacts, boolean updateCrossProfileIntentFiltersOnOTA, @CrossProfileIntentFilterAccessControlLevel int crossProfileIntentFilterAccessControl, @@ -1001,6 +1059,7 @@ public final class UserProperties implements Parcelable { setShowInLauncher(showInLauncher); setStartWithParent(startWithParent); setShowInSettings(showInSettings); + setHideInSettingsInQuietMode(hideInSettingsInQuietMode); setInheritDevicePolicy(inheritDevicePolicy); setUseParentsContacts(useParentsContacts); setUpdateCrossProfileIntentFiltersOnOTA(updateCrossProfileIntentFiltersOnOTA); diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig index 89ca915cb995..b2cc070228b7 100644 --- a/core/java/android/content/pm/flags.aconfig +++ b/core/java/android/content/pm/flags.aconfig @@ -19,6 +19,7 @@ flag { namespace: "package_manager_service" description: "Feature flag to enable the prevent sdk-library be an application." bug: "295843617" + is_fixed_read_only: true } flag { @@ -42,3 +43,10 @@ flag { description: "Feature flag to enable the feature to retrieve package info without installation." bug: "269149275" } + +flag { + name: "use_art_service_v2" + namespace: "package_manager_service" + 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" +} diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationUserState.java b/core/java/android/content/pm/verify/domain/DomainVerificationUserState.java index 1e60abb30011..7ada9469726b 100644 --- a/core/java/android/content/pm/verify/domain/DomainVerificationUserState.java +++ b/core/java/android/content/pm/verify/domain/DomainVerificationUserState.java @@ -231,7 +231,7 @@ public final class DomainVerificationUserState implements Parcelable { } /** - * Mapping of domain host to state, as defined by {@link DomainState}. + * Mapping of domain host to state, as defined by the {@code DOMAIN_STATE_*} constants */ @DataClass.Generated.Member public @NonNull Map<String,Integer> getHostToStateMap() { diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index ed22284ae23d..1b37092f77b6 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -2210,8 +2210,7 @@ public class Resources { * * <p>The best practices is to obtain metrics from * {@link WindowManager#getCurrentWindowMetrics()} for window bounds. The value obtained from - * this API may be wrong if {@link Context#getResources()} is from - * non-{@link android.annotation.UiContext}. + * this API may be wrong if {@link Context#getResources()} is not from a {@code UiContext}. * For example, use the {@link DisplayMetrics} obtained from {@link Application#getResources()} * to build {@link android.app.Activity} UI elements especially when the * {@link android.app.Activity} is in the multi-window mode or on the secondary {@link Display}. diff --git a/core/java/android/credentials/CreateCredentialException.java b/core/java/android/credentials/CreateCredentialException.java index c3440043a4c0..8f07d19a8592 100644 --- a/core/java/android/credentials/CreateCredentialException.java +++ b/core/java/android/credentials/CreateCredentialException.java @@ -18,7 +18,7 @@ package android.credentials; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.Activity; +import android.content.Context; import android.os.CancellationSignal; import android.os.OutcomeReceiver; @@ -28,8 +28,8 @@ import java.util.concurrent.Executor; /** * Represents an error encountered during the - * {@link CredentialManager#createCredential(CreateCredentialRequest, - * Activity, CancellationSignal, Executor, OutcomeReceiver)} operation. + * {@link CredentialManager#createCredential(Context, CreateCredentialRequest, + * CancellationSignal, Executor, OutcomeReceiver)} operation. */ public class CreateCredentialException extends Exception { /** @@ -41,7 +41,7 @@ public class CreateCredentialException extends Exception { /** * The error type value for when no create options are available from any provider(s), - * for the given {@link CredentialManager#createCredential(CreateCredentialRequest, Activity, + * for the given {@link CredentialManager#createCredential(Context, CreateCredentialRequest, * CancellationSignal, Executor, OutcomeReceiver)} request. */ @NonNull diff --git a/core/java/android/credentials/CredentialDescription.java b/core/java/android/credentials/CredentialDescription.java index db71624cbe89..755e6590a934 100644 --- a/core/java/android/credentials/CredentialDescription.java +++ b/core/java/android/credentials/CredentialDescription.java @@ -155,8 +155,7 @@ public final class CredentialDescription implements Parcelable { } /** - * {@link CredentialDescription#mType} and - * {@link CredentialDescription#mSupportedElementKeys} are enough for hashing. Constructor + * {@link #getType()} and {@link #getSupportedElementKeys()} are enough for hashing. Constructor * enforces {@link CredentialEntry} to have the same type and * {@link android.app.slice.Slice} contained by the entry can not be hashed. */ @@ -166,8 +165,7 @@ public final class CredentialDescription implements Parcelable { } /** - * {@link CredentialDescription#mType} and - * {@link CredentialDescription#mSupportedElementKeys} are enough for equality check. + * {@link #getType()} and {@link #getSupportedElementKeys()} are enough for equality check. */ @Override public boolean equals(Object obj) { diff --git a/core/java/android/credentials/CredentialProviderInfo.java b/core/java/android/credentials/CredentialProviderInfo.java index 95ca0112b8b3..8503072838d1 100644 --- a/core/java/android/credentials/CredentialProviderInfo.java +++ b/core/java/android/credentials/CredentialProviderInfo.java @@ -16,12 +16,14 @@ package android.credentials; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; import android.content.ComponentName; import android.content.Context; import android.content.pm.ServiceInfo; +import android.credentials.flags.Flags; import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.Parcelable; @@ -42,6 +44,7 @@ public final class CredentialProviderInfo implements Parcelable { @NonNull private final List<String> mCapabilities = new ArrayList<>(); @Nullable private final CharSequence mOverrideLabel; @Nullable private CharSequence mSettingsSubtitle = null; + @Nullable private CharSequence mSettingsActivity = null; private final boolean mIsSystemProvider; private final boolean mIsEnabled; private final boolean mIsPrimary; @@ -59,6 +62,7 @@ public final class CredentialProviderInfo implements Parcelable { mIsEnabled = builder.mIsEnabled; mIsPrimary = builder.mIsPrimary; mOverrideLabel = builder.mOverrideLabel; + mSettingsActivity = builder.mSettingsActivity; } /** Returns true if the service supports the given {@code credentialType}, false otherwise. */ @@ -104,10 +108,7 @@ public final class CredentialProviderInfo implements Parcelable { return mIsEnabled; } - /** - * Returns whether the provider is set as primary by the user. - * - */ + /** Returns whether the provider is set as primary by the user. */ public boolean isPrimary() { return mIsPrimary; } @@ -118,6 +119,18 @@ public final class CredentialProviderInfo implements Parcelable { return mSettingsSubtitle; } + /** + * Returns the settings activity. + * + * @hide + */ + @Nullable + @TestApi + @FlaggedApi(Flags.FLAG_SETTINGS_ACTIVITY_ENABLED) + public CharSequence getSettingsActivity() { + return mSettingsActivity; + } + /** Returns the component name for the service. */ @NonNull public ComponentName getComponentName() { @@ -133,6 +146,7 @@ public final class CredentialProviderInfo implements Parcelable { dest.writeBoolean(mIsPrimary); TextUtils.writeToParcel(mOverrideLabel, dest, flags); TextUtils.writeToParcel(mSettingsSubtitle, dest, flags); + TextUtils.writeToParcel(mSettingsActivity, dest, flags); } @Override @@ -161,6 +175,9 @@ public final class CredentialProviderInfo implements Parcelable { + "settingsSubtitle=" + mSettingsSubtitle + ", " + + "settingsActivity=" + + mSettingsActivity + + ", " + "capabilities=" + String.join(",", mCapabilities) + "}"; @@ -174,6 +191,7 @@ public final class CredentialProviderInfo implements Parcelable { mIsPrimary = in.readBoolean(); mOverrideLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); mSettingsSubtitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); + mSettingsActivity = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); } public static final @NonNull Parcelable.Creator<CredentialProviderInfo> CREATOR = @@ -196,6 +214,7 @@ public final class CredentialProviderInfo implements Parcelable { @NonNull private List<String> mCapabilities = new ArrayList<>(); private boolean mIsSystemProvider = false; @Nullable private CharSequence mSettingsSubtitle = null; + @Nullable private CharSequence mSettingsActivity = null; private boolean mIsEnabled = false; private boolean mIsPrimary = false; @Nullable private CharSequence mOverrideLabel = null; @@ -231,6 +250,16 @@ public final class CredentialProviderInfo implements Parcelable { return this; } + /** + * Sets the settings activity. + * + * @hide + */ + public @NonNull Builder setSettingsActivity(@Nullable CharSequence settingsActivity) { + mSettingsActivity = settingsActivity; + return this; + } + /** Sets a list of capabilities this provider service can support. */ public @NonNull Builder addCapabilities(@NonNull List<String> capabilities) { mCapabilities.addAll(capabilities); diff --git a/core/java/android/credentials/GetCredentialException.java b/core/java/android/credentials/GetCredentialException.java index 720c53ba51cd..0421d1f5aba0 100644 --- a/core/java/android/credentials/GetCredentialException.java +++ b/core/java/android/credentials/GetCredentialException.java @@ -18,7 +18,7 @@ package android.credentials; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.Activity; +import android.content.Context; import android.os.CancellationSignal; import android.os.OutcomeReceiver; @@ -28,8 +28,8 @@ import java.util.concurrent.Executor; /** * Represents an error encountered during the - * {@link CredentialManager#getCredential(GetCredentialRequest, - * Activity, CancellationSignal, Executor, OutcomeReceiver)} operation. + * {@link CredentialManager#getCredential(Context, GetCredentialRequest, + * CancellationSignal, Executor, OutcomeReceiver)} operation. */ public class GetCredentialException extends Exception { /** @@ -41,7 +41,7 @@ public class GetCredentialException extends Exception { /** * The error type value for when no credential is found available for the given {@link - * CredentialManager#getCredential(GetCredentialRequest, Activity, CancellationSignal, + * CredentialManager#getCredential(Context, GetCredentialRequest, CancellationSignal, * Executor, OutcomeReceiver)} request. */ @NonNull diff --git a/core/java/android/credentials/PrepareGetCredentialResponse.java b/core/java/android/credentials/PrepareGetCredentialResponse.java index 056b18a51b6d..212f5716d041 100644 --- a/core/java/android/credentials/PrepareGetCredentialResponse.java +++ b/core/java/android/credentials/PrepareGetCredentialResponse.java @@ -22,7 +22,6 @@ import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; -import android.app.Activity; import android.app.PendingIntent; import android.content.Context; import android.content.IntentSender; @@ -36,7 +35,7 @@ import java.util.concurrent.Executor; /** * A response object that prefetches user app credentials and provides metadata about them. It can * then be used to issue the full credential retrieval flow via the - * {@link CredentialManager#getCredential(PendingGetCredentialHandle, Activity, CancellationSignal, + * {@link CredentialManager#getCredential(Context, PendingGetCredentialHandle, CancellationSignal, * Executor, OutcomeReceiver)} method to perform the remaining flows such as consent collection * and credential selection, to officially retrieve a credential. */ @@ -44,7 +43,7 @@ public final class PrepareGetCredentialResponse { /** * A handle that represents a pending get-credential operation. Pass this handle to {@link - * CredentialManager#getCredential(PendingGetCredentialHandle, Activity, CancellationSignal, + * CredentialManager#getCredential(Context, PendingGetCredentialHandle, CancellationSignal, * Executor, OutcomeReceiver)} to perform the remaining flows to officially retrieve a * credential. */ @@ -144,7 +143,7 @@ public final class PrepareGetCredentialResponse { /** * Returns a handle that represents this pending get-credential operation. Pass this handle to - * {@link CredentialManager#getCredential(PendingGetCredentialHandle, Activity, + * {@link CredentialManager#getCredential(Context, PendingGetCredentialHandle, * CancellationSignal, Executor, OutcomeReceiver)} to perform the remaining flows to officially * retrieve a credential. */ diff --git a/core/java/android/credentials/flags.aconfig b/core/java/android/credentials/flags.aconfig index 0d0305b5b626..9b819a792f8f 100644 --- a/core/java/android/credentials/flags.aconfig +++ b/core/java/android/credentials/flags.aconfig @@ -5,4 +5,11 @@ flag { name: "settings_activity_enabled" description: "Enable the Credential Manager Settings Activity APIs" bug: "300014059" -}
\ No newline at end of file +} + +flag { + namespace: "credential_manager" + name: "instant_apps_enabled" + description: "Enables Credential Manager to work with Instant Apps" + bug: "302190269" +} 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/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java index 82694ee3463b..9c05dfc94ad4 100644 --- a/core/java/android/hardware/biometrics/BiometricManager.java +++ b/core/java/android/hardware/biometrics/BiometricManager.java @@ -537,6 +537,24 @@ public class BiometricManager { } /** + * Listens for biometric prompt status, i.e., if it is being shown or idle. + * @hide + */ + @RequiresPermission(USE_BIOMETRIC_INTERNAL) + public void registerBiometricPromptStatusListener( + IBiometricPromptStatusListener callback) { + if (mService != null) { + try { + mService.registerBiometricPromptStatusListener(callback); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } else { + Slog.w(TAG, "registerBiometricPromptOnKeyguardCallback(): Service not connected"); + } + } + + /** * Requests all {@link Authenticators.Types#BIOMETRIC_STRONG} sensors to have their * authenticatorId invalidated for the specified user. This happens when enrollments have been * added on devices with multiple biometric sensors. diff --git a/core/java/android/hardware/biometrics/CryptoObject.java b/core/java/android/hardware/biometrics/CryptoObject.java index 151f819329c9..6ac1efb49839 100644 --- a/core/java/android/hardware/biometrics/CryptoObject.java +++ b/core/java/android/hardware/biometrics/CryptoObject.java @@ -114,8 +114,8 @@ public class CryptoObject { } /** - * Get {@link PresentationSession} object. - * @return {@link PresentationSession} object or null if this doesn't contain one. + * Get {@link KeyAgreement} object. + * @return {@link KeyAgreement} object or null if this doesn't contain one. */ @FlaggedApi(FLAG_ADD_KEY_AGREEMENT_CRYPTO_OBJECT) public KeyAgreement getKeyAgreement() { diff --git a/core/java/android/hardware/biometrics/IAuthService.aidl b/core/java/android/hardware/biometrics/IAuthService.aidl index c2e5c0b6d519..8eede472bec5 100644 --- a/core/java/android/hardware/biometrics/IAuthService.aidl +++ b/core/java/android/hardware/biometrics/IAuthService.aidl @@ -17,6 +17,7 @@ package android.hardware.biometrics; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; +import android.hardware.biometrics.IBiometricPromptStatusListener; import android.hardware.biometrics.IBiometricServiceReceiver; import android.hardware.biometrics.IInvalidationCallback; import android.hardware.biometrics.ITestSession; @@ -63,6 +64,9 @@ interface IAuthService { // Register callback for when keyguard biometric eligibility changes. void registerEnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback); + // Register callback to check biometric prompt status. + void registerBiometricPromptStatusListener(IBiometricPromptStatusListener callback); + // Requests all BIOMETRIC_STRONG sensors to have their authenticatorId invalidated for the // specified user. This happens when enrollments have been added on devices with multiple // biometric sensors. diff --git a/core/java/android/security/keymaster/KeyAttestationApplicationId.aidl b/core/java/android/hardware/biometrics/IBiometricPromptStatusListener.aidl index 9f6ff58ed5ce..7a0f4389ed60 100644 --- a/core/java/android/security/keymaster/KeyAttestationApplicationId.aidl +++ b/core/java/android/hardware/biometrics/IBiometricPromptStatusListener.aidl @@ -1,11 +1,11 @@ /* - * Copyright (c) 2016, The Android Open Source Project + * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,9 +14,14 @@ * limitations under the License. */ -package android.security.keymaster; +package android.hardware.biometrics; -/* The cpp_header is relative to system/security/keystore/include - * Link against libkeystore_binder to make use of the native implementation of this Parcelable. +/** + * Communication channel to propagate biometric prompt status. Implementation of this interface + * should be registered in BiometricService#registerBiometricPromptStatusListener. + * @hide */ -parcelable KeyAttestationApplicationId cpp_header "keystore/KeyAttestationApplicationId.h"; +oneway interface IBiometricPromptStatusListener { + void onBiometricPromptShowing(); + void onBiometricPromptIdle(); +}
\ No newline at end of file diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl index 18c8d1bd3a1e..36606a135f3e 100644 --- a/core/java/android/hardware/biometrics/IBiometricService.aidl +++ b/core/java/android/hardware/biometrics/IBiometricService.aidl @@ -17,6 +17,7 @@ package android.hardware.biometrics; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; +import android.hardware.biometrics.IBiometricPromptStatusListener; import android.hardware.biometrics.IBiometricServiceReceiver; import android.hardware.biometrics.IBiometricAuthenticator; import android.hardware.biometrics.IInvalidationCallback; @@ -68,6 +69,10 @@ interface IBiometricService { @EnforcePermission("USE_BIOMETRIC_INTERNAL") void registerEnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback); + // Register a callback for biometric prompt status on keyguard. + @EnforcePermission("USE_BIOMETRIC_INTERNAL") + void registerBiometricPromptStatusListener(IBiometricPromptStatusListener callback); + // Notify BiometricService when <Biometric>Service is ready to start the prepared client. // Client lifecycle is still managed in <Biometric>Service. @EnforcePermission("USE_BIOMETRIC_INTERNAL") diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index 990ebc5fdbcd..f347909348c8 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -20,6 +20,7 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.HdrCapabilities.HdrType; import android.Manifest; +import android.annotation.FlaggedApi; import android.annotation.FloatRange; import android.annotation.IntDef; import android.annotation.IntRange; @@ -27,7 +28,6 @@ import android.annotation.LongDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; -import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; @@ -377,7 +377,7 @@ public final class DisplayManager { * @see #createVirtualDisplay * @hide */ - @SuppressLint("UnflaggedApi") + @FlaggedApi(android.companion.virtual.flags.Flags.FLAG_VDM_PUBLIC_APIS) @SystemApi public static final int VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT = 1 << 7; diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl index a6d8cafe8263..0c95c2ec7a7a 100644 --- a/core/java/android/nfc/INfcAdapter.aidl +++ b/core/java/android/nfc/INfcAdapter.aidl @@ -79,4 +79,9 @@ interface INfcAdapter Map getTagIntentAppPreferenceForUser(int userId); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)") int setTagIntentAppPreferenceForUser(int userId, String pkg, boolean allow); + + boolean isReaderOptionEnabled(); + boolean isReaderOptionSupported(); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)") + boolean enableReaderOption(boolean enable); } diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java index 1307dfc2665e..46586308e3cf 100644 --- a/core/java/android/nfc/NfcAdapter.java +++ b/core/java/android/nfc/NfcAdapter.java @@ -17,6 +17,7 @@ package android.nfc; import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -1826,6 +1827,97 @@ public final class NfcAdapter { } /** + * Sets NFC Reader option feature. + * <p>This API is for the Settings application. + * @return True if successful + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_ENABLE_NFC_READER_OPTION) + @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) + public boolean enableReaderOption(boolean enable) { + if (!sHasNfcFeature) { + throw new UnsupportedOperationException(); + } + try { + return sService.enableReaderOption(enable); + } catch (RemoteException e) { + attemptDeadServiceRecovery(e); + // Try one more time + if (sService == null) { + Log.e(TAG, "Failed to recover NFC Service."); + return false; + } + try { + return sService.enableReaderOption(enable); + } catch (RemoteException ee) { + Log.e(TAG, "Failed to recover NFC Service."); + } + return false; + } + } + + /** + * Checks if the device supports NFC Reader option functionality. + * + * @return True if device supports NFC Reader option, false otherwise + * @throws UnsupportedOperationException if FEATURE_NFC is unavailable. + */ + @FlaggedApi(Flags.FLAG_ENABLE_NFC_READER_OPTION) + public boolean isReaderOptionSupported() { + if (!sHasNfcFeature) { + throw new UnsupportedOperationException(); + } + try { + return sService.isReaderOptionSupported(); + } catch (RemoteException e) { + attemptDeadServiceRecovery(e); + // Try one more time + if (sService == null) { + Log.e(TAG, "Failed to recover NFC Service."); + return false; + } + try { + return sService.isReaderOptionSupported(); + } catch (RemoteException ee) { + Log.e(TAG, "Failed to recover NFC Service."); + } + return false; + } + } + + /** + * Checks NFC Reader option feature is enabled. + * + * @return True if NFC Reader option is enabled, false otherwise + * @throws UnsupportedOperationException if FEATURE_NFC is unavailable. + * @throws UnsupportedOperationException if device doesn't support + * NFC Reader option functionality. {@link #isReaderOptionSupported} + */ + @FlaggedApi(Flags.FLAG_ENABLE_NFC_READER_OPTION) + public boolean isReaderOptionEnabled() { + if (!sHasNfcFeature) { + throw new UnsupportedOperationException(); + } + try { + return sService.isReaderOptionEnabled(); + } catch (RemoteException e) { + attemptDeadServiceRecovery(e); + // Try one more time + if (sService == null) { + Log.e(TAG, "Failed to recover NFC Service."); + return false; + } + try { + return sService.isReaderOptionEnabled(); + } catch (RemoteException ee) { + Log.e(TAG, "Failed to recover NFC Service."); + } + return false; + } + } + + /** * Enable NDEF Push feature. * <p>This API is for the Settings application. * @hide diff --git a/core/java/android/nfc/flags.aconfig b/core/java/android/nfc/flags.aconfig index e3faf3978856..55b0b4261763 100644 --- a/core/java/android/nfc/flags.aconfig +++ b/core/java/android/nfc/flags.aconfig @@ -6,3 +6,10 @@ flag { description: "Flag for NFC mainline changes" bug: "292140387" } + +flag { + name: "enable_nfc_reader_option" + namespace: "nfc" + description: "Flag for NFC reader option API changes" + bug: "291187960" +} diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java index 092923e927a3..6a4ec9b7605a 100644 --- a/core/java/android/os/BatteryManager.java +++ b/core/java/android/os/BatteryManager.java @@ -16,7 +16,10 @@ package android.os; +import static android.os.Flags.FLAG_STATE_OF_HEALTH_PUBLIC; + import android.Manifest.permission; +import android.annotation.FlaggedApi; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; @@ -354,17 +357,11 @@ public class BatteryManager { public static final int BATTERY_PROPERTY_CHARGING_POLICY = 9; /** - * - * Percentage representing the measured battery state of health (remaining - * estimated full charge capacity relative to the rated capacity in %). - * - * <p class="note"> - * The sender must hold the {@link android.Manifest.permission#BATTERY_STATS} permission. - * - * @hide + * Percentage representing the measured battery state of health. + * This is the remaining estimated full charge capacity relative + * to the rated capacity in %. */ - @RequiresPermission(permission.BATTERY_STATS) - @SystemApi + @FlaggedApi(FLAG_STATE_OF_HEALTH_PUBLIC) public static final int BATTERY_PROPERTY_STATE_OF_HEALTH = 10; private final Context mContext; diff --git a/core/java/android/os/BatteryStatsManager.java b/core/java/android/os/BatteryStatsManager.java index 955fad3d1a48..3abe9a0e38f5 100644 --- a/core/java/android/os/BatteryStatsManager.java +++ b/core/java/android/os/BatteryStatsManager.java @@ -520,8 +520,9 @@ public final class BatteryStatsManager { * @param uid calling package uid * @param reason why Bluetooth has been turned on * @param packageName package responsible for this change - * @Deprecated Bluetooth self report its state and no longer call this + * @deprecated Bluetooth self report its state and no longer call this */ + @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void reportBluetoothOn(int uid, int reason, @NonNull String packageName) { } @@ -532,8 +533,9 @@ public final class BatteryStatsManager { * @param uid calling package uid * @param reason why Bluetooth has been turned on * @param packageName package responsible for this change - * @Deprecated Bluetooth self report its state and no longer call this + * @deprecated Bluetooth self report its state and no longer call this */ + @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void reportBluetoothOff(int uid, int reason, @NonNull String packageName) { } diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig index a95e66d9eb04..37559b32e841 100644 --- a/core/java/android/os/flags.aconfig +++ b/core/java/android/os/flags.aconfig @@ -1,6 +1,13 @@ package: "android.os" flag { + name: "state_of_health_public" + namespace: "system_sw_battery" + description: "Feature flag for making state_of_health a public api." + bug: "288842045" +} + +flag { name: "disallow_cellular_null_ciphers_restriction" namespace: "cellular_security" description: "Guards a new UserManager user restriction that admins can use to require cellular encryption on their managed devices." diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig index 88f62f327fdd..66ad12c7559a 100644 --- a/core/java/android/os/vibrator/flags.aconfig +++ b/core/java/android/os/vibrator/flags.aconfig @@ -27,3 +27,13 @@ flag { description: "Enables the APIs for vibration serialization/deserialization." bug: "245129509" } + +flag { + namespace: "haptics" + name: "haptic_feedback_vibration_oem_customization_enabled" + description: "Enables OEMs/devices to customize vibrations for haptic feedback" + # Make read only. This is because the flag is used only once, and this could happen before + # the read-write flag values propagate to the device. + is_fixed_read_only: true + bug: "291128479" +} diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 5c7a47123b94..b19a034d4628 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -5354,6 +5354,37 @@ public final class Settings { public static final Uri NOTIFICATION_SOUND_CACHE_URI = getUriFor(NOTIFICATION_SOUND_CACHE); /** + * When enabled, notifications attention effects: sound, vibration, flashing + * will have a cooldown timer. + * + * The value 1 - enable, 0 - disable + * @hide + */ + public static final String NOTIFICATION_COOLDOWN_ENABLED = + "notification_cooldown_enabled"; + + /** + * When enabled, notification cooldown will apply to all notifications. + * Otherwise cooldown will only apply to conversations. + * + * The value 1 - enable, 0 - disable + * Only valid if {@code NOTIFICATION_COOLDOWN_ENABLED} is enabled. + * @hide + */ + public static final String NOTIFICATION_COOLDOWN_ALL = + "notification_cooldown_all"; + + /** + * When enabled, notification attention effects will be restricted to vibration only + * as long as the screen is unlocked. + * + * The value 1 - enable, 0 - disable + * @hide + */ + public static final String NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED = + "notification_cooldown_vibrate_unlocked"; + + /** * Persistent store for the system-wide default alarm alert. * * @see #RINGTONE @@ -12471,6 +12502,13 @@ public final class Settings { "wireless_charging_started_sound"; /** + * Whether to auto enable reverse charging once plugged-in. + * @hide + */ + public static final String REVERSE_CHARGING_AUTO_ON = + "settings_key_reverse_charging_auto_turn_on"; + + /** * URI for "wired charging started" sound. * @hide */ diff --git a/core/java/android/security/keymaster/KeyAttestationApplicationId.java b/core/java/android/security/keymaster/KeyAttestationApplicationId.java deleted file mode 100644 index 670f30e1b04b..000000000000 --- a/core/java/android/security/keymaster/KeyAttestationApplicationId.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.security.keymaster; - -import android.os.Parcel; -import android.os.Parcelable; - -/** - * @hide - * The information aggregated by this class is used by keystore to identify a caller of the - * keystore API toward a remote party. It aggregates multiple PackageInfos because keystore - * can only determine a caller by uid granularity, and a uid can be shared by multiple packages. - * The remote party must decide if it trusts all of the packages enough to consider the - * confidentiality of the key material in question intact. - */ -public class KeyAttestationApplicationId implements Parcelable { - private final KeyAttestationPackageInfo[] mAttestationPackageInfos; - - /** - * @param mAttestationPackageInfos - */ - public KeyAttestationApplicationId(KeyAttestationPackageInfo[] mAttestationPackageInfos) { - super(); - this.mAttestationPackageInfos = mAttestationPackageInfos; - } - - /** - * @return the mAttestationPackageInfos - */ - public KeyAttestationPackageInfo[] getAttestationPackageInfos() { - return mAttestationPackageInfos; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeTypedArray(mAttestationPackageInfos, flags); - } - - public static final @android.annotation.NonNull Parcelable.Creator<KeyAttestationApplicationId> CREATOR - = new Parcelable.Creator<KeyAttestationApplicationId>() { - @Override - public KeyAttestationApplicationId createFromParcel(Parcel source) { - return new KeyAttestationApplicationId(source); - } - - @Override - public KeyAttestationApplicationId[] newArray(int size) { - return new KeyAttestationApplicationId[size]; - } - }; - - KeyAttestationApplicationId(Parcel source) { - mAttestationPackageInfos = source.createTypedArray(KeyAttestationPackageInfo.CREATOR); - } -} diff --git a/core/java/android/security/keymaster/KeyAttestationPackageInfo.java b/core/java/android/security/keymaster/KeyAttestationPackageInfo.java deleted file mode 100644 index c0b8d8dfd4d9..000000000000 --- a/core/java/android/security/keymaster/KeyAttestationPackageInfo.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.security.keymaster; - -import android.content.pm.Signature; -import android.os.Parcel; -import android.os.Parcelable; - -/** - * @hide - * This class constitutes and excerpt from the PackageManager's PackageInfo for the purpose of - * key attestation. It is part of the KeyAttestationApplicationId, which is used by - * keystore to identify the caller of the keystore API towards a remote party. - */ -public class KeyAttestationPackageInfo implements Parcelable { - private final String mPackageName; - private final long mPackageVersionCode; - private final Signature[] mPackageSignatures; - - /** - * @param mPackageName - * @param mPackageVersionCode - * @param mPackageSignatures - */ - public KeyAttestationPackageInfo( - String mPackageName, long mPackageVersionCode, Signature[] mPackageSignatures) { - super(); - this.mPackageName = mPackageName; - this.mPackageVersionCode = mPackageVersionCode; - this.mPackageSignatures = mPackageSignatures; - } - /** - * @return the mPackageName - */ - public String getPackageName() { - return mPackageName; - } - /** - * @return the mPackageVersionCode - */ - public long getPackageVersionCode() { - return mPackageVersionCode; - } - /** - * @return the mPackageSignatures - */ - public Signature[] getPackageSignatures() { - return mPackageSignatures; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeString(mPackageName); - dest.writeLong(mPackageVersionCode); - dest.writeTypedArray(mPackageSignatures, flags); - } - - public static final @android.annotation.NonNull Parcelable.Creator<KeyAttestationPackageInfo> CREATOR - = new Parcelable.Creator<KeyAttestationPackageInfo>() { - @Override - public KeyAttestationPackageInfo createFromParcel(Parcel source) { - return new KeyAttestationPackageInfo(source); - } - - @Override - public KeyAttestationPackageInfo[] newArray(int size) { - return new KeyAttestationPackageInfo[size]; - } - }; - - private KeyAttestationPackageInfo(Parcel source) { - mPackageName = source.readString(); - mPackageVersionCode = source.readLong(); - mPackageSignatures = source.createTypedArray(Signature.CREATOR); - } -} diff --git a/core/java/android/service/autofill/FillRequest.java b/core/java/android/service/autofill/FillRequest.java index 8cf2ce49712a..7ec14830b0af 100644 --- a/core/java/android/service/autofill/FillRequest.java +++ b/core/java/android/service/autofill/FillRequest.java @@ -97,8 +97,6 @@ public final class FillRequest implements Parcelable { */ public static final @RequestFlags int FLAG_VIEW_NOT_FOCUSED = 0x10; - // The flag value 0x20 has been defined in AutofillManager. - /** * Indicates the request supports fill dialog presentation for the fields, the * system will send the request when the activity just started. diff --git a/core/java/android/service/credentials/BeginCreateCredentialResponse.java b/core/java/android/service/credentials/BeginCreateCredentialResponse.java index df934335e49d..5d96f69c2929 100644 --- a/core/java/android/service/credentials/BeginCreateCredentialResponse.java +++ b/core/java/android/service/credentials/BeginCreateCredentialResponse.java @@ -143,7 +143,7 @@ public final class BeginCreateCredentialResponse implements Parcelable { * * <p> Note that as a provider service you will only be able to set a remote entry if : * - Provider service possesses the - * {@link Manifest.permission.PROVIDE_REMOTE_CREDENTIALS} permission. + * {@link Manifest.permission#PROVIDE_REMOTE_CREDENTIALS} permission. * - Provider service is configured as the provider that can provide remote entries. * * If the above conditions are not met, setting back {@link BeginCreateCredentialResponse} diff --git a/core/java/android/service/credentials/BeginGetCredentialResponse.java b/core/java/android/service/credentials/BeginGetCredentialResponse.java index 5ed06ac1ade7..ae6ca25a38ad 100644 --- a/core/java/android/service/credentials/BeginGetCredentialResponse.java +++ b/core/java/android/service/credentials/BeginGetCredentialResponse.java @@ -160,7 +160,7 @@ public final class BeginGetCredentialResponse implements Parcelable { * * <p> Note that as a provider service you will only be able to set a remote entry if : * - Provider service possesses the - * {@link Manifest.permission.PROVIDE_REMOTE_CREDENTIALS} permission. + * {@link Manifest.permission#PROVIDE_REMOTE_CREDENTIALS} permission. * - Provider service is configured as the provider that can provide remote entries. * * If the above conditions are not met, setting back {@link BeginGetCredentialResponse} diff --git a/core/java/android/service/credentials/CallingAppInfo.java b/core/java/android/service/credentials/CallingAppInfo.java index e755581638f5..c3524c5c85aa 100644 --- a/core/java/android/service/credentials/CallingAppInfo.java +++ b/core/java/android/service/credentials/CallingAppInfo.java @@ -103,7 +103,7 @@ public final class CallingAppInfo implements Parcelable { * of other applications. * * Android system makes sure that only applications that poses the permission - * {@link android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN} can set the origin on + * {@link android.Manifest.permission#CREDENTIAL_MANAGER_SET_ORIGIN} can set the origin on * the incoming {@link android.credentials.GetCredentialRequest} or * {@link android.credentials.CreateCredentialRequest}. */ diff --git a/core/java/android/service/credentials/CredentialProviderInfoFactory.java b/core/java/android/service/credentials/CredentialProviderInfoFactory.java index 0aa6a232d416..514d72252c3d 100644 --- a/core/java/android/service/credentials/CredentialProviderInfoFactory.java +++ b/core/java/android/service/credentials/CredentialProviderInfoFactory.java @@ -45,8 +45,8 @@ import android.util.AttributeSet; import android.util.Slog; import android.util.Xml; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -135,8 +135,8 @@ public final class CredentialProviderInfoFactory { } /** - * Constructs an information instance of the credential provider for testing purposes. Does - * not run any verifications and passes parameters as is. + * Constructs an information instance of the credential provider for testing purposes. Does not + * run any verifications and passes parameters as is. */ @VisibleForTesting public static CredentialProviderInfo createForTests( @@ -151,7 +151,6 @@ public final class CredentialProviderInfoFactory { .setSystemProvider(isSystemProvider) .addCapabilities(capabilities) .build(); - } private static void verifyProviderPermission(ServiceInfo serviceInfo) throws SecurityException { @@ -267,15 +266,21 @@ public final class CredentialProviderInfoFactory { allAttributes, com.android.internal.R.styleable.CredentialProvider); builder.setSettingsSubtitle( - afsAttributes.getString( + getAfsAttributeSafe( + afsAttributes, R.styleable.CredentialProvider_settingsSubtitle)); + builder.setSettingsActivity( + getAfsAttributeSafe( + afsAttributes, + R.styleable.CredentialProvider_settingsActivity)); } catch (Exception e) { - Slog.e(TAG, "Failed to get XML attr", e); + Slog.w(TAG, "Failed to get XML attr for metadata", e); } finally { if (afsAttributes != null) { afsAttributes.recycle(); } } + builder.addCapabilities(parseXmlProviderOuterCapabilities(parser, resources)); } else { Slog.w(TAG, "Meta-data does not start with credential-provider-service tag"); @@ -287,6 +292,21 @@ public final class CredentialProviderInfoFactory { return builder; } + private static @Nullable String getAfsAttributeSafe( + @Nullable TypedArray afsAttributes, int resId) { + if (afsAttributes == null) { + return null; + } + + try { + return afsAttributes.getString(resId); + } catch (Exception e) { + Slog.w(TAG, "Failed to get XML attr from afs attributes", e); + } + + return null; + } + private static List<String> parseXmlProviderOuterCapabilities( XmlPullParser parser, Resources resources) throws IOException, XmlPullParserException { final List<String> capabilities = new ArrayList<>(); diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java index 0dad96eb3b5b..c82a4cabaddd 100644 --- a/core/java/android/service/notification/NotificationRankingUpdate.java +++ b/core/java/android/service/notification/NotificationRankingUpdate.java @@ -17,7 +17,6 @@ package android.service.notification; import android.annotation.Nullable; import android.annotation.SuppressLint; -import android.annotation.TestApi; import android.app.Notification; import android.os.Bundle; import android.os.Parcel; @@ -27,6 +26,7 @@ import android.system.ErrnoException; import android.system.OsConstants; import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags; @@ -40,7 +40,6 @@ import java.util.List; * @hide */ @SuppressLint({"ParcelNotFinal", "ParcelCreator"}) -@TestApi public class NotificationRankingUpdate implements Parcelable { private final NotificationListenerService.RankingMap mRankingMap; @@ -101,7 +100,7 @@ public class NotificationRankingUpdate implements Parcelable { throw new RuntimeException(e); } finally { mapParcel.recycle(); - if (buffer != null) { + if (buffer != null && mRankingMapFd != null) { mRankingMapFd.unmap(buffer); mRankingMapFd.close(); } @@ -140,7 +139,7 @@ public class NotificationRankingUpdate implements Parcelable { * * @hide */ - @TestApi + @VisibleForTesting(otherwise = VisibleForTesting.NONE) public final boolean isFdNotNullAndClosed() { return mRankingMapFd != null && mRankingMapFd.getFd() == -1; } diff --git a/core/java/android/speech/SpeechRecognizer.java b/core/java/android/speech/SpeechRecognizer.java index bb5dd7f0cdfe..3ed13bbeeaad 100644 --- a/core/java/android/speech/SpeechRecognizer.java +++ b/core/java/android/speech/SpeechRecognizer.java @@ -536,7 +536,16 @@ public class SpeechRecognizer { @MainThread public void setRecognitionListener(RecognitionListener listener) { checkIsCalledFromMainThread(); - putMessage(Message.obtain(mHandler, MSG_CHANGE_LISTENER, listener)); + if (mListener.mInternalListener == null) { + // This shortcut is needed because otherwise, if there's an error connecting, it never + // gets delivered. I.e., the onSuccess callback set up in connectToSystemService does + // not get called, MSG_CHANGE_LISTENER does not get executed, so the onError in the same + // place does not get forwarded anywhere. + // Thread-wise, this is safe as both this method and the handler are on the UI thread. + handleChangeListener(listener); + } else { + putMessage(Message.obtain(mHandler, MSG_CHANGE_LISTENER, listener)); + } } /** diff --git a/core/java/android/text/TextFlags.java b/core/java/android/text/TextFlags.java index 536e3ccd98f6..9edf298b1fd2 100644 --- a/core/java/android/text/TextFlags.java +++ b/core/java/android/text/TextFlags.java @@ -62,6 +62,18 @@ public final class TextFlags { }; /** + * List of the default values of the text flags. + * + * The order must be the same to the TEXT_ACONFIG_FLAGS. + */ + public static final boolean[] TEXT_ACONFIG_DEFAULT_VALUE = { + Flags.deprecateFontsXml(), + Flags.noBreakNoHyphenationSpan(), + Flags.phraseStrictFallback(), + Flags.useBoundsForWidth(), + }; + + /** * Get a key for the feature flag. */ public static String getKeyForFlag(@NonNull String flag) { diff --git a/core/java/android/text/flags/custom_locale_fallback.aconfig b/core/java/android/text/flags/custom_locale_fallback.aconfig index 04e64f96454b..52fe8834f234 100644 --- a/core/java/android/text/flags/custom_locale_fallback.aconfig +++ b/core/java/android/text/flags/custom_locale_fallback.aconfig @@ -4,5 +4,6 @@ flag { name: "custom_locale_fallback" namespace: "text" description: "A feature flag that adds custom locale fallback to the vendor customization XML. This enables vendors to add their locale specific fonts, e.g. Japanese font." - bug: "" + is_fixed_read_only: true + bug: "278768958" } diff --git a/core/java/android/text/flags/deprecate_fonts_xml.aconfig b/core/java/android/text/flags/deprecate_fonts_xml.aconfig index 58dc210af581..53621385dd4b 100644 --- a/core/java/android/text/flags/deprecate_fonts_xml.aconfig +++ b/core/java/android/text/flags/deprecate_fonts_xml.aconfig @@ -4,5 +4,7 @@ flag { name: "deprecate_fonts_xml" namespace: "text" description: "Feature flag for deprecating fonts.xml. By setting true for this feature flag, the new font configuration XML, /system/etc/font_fallback.xml is used. The new XML has a new syntax and flexibility of variable font declarations, but it is not compatible with the apps that reads fonts.xml. So, fonts.xml is maintained as a subset of the font_fallback.xml" + # Make read only, as it could be used before the Settings provider is initialized. + is_fixed_read_only: true bug: "281769620" } diff --git a/core/java/android/text/flags/fix_line_height_for_locale.aconfig b/core/java/android/text/flags/fix_line_height_for_locale.aconfig new file mode 100644 index 000000000000..8696bfa61e5c --- /dev/null +++ b/core/java/android/text/flags/fix_line_height_for_locale.aconfig @@ -0,0 +1,8 @@ +package: "com.android.text.flags" + +flag { + name: "fix_line_height_for_locale" + namespace: "text" + description: "Feature flag that preserve the line height of the TextView and EditText even if the the locale is different from Latin" + bug: "303326708" +} diff --git a/core/java/android/text/flags/optimized_font_loading.aconfig b/core/java/android/text/flags/optimized_font_loading.aconfig new file mode 100644 index 000000000000..0e4a69f90375 --- /dev/null +++ b/core/java/android/text/flags/optimized_font_loading.aconfig @@ -0,0 +1,11 @@ +package: "com.android.text.flags" + +flag { + name: "use_optimized_boottime_font_loading" + namespace: "text" + description: "Feature flag ensuring that font is loaded once and asynchronously." + # Make read only, as font loading is in the critical boot path which happens before the read-write + # flags propagate to the device. + is_fixed_read_only: true + bug: "304406888" +} diff --git a/core/java/android/util/Base64.java b/core/java/android/util/Base64.java index 92abd7c15f5f..33cc5e37da62 100644 --- a/core/java/android/util/Base64.java +++ b/core/java/android/util/Base64.java @@ -26,6 +26,7 @@ import java.io.UnsupportedEncodingException; * href="http://www.ietf.org/rfc/rfc2045.txt">2045</a> and <a * href="http://www.ietf.org/rfc/rfc3548.txt">3548</a>. */ +@android.ravenwood.annotations.RavenwoodWholeClassKeep public class Base64 { /** * Default values for encoder/decoder flags. diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java index 1ab055a6a7a1..a74cbe46b404 100644 --- a/core/java/android/view/Choreographer.java +++ b/core/java/android/view/Choreographer.java @@ -16,9 +16,11 @@ package android.view; +import static android.view.flags.Flags.FLAG_EXPECTED_PRESENTATION_TIME_API; import static android.view.DisplayEventReceiver.VSYNC_SOURCE_APP; import static android.view.DisplayEventReceiver.VSYNC_SOURCE_SURFACE_FLINGER; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; @@ -695,6 +697,7 @@ public final class Choreographer { */ @TestApi @UnsupportedAppUsage + @FlaggedApi(FLAG_EXPECTED_PRESENTATION_TIME_API) public long getFrameTimeNanos() { synchronized (mLock) { if (!mCallbacksRunning) { diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl index d554514349c3..11180aef4479 100644 --- a/core/java/android/view/IWindow.aidl +++ b/core/java/android/view/IWindow.aidl @@ -54,6 +54,10 @@ oneway interface IWindow { */ void executeCommand(String command, String parameters, in ParcelFileDescriptor descriptor); + /** + * Please dispatch through WindowStateResizeItem instead of directly calling this method from + * the system server. + */ void resized(in ClientWindowFrames frames, boolean reportDraw, in MergedConfiguration newMergedConfiguration, in InsetsState insetsState, boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, 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 139c0bebedec..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); @@ -1790,7 +1790,12 @@ public final class SurfaceControl implements Parcelable { public float xDpi; public float yDpi; + // Some modes have peak refresh rate lower than the panel vsync rate. public float refreshRate; + // Fixed rate of vsync deadlines for the panel. + // This can be higher then the peak refresh rate for some panel technologies + // See: VrrConfig.aidl + public float vsyncRate; public long appVsyncOffsetNanos; public long presentationDeadlineNanos; public int[] supportedHdrTypes; @@ -1811,6 +1816,7 @@ public final class SurfaceControl implements Parcelable { + ", xDpi=" + xDpi + ", yDpi=" + yDpi + ", refreshRate=" + refreshRate + + ", vsyncRate=" + vsyncRate + ", appVsyncOffsetNanos=" + appVsyncOffsetNanos + ", presentationDeadlineNanos=" + presentationDeadlineNanos + ", supportedHdrTypes=" + Arrays.toString(supportedHdrTypes) @@ -1828,6 +1834,7 @@ public final class SurfaceControl implements Parcelable { && Float.compare(that.xDpi, xDpi) == 0 && Float.compare(that.yDpi, yDpi) == 0 && Float.compare(that.refreshRate, refreshRate) == 0 + && Float.compare(that.vsyncRate, vsyncRate) == 0 && appVsyncOffsetNanos == that.appVsyncOffsetNanos && presentationDeadlineNanos == that.presentationDeadlineNanos && Arrays.equals(supportedHdrTypes, that.supportedHdrTypes) @@ -1836,8 +1843,9 @@ public final class SurfaceControl implements Parcelable { @Override public int hashCode() { - return Objects.hash(id, width, height, xDpi, yDpi, refreshRate, appVsyncOffsetNanos, - presentationDeadlineNanos, group, Arrays.hashCode(supportedHdrTypes)); + return Objects.hash(id, width, height, xDpi, yDpi, refreshRate, vsyncRate, + appVsyncOffsetNanos, presentationDeadlineNanos, group, + Arrays.hashCode(supportedHdrTypes)); } } @@ -3701,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. * @@ -3709,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; } @@ -4258,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. @@ -4292,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/View.java b/core/java/android/view/View.java index cfde4003da9b..16318e0a3f49 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -12161,12 +12161,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * a precision touch gesture in a small area in either the X or Y dimension, such as * an edge swipe or dragging a <code>SeekBar</code> thumb.</p> * - * <p>On Wear OS, these rects control where system-level swipe-to-dismiss gesture can start. If - * the attribute {@code android:windowSwipeToDismiss} has been set to {@code false}, the system - * will create an exclusion rect with size equal to the window frame size. In order words, the - * system swipe-to-dismiss will not apply, and the app must handle gestural input within itself. - * </p> - * * <p>Note: the system will put a limit of <code>200dp</code> on the vertical extent of the * exclusions it takes into account. The limit does not apply while the navigation * bar is {@link #SYSTEM_UI_FLAG_IMMERSIVE_STICKY stickily} hidden, nor to the diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index aa47f077226a..b5648cc90dcd 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1730,7 +1730,7 @@ public final class ViewRootImpl implements ViewParent, attrs.getTitle().toString()); mAttachInfo.mThreadedRenderer = renderer; renderer.setSurfaceControl(mSurfaceControl, mBlastBufferQueue); - updateColorModeIfNeeded(attrs.getColorMode()); + updateColorModeIfNeeded(attrs.getColorMode(), attrs.getDesiredHdrHeadroom()); updateRenderHdrSdrRatio(); updateForceDarkMode(); mAttachInfo.mHardwareAccelerated = true; @@ -2232,9 +2232,7 @@ public final class ViewRootImpl implements ViewParent, mStopped = stopped; final ThreadedRenderer renderer = mAttachInfo.mThreadedRenderer; if (renderer != null) { - if (DEBUG_DRAW) { - Log.d(mTag, "WindowStopped on " + getTitle() + " set to " + mStopped); - } + if (DEBUG_DRAW) Log.d(mTag, "WindowStopped on " + getTitle() + " set to " + mStopped); renderer.setStopped(mStopped); } if (!mStopped) { @@ -3351,7 +3349,7 @@ public final class ViewRootImpl implements ViewParent, } final boolean alwaysConsumeSystemBarsChanged = mPendingAlwaysConsumeSystemBars != mAttachInfo.mAlwaysConsumeSystemBars; - updateColorModeIfNeeded(lp.getColorMode()); + updateColorModeIfNeeded(lp.getColorMode(), lp.getDesiredHdrHeadroom()); surfaceCreated = !hadSurface && mSurface.isValid(); surfaceDestroyed = hadSurface && !mSurface.isValid(); @@ -3879,8 +3877,8 @@ public final class ViewRootImpl implements ViewParent, mPendingTransitions.clear(); } - handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mPendingTransaction, - "view not visible"); + handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mHasPendingTransactions, + mPendingTransaction, "view not visible"); } else if (cancelAndRedraw) { mLastPerformTraversalsSkipDrawReason = cancelDueToPreDrawListener ? "predraw_" + mAttachInfo.mTreeObserver.getLastDispatchOnPreDrawCanceledReason() @@ -3895,8 +3893,8 @@ public final class ViewRootImpl implements ViewParent, mPendingTransitions.clear(); } if (!performDraw(mActiveSurfaceSyncGroup)) { - handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mPendingTransaction, - mLastPerformDrawSkippedReason); + handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mHasPendingTransactions, + mPendingTransaction, mLastPerformDrawSkippedReason); } } @@ -4774,8 +4772,8 @@ public final class ViewRootImpl implements ViewParent, if (mSurfaceHolder != null && mSurface.isValid()) { usingAsyncReport = true; SurfaceCallbackHelper sch = new SurfaceCallbackHelper(() -> { - handleSyncRequestWhenNoAsyncDraw(surfaceSyncGroup, pendingTransaction, - "SurfaceHolder"); + handleSyncRequestWhenNoAsyncDraw(surfaceSyncGroup, pendingTransaction != null, + pendingTransaction, "SurfaceHolder"); }); SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks(); @@ -4789,8 +4787,8 @@ public final class ViewRootImpl implements ViewParent, } if (!usingAsyncReport) { - handleSyncRequestWhenNoAsyncDraw(surfaceSyncGroup, pendingTransaction, - "no async report"); + handleSyncRequestWhenNoAsyncDraw(surfaceSyncGroup, pendingTransaction != null, + pendingTransaction, "no async report"); } if (mPerformContentCapture) { @@ -4800,13 +4798,14 @@ public final class ViewRootImpl implements ViewParent, } private void handleSyncRequestWhenNoAsyncDraw(SurfaceSyncGroup surfaceSyncGroup, - @Nullable Transaction pendingTransaction, String logReason) { + boolean hasPendingTransaction, @Nullable Transaction pendingTransaction, + String logReason) { if (surfaceSyncGroup != null) { - if (pendingTransaction != null) { + if (hasPendingTransaction && pendingTransaction != null) { surfaceSyncGroup.addTransaction(pendingTransaction); } surfaceSyncGroup.markSyncReady(); - } else if (pendingTransaction != null) { + } else if (hasPendingTransaction && pendingTransaction != null) { Trace.instant(Trace.TRACE_TAG_VIEW, "Transaction not synced due to " + logReason + "-" + mTag); if (DEBUG_BLAST) { @@ -5653,7 +5652,8 @@ public final class ViewRootImpl implements ViewParent, mUpdateHdrSdrRatioInfo = true; } - private void updateColorModeIfNeeded(@ActivityInfo.ColorMode int colorMode) { + private void updateColorModeIfNeeded(@ActivityInfo.ColorMode int colorMode, + float desiredRatio) { if (mAttachInfo.mThreadedRenderer == null) { return; } @@ -5667,7 +5667,10 @@ public final class ViewRootImpl implements ViewParent, && !getConfiguration().isScreenWideColorGamut()) { colorMode = ActivityInfo.COLOR_MODE_DEFAULT; } - float desiredRatio = mAttachInfo.mThreadedRenderer.setColorMode(colorMode); + float automaticRatio = mAttachInfo.mThreadedRenderer.setColorMode(colorMode); + if (desiredRatio == 0 || desiredRatio > automaticRatio) { + desiredRatio = automaticRatio; + } if (desiredRatio != mDesiredHdrSdrRatio) { mDesiredHdrSdrRatio = desiredRatio; updateRenderHdrSdrRatio(); @@ -8606,10 +8609,6 @@ public final class ViewRootImpl implements ViewParent, mLastLayoutFrame.set(frame); } - if (mOnBackInvokedDispatcher.isSystemGestureExclusionNeeded()) { - setRootSystemGestureExclusionRects(List.of(frame)); - } - final WindowConfiguration winConfig = getCompatWindowConfiguration(); mPendingBackDropFrame.set(mPendingDragResizing && !winConfig.useWindowFrameForBackdrop() ? winConfig.getMaxBounds() @@ -9054,8 +9053,8 @@ public final class ViewRootImpl implements ViewParent, mAdded = false; AnimationHandler.removeRequestor(this); } - handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mPendingTransaction, - "shutting down VRI"); + handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mHasPendingTransactions, + mPendingTransaction, "shutting down VRI"); WindowManagerGlobal.getInstance().doRemoveView(this); } @@ -10548,6 +10547,8 @@ public final class ViewRootImpl implements ViewParent, MergedConfiguration mergedConfiguration, InsetsState insetsState, boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int syncSeqId, boolean dragResizing) { + // Although this is a AIDL method, it will only be triggered in local process through + // either WindowStateResizeItem or WindowlessWindowManager. final ViewRootImpl viewAncestor = mViewAncestor.get(); if (viewAncestor != null) { viewAncestor.dispatchResized(frames, reportDraw, mergedConfiguration, insetsState, diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index 2f04b0c695da..87537fbc9961 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -24,6 +24,8 @@ import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTE import android.annotation.ColorInt; import android.annotation.DrawableRes; +import android.annotation.FlaggedApi; +import android.annotation.FloatRange; import android.annotation.IdRes; import android.annotation.LayoutRes; import android.annotation.NonNull; @@ -1330,6 +1332,47 @@ public abstract class Window { } /** + * <p>Sets the desired about of HDR headroom to be used when rendering as a ratio of + * targetHdrPeakBrightnessInNits / targetSdrWhitePointInNits. Only applies when + * {@link #setColorMode(int)} is {@link ActivityInfo#COLOR_MODE_HDR}</p> + * + * <p>By default the system will choose an amount of HDR headroom that is appropriate + * for the underlying device capabilities & bit-depth of the panel. However, for some types + * of content this can end up being more headroom than necessary or desired. An example + * would be a messaging app or gallery thumbnail view where some amount of HDR pop is desired + * without overly influencing the perceived brightness of the majority SDR content. This can + * also be used to animate in/out of an HDR range for smoother transitions.</p> + * + * <p>Note: The actual amount of HDR headroom that will be given is subject to a variety + * of factors such as ambient conditions, display capabilities, or bit-depth limitations. + * See {@link Display#getHdrSdrRatio()} for more information as well as how to query the + * current value.</p> + * + * @param desiredHeadroom The amount of HDR headroom that is desired. Must be >= 1.0 (no HDR) + * and <= 10,000.0. Passing 0.0 will reset to the default, automatically + * chosen value. + * @see #getDesiredHdrHeadroom() + * @see Display#getHdrSdrRatio() + */ + @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_LIMITED_HDR) + public void setDesiredHdrHeadroom( + @FloatRange(from = 0.0f, to = 10000.0) float desiredHeadroom) { + final WindowManager.LayoutParams attrs = getAttributes(); + attrs.setDesiredHdrHeadroom(desiredHeadroom); + dispatchWindowAttributesChanged(attrs); + } + + /** + * Get the desired amount of HDR headroom as set by {@link #setDesiredHdrHeadroom(float)} + * @return The amount of HDR headroom set, or 0 for automatic/default behavior. + * @see #setDesiredHdrHeadroom(float) + */ + @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_LIMITED_HDR) + public float getDesiredHdrHeadroom() { + return getAttributes().getDesiredHdrHeadroom(); + } + + /** * If {@code isPreferred} is true, this method requests that the connected display does minimal * post processing when this window is visible on the screen. Otherwise, it requests that the * display switches back to standard image processing. diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 2f4bea0270c1..a3b93b433dda 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -82,6 +82,8 @@ import static android.view.WindowLayoutParamsProto.Y; import android.Manifest.permission; import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; +import android.annotation.FloatRange; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; @@ -1683,7 +1685,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. @@ -1691,7 +1693,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) { @@ -3113,7 +3115,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 @@ -4314,6 +4316,9 @@ public interface WindowManager extends ViewManager { @ActivityInfo.ColorMode private int mColorMode = COLOR_MODE_DEFAULT; + /** @hide */ + private float mDesiredHdrHeadroom = 0; + /** * Carries the requests about {@link WindowInsetsController.Appearance} and * {@link WindowInsetsController.Behavior} to the system windows which can produce insets. @@ -4526,7 +4531,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) { @@ -4541,7 +4546,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; @@ -4716,6 +4721,39 @@ public interface WindowManager extends ViewManager { } /** + * <p>Sets the desired about of HDR headroom to be used when rendering as a ratio of + * targetHdrPeakBrightnessInNits / targetSdrWhitePointInNits. Only applies when + * {@link #setColorMode(int)} is {@link ActivityInfo#COLOR_MODE_HDR}</p> + * + * @see Window#setDesiredHdrHeadroom(float) + * @param desiredHeadroom Desired amount of HDR headroom. Must be in the range of 1.0 (SDR) + * to 10,000.0, or 0.0 to reset to default. + */ + @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_LIMITED_HDR) + public void setDesiredHdrHeadroom( + @FloatRange(from = 0.0f, to = 10000.0f) float desiredHeadroom) { + if (!Float.isFinite(desiredHeadroom)) { + throw new IllegalArgumentException("desiredHeadroom must be finite: " + + desiredHeadroom); + } + if (desiredHeadroom != 0 && (desiredHeadroom < 1.0f || desiredHeadroom > 10000.0f)) { + throw new IllegalArgumentException( + "desiredHeadroom must be 0.0 or in the range [1.0, 10000.0f], received: " + + desiredHeadroom); + } + mDesiredHdrHeadroom = desiredHeadroom; + } + + /** + * Get the desired amount of HDR headroom as set by {@link #setDesiredHdrHeadroom(float)} + * @return The amount of HDR headroom set, or 0 for automatic/default behavior. + */ + @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_LIMITED_HDR) + public float getDesiredHdrHeadroom() { + return mDesiredHdrHeadroom; + } + + /** * <p> * Blurs the screen behind the window. The effect is similar to that of {@link #dimAmount}, * but instead of dimmed, the content behind the window will be blurred (or combined with @@ -4865,6 +4903,7 @@ public interface WindowManager extends ViewManager { checkNonRecursiveParams(); out.writeTypedArray(paramsForRotation, 0 /* parcelableFlags */); out.writeInt(mDisplayFlags); + out.writeFloat(mDesiredHdrHeadroom); } public static final @android.annotation.NonNull Parcelable.Creator<LayoutParams> CREATOR @@ -4936,6 +4975,7 @@ public interface WindowManager extends ViewManager { forciblyShownTypes = in.readInt(); paramsForRotation = in.createTypedArray(LayoutParams.CREATOR); mDisplayFlags = in.readInt(); + mDesiredHdrHeadroom = in.readFloat(); } @SuppressWarnings({"PointlessBitwiseExpression"}) @@ -5196,6 +5236,11 @@ public interface WindowManager extends ViewManager { changes |= COLOR_MODE_CHANGED; } + if (mDesiredHdrHeadroom != o.mDesiredHdrHeadroom) { + mDesiredHdrHeadroom = o.mDesiredHdrHeadroom; + changes |= COLOR_MODE_CHANGED; + } + if (preferMinimalPostProcessing != o.preferMinimalPostProcessing) { preferMinimalPostProcessing = o.preferMinimalPostProcessing; changes |= MINIMAL_POST_PROCESSING_PREFERENCE_CHANGED; @@ -5423,6 +5468,9 @@ public interface WindowManager extends ViewManager { if (mColorMode != COLOR_MODE_DEFAULT) { sb.append(" colorMode=").append(ActivityInfo.colorModeToString(mColorMode)); } + if (mDesiredHdrHeadroom != 0) { + sb.append(" desiredHdrHeadroom=").append(mDesiredHdrHeadroom); + } if (preferMinimalPostProcessing) { sb.append(" preferMinimalPostProcessing="); sb.append(preferMinimalPostProcessing); @@ -5821,6 +5869,7 @@ public interface WindowManager extends ViewManager { * * @hide */ + @FlaggedApi("REPLACE_CONTENT_WITH_MIRROR") @TestApi @RequiresPermission(permission.ACCESS_SURFACE_FLINGER) default boolean replaceContentOnDisplayWithMirror(int displayId, @NonNull Window window) { @@ -5836,6 +5885,7 @@ public interface WindowManager extends ViewManager { * * @hide */ + @FlaggedApi("REPLACE_CONTENT_WITH_MIRROR") @TestApi @RequiresPermission(permission.ACCESS_SURFACE_FLINGER) default boolean replaceContentOnDisplayWithSc(int displayId, @NonNull SurfaceControl sc) { 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/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/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig index e31ad82d3f55..85dadd4a061d 100644 --- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig +++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig @@ -5,4 +5,11 @@ flag { name: "force_invert_color" description: "Enable force force-dark for smart inversion and dark theme everywhere" bug: "282821643" -}
\ No newline at end of file +} + +flag { + namespace: "accessibility" + name: "allow_shortcut_chooser_on_lockscreen" + description: "Allows the a11y shortcut disambig dialog to appear on the lockscreen" + bug: "303871725" +} diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java index a07b62fe2890..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 . * @@ -108,11 +108,14 @@ public class AnimationUtils { * @hide */ @TestApi + @FlaggedApi(FLAG_EXPECTED_PRESENTATION_TIME_API) public static void lockAnimationClock(long vsyncMillis, long expectedPresentationTimeNanos) { AnimationState state = sAnimationState.get(); state.animationClockLocked = true; state.currentVsyncTimeMillis = vsyncMillis; - state.mExpectedPresentationTimeNanos = expectedPresentationTimeNanos; + if (!expectedPresentationTimeApi()) { + state.mExpectedPresentationTimeNanos = expectedPresentationTimeNanos; + } } /** diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 34e4c37de1b5..a40ff643379a 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -16,7 +16,6 @@ package android.view.autofill; -import static android.Manifest.permission.PROVIDE_OWN_AUTOFILL_SUGGESTIONS; import static android.service.autofill.FillRequest.FLAG_IME_SHOWING; import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST; import static android.service.autofill.FillRequest.FLAG_PASSWORD_INPUT_TYPE; @@ -31,12 +30,10 @@ import static android.view.autofill.Helper.sVerbose; import static android.view.autofill.Helper.toList; import android.accessibilityservice.AccessibilityServiceInfo; -import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresFeature; -import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; @@ -53,19 +50,15 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.graphics.Rect; import android.metrics.LogMaker; -import android.os.Binder; import android.os.Build; import android.os.Bundle; -import android.os.CancellationSignal; import android.os.Handler; import android.os.IBinder; -import android.os.ICancellationSignal; import android.os.Looper; import android.os.Parcelable; import android.os.RemoteException; import android.os.SystemClock; import android.service.autofill.AutofillService; -import android.service.autofill.FillCallback; import android.service.autofill.FillEventHistory; import android.service.autofill.Flags; import android.service.autofill.IFillCallback; @@ -89,7 +82,6 @@ import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeProvider; import android.view.accessibility.AccessibilityWindowInfo; -import android.view.inputmethod.InlineSuggestionsRequest; import android.view.inputmethod.InputMethodManager; import android.widget.CheckBox; import android.widget.DatePicker; @@ -119,7 +111,6 @@ import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Set; -import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; import sun.misc.Cleaner; @@ -189,12 +180,6 @@ import sun.misc.Cleaner; * shows an autofill save UI if the value of savable views have changed. If the user selects the * option to Save, the current value of the views is then sent to the autofill service. * - * <p>There is another choice for the application to provide it's datasets to the Autofill framework - * by setting an {@link AutofillRequestCallback} through - * {@link #setAutofillRequestCallback(Executor, AutofillRequestCallback)}. The application can use - * its callback instead of the default {@link AutofillService}. See - * {@link AutofillRequestCallback} for more details. - * * <h3 id="additional-notes">Additional notes</h3> * * <p>It is safe to call <code>AutofillManager</code> methods from any thread. @@ -334,7 +319,6 @@ public final class AutofillManager { /** @hide */ public static final int FLAG_ADD_CLIENT_DEBUG = 0x2; /** @hide */ public static final int FLAG_ADD_CLIENT_VERBOSE = 0x4; /** @hide */ public static final int FLAG_ADD_CLIENT_ENABLED_FOR_AUGMENTED_AUTOFILL_ONLY = 0x8; - /** @hide */ public static final int FLAG_ENABLED_CLIENT_SUGGESTIONS = 0x20; // NOTE: flag below is used by the session start receiver only, hence it can have values above /** @hide */ public static final int RECEIVER_FLAG_SESSION_FOR_AUGMENTED_AUTOFILL_ONLY = 0x1; @@ -676,11 +660,6 @@ public final class AutofillManager { @GuardedBy("mLock") private boolean mEnabledForAugmentedAutofillOnly; - @GuardedBy("mLock") - @Nullable private AutofillRequestCallback mAutofillRequestCallback; - @GuardedBy("mLock") - @Nullable private Executor mRequestCallbackExecutor; - private boolean mScreenHasCredmanField; /** @@ -2434,38 +2413,6 @@ public final class AutofillManager { return new AutofillId(parent.getAutofillViewId(), virtualId); } - /** - * Sets the client's suggestions callback for autofill. - * - * @see AutofillRequestCallback - * - * @param executor specifies the thread upon which the callbacks will be invoked. - * @param callback which handles autofill request to provide client's suggestions. - */ - @RequiresPermission(PROVIDE_OWN_AUTOFILL_SUGGESTIONS) - public void setAutofillRequestCallback(@NonNull @CallbackExecutor Executor executor, - @NonNull AutofillRequestCallback callback) { - if (mContext.checkSelfPermission(PROVIDE_OWN_AUTOFILL_SUGGESTIONS) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Requires PROVIDE_OWN_AUTOFILL_SUGGESTIONS permission!"); - } - - synchronized (mLock) { - mRequestCallbackExecutor = executor; - mAutofillRequestCallback = callback; - } - } - - /** - * clears the client's suggestions callback for autofill. - */ - public void clearAutofillRequestCallback() { - synchronized (mLock) { - mRequestCallbackExecutor = null; - mAutofillRequestCallback = null; - } - } - @GuardedBy("mLock") private void startSessionLocked(@NonNull AutofillId id, @NonNull Rect bounds, @NonNull AutofillValue value, int flags) { @@ -2526,13 +2473,6 @@ public final class AutofillManager { } } - if (mAutofillRequestCallback != null) { - if (sDebug) { - Log.d(TAG, "startSession with the client suggestions provider"); - } - flags |= FLAG_ENABLED_CLIENT_SUGGESTIONS; - } - mService.startSession(client.autofillClientGetActivityToken(), mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(), mCallback != null, flags, clientActivity, @@ -2890,28 +2830,6 @@ public final class AutofillManager { } } - private void onFillRequest(InlineSuggestionsRequest request, - CancellationSignal cancellationSignal, FillCallback callback) { - final AutofillRequestCallback autofillRequestCallback; - final Executor executor; - synchronized (mLock) { - autofillRequestCallback = mAutofillRequestCallback; - executor = mRequestCallbackExecutor; - } - if (autofillRequestCallback != null && executor != null) { - final long ident = Binder.clearCallingIdentity(); - try { - executor.execute(() -> - autofillRequestCallback.onFillRequest( - request, cancellationSignal, callback)); - } finally { - Binder.restoreCallingIdentity(ident); - } - } else { - callback.onSuccess(null); - } - } - /** @hide */ public static final int SET_STATE_FLAG_ENABLED = 0x01; /** @hide */ @@ -4495,23 +4413,6 @@ public final class AutofillManager { } @Override - public void requestFillFromClient(int id, InlineSuggestionsRequest request, - IFillCallback callback) { - final AutofillManager afm = mAfm.get(); - if (afm != null) { - ICancellationSignal transport = CancellationSignal.createTransport(); - try { - callback.onCancellable(transport); - } catch (RemoteException e) { - Slog.w(TAG, "Error requesting a cancellation", e); - } - - afm.onFillRequest(request, CancellationSignal.fromTransport(transport), - new FillCallback(callback, id)); - } - } - - @Override public void notifyFillDialogTriggerIds(List<AutofillId> ids) { final AutofillManager afm = mAfm.get(); if (afm != null) { diff --git a/core/java/android/view/autofill/AutofillRequestCallback.java b/core/java/android/view/autofill/AutofillRequestCallback.java deleted file mode 100644 index e632a5849471..000000000000 --- a/core/java/android/view/autofill/AutofillRequestCallback.java +++ /dev/null @@ -1,72 +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 android.view.autofill; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.os.CancellationSignal; -import android.service.autofill.FillCallback; -import android.view.inputmethod.InlineSuggestionsRequest; - -/** - * <p>This class is used to provide some input suggestions to the Autofill framework. - * - * <P>When the user is requested to input something, Autofill will try to query input suggestions - * for the user choosing. If the application want to provide some internal input suggestions, - * implements this callback and register via - * {@link AutofillManager#setAutofillRequestCallback(java.util.concurrent.Executor, - * AutofillRequestCallback)}. Autofill will callback the - * {@link #onFillRequest(InlineSuggestionsRequest, CancellationSignal, FillCallback)} to request - * input suggestions. - * - * <P>To make sure the callback to take effect, must register before the autofill session starts. - * If the autofill session is started, calls {@link AutofillManager#cancel()} to finish current - * session, and then the callback will be used at the next restarted session. - * - * <P>To create a {@link android.service.autofill.FillResponse}, application should fetch - * {@link AutofillId}s from its view structure. Below is an example: - * <pre class="prettyprint"> - * AutofillId usernameId = findViewById(R.id.username).getAutofillId(); - * AutofillId passwordId = findViewById(R.id.password).getAutofillId(); - * </pre> - * To learn more about creating a {@link android.service.autofill.FillResponse}, read - * <a href="/guide/topics/text/autofill-services#fill">Fill out client views</a>. - * - * <P>To fallback to the default {@link android.service.autofill.AutofillService}, just respond - * a null of the {@link android.service.autofill.FillResponse}. And then Autofill will do a fill - * request with the default {@link android.service.autofill.AutofillService}. Or clear the callback - * from {@link AutofillManager} via {@link AutofillManager#clearAutofillRequestCallback()}. If the - * client would like to keep no suggestions for the field, respond with an empty - * {@link android.service.autofill.FillResponse} which has no dataset. - * - * <P>IMPORTANT: This should not be used for displaying anything other than input suggestions, or - * the keyboard may choose to block your app from the inline strip. - */ -public interface AutofillRequestCallback { - /** - * Called by the Android system to decide if a screen can be autofilled by the callback. - * - * @param inlineSuggestionsRequest the {@link InlineSuggestionsRequest request} to handle if - * currently inline suggestions are supported and can be displayed. - * @param cancellationSignal signal for observing cancellation requests. The system will use - * this to notify you that the fill result is no longer needed and you should stop - * handling this fill request in order to save resources. - * @param callback object used to notify the result of the request. - */ - void onFillRequest(@Nullable InlineSuggestionsRequest inlineSuggestionsRequest, - @NonNull CancellationSignal cancellationSignal, @NonNull FillCallback callback); -} diff --git a/core/java/android/view/autofill/IAutoFillManagerClient.aidl b/core/java/android/view/autofill/IAutoFillManagerClient.aidl index 6e13097fe319..917a974f992d 100644 --- a/core/java/android/view/autofill/IAutoFillManagerClient.aidl +++ b/core/java/android/view/autofill/IAutoFillManagerClient.aidl @@ -24,11 +24,9 @@ import android.content.Intent; import android.content.IntentSender; import android.graphics.Rect; import android.os.IBinder; -import android.service.autofill.IFillCallback; import android.view.autofill.AutofillId; import android.view.autofill.AutofillValue; import android.view.autofill.IAutofillWindowPresenter; -import android.view.inputmethod.InlineSuggestionsRequest; import android.view.KeyEvent; import com.android.internal.os.IResultReceiver; @@ -149,12 +147,6 @@ oneway interface IAutoFillManagerClient { void requestShowSoftInput(in AutofillId id); /** - * Requests to determine if a screen can be autofilled by the client app. - */ - void requestFillFromClient(int id, in InlineSuggestionsRequest request, - in IFillCallback callback); - - /** * Notifies autofill ids that require to show the fill dialog. */ void notifyFillDialogTriggerIds(in List<AutofillId> ids); diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java index 2c7d326587c7..42b3e38b544f 100644 --- a/core/java/android/view/contentcapture/ContentCaptureManager.java +++ b/core/java/android/view/contentcapture/ContentCaptureManager.java @@ -60,7 +60,9 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -377,6 +379,30 @@ public final class ContentCaptureManager { public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_BUFFER_SIZE = "content_protection_buffer_size"; + /** + * Sets the config for content protection required groups. + * + * @hide + */ + public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG = + "content_protection_required_groups_config"; + + /** + * Sets the config for content protection optional groups. + * + * @hide + */ + public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG = + "content_protection_optional_groups_config"; + + /** + * Sets the threshold for content protection optional groups. + * + * @hide + */ + public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD = + "content_protection_optional_groups_threshold"; + /** @hide */ @TestApi public static final int LOGGING_LEVEL_OFF = 0; @@ -417,6 +443,18 @@ public final class ContentCaptureManager { public static final int DEFAULT_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE = 5000; /** @hide */ public static final int DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE = 150; + /** @hide */ + public static final List<List<String>> DEFAULT_CONTENT_PROTECTION_REQUIRED_GROUPS = + Collections.emptyList(); + /** @hide */ + public static final String DEFAULT_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG = ""; + /** @hide */ + public static final List<List<String>> DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS = + Collections.emptyList(); + /** @hide */ + public static final String DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG = ""; + /** @hide */ + public static final int DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD = 0; private final Object mLock = new Object(); 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/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/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/InlineSuggestionsRequest.java b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java index 70279cc8e845..c83dfe8e68f4 100644 --- a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java +++ b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java @@ -111,22 +111,6 @@ public final class InlineSuggestionsRequest implements Parcelable { private @Nullable InlinePresentationSpec mInlineTooltipPresentationSpec; /** - * Whether the IME supports inline suggestions from the default Autofill service that - * provides the input view. - * - * Note: The default value is {@code true}. - */ - private boolean mServiceSupported; - - /** - * Whether the IME supports inline suggestions from the application that provides the - * input view. - * - * Note: The default value is {@code true}. - */ - private boolean mClientSupported; - - /** * @hide * @see {@link #mHostInputToken}. */ @@ -220,14 +204,6 @@ public final class InlineSuggestionsRequest implements Parcelable { return Bundle.EMPTY; } - private static boolean defaultServiceSupported() { - return true; - } - - private static boolean defaultClientSupported() { - return true; - } - /** @hide */ abstract static class BaseBuilder { abstract Builder setInlinePresentationSpecs( @@ -240,25 +216,13 @@ public final class InlineSuggestionsRequest implements Parcelable { abstract Builder setHostDisplayId(int value); } - /** @hide */ - public boolean isServiceSupported() { - return mServiceSupported; - } - - /** @hide */ - public boolean isClientSupported() { - return mClientSupported; - } - - - - // Code below generated by codegen v1.0.22. + // Code below generated by codegen v1.0.23. // // DO NOT MODIFY! // CHECKSTYLE:OFF Generated code // // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java + // $ codegen $ANDROID_BUILD_TOP/./frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java // // To exclude the generated code from IntelliJ auto-formatting enable (one-time): // Settings > Editor > Code Style > Formatter Control @@ -274,9 +238,7 @@ public final class InlineSuggestionsRequest implements Parcelable { @NonNull Bundle extras, @Nullable IBinder hostInputToken, int hostDisplayId, - @Nullable InlinePresentationSpec inlineTooltipPresentationSpec, - boolean serviceSupported, - boolean clientSupported) { + @Nullable InlinePresentationSpec inlineTooltipPresentationSpec) { this.mMaxSuggestionCount = maxSuggestionCount; this.mInlinePresentationSpecs = inlinePresentationSpecs; com.android.internal.util.AnnotationValidations.validate( @@ -293,8 +255,6 @@ public final class InlineSuggestionsRequest implements Parcelable { this.mHostInputToken = hostInputToken; this.mHostDisplayId = hostDisplayId; this.mInlineTooltipPresentationSpec = inlineTooltipPresentationSpec; - this.mServiceSupported = serviceSupported; - this.mClientSupported = clientSupported; onConstructed(); } @@ -378,9 +338,7 @@ public final class InlineSuggestionsRequest implements Parcelable { } /** - * The {@link InlinePresentationSpec} for the inline suggestion tooltip in the response. - * - * @see android.service.autofill.InlinePresentation#createTooltipPresentation(Slice, InlinePresentationSpec) + * Specifies the UI specification for the inline suggestion tooltip in the response. */ @DataClass.Generated.Member public @Nullable InlinePresentationSpec getInlineTooltipPresentationSpec() { @@ -401,9 +359,7 @@ public final class InlineSuggestionsRequest implements Parcelable { "extras = " + mExtras + ", " + "hostInputToken = " + mHostInputToken + ", " + "hostDisplayId = " + mHostDisplayId + ", " + - "inlineTooltipPresentationSpec = " + mInlineTooltipPresentationSpec + ", " + - "serviceSupported = " + mServiceSupported + ", " + - "clientSupported = " + mClientSupported + + "inlineTooltipPresentationSpec = " + mInlineTooltipPresentationSpec + " }"; } @@ -427,9 +383,7 @@ public final class InlineSuggestionsRequest implements Parcelable { && extrasEquals(that.mExtras) && java.util.Objects.equals(mHostInputToken, that.mHostInputToken) && mHostDisplayId == that.mHostDisplayId - && java.util.Objects.equals(mInlineTooltipPresentationSpec, that.mInlineTooltipPresentationSpec) - && mServiceSupported == that.mServiceSupported - && mClientSupported == that.mClientSupported; + && java.util.Objects.equals(mInlineTooltipPresentationSpec, that.mInlineTooltipPresentationSpec); } @Override @@ -447,8 +401,6 @@ public final class InlineSuggestionsRequest implements Parcelable { _hash = 31 * _hash + java.util.Objects.hashCode(mHostInputToken); _hash = 31 * _hash + mHostDisplayId; _hash = 31 * _hash + java.util.Objects.hashCode(mInlineTooltipPresentationSpec); - _hash = 31 * _hash + Boolean.hashCode(mServiceSupported); - _hash = 31 * _hash + Boolean.hashCode(mClientSupported); return _hash; } @@ -459,8 +411,6 @@ public final class InlineSuggestionsRequest implements Parcelable { // void parcelFieldName(Parcel dest, int flags) { ... } int flg = 0; - if (mServiceSupported) flg |= 0x100; - if (mClientSupported) flg |= 0x200; if (mHostInputToken != null) flg |= 0x20; if (mInlineTooltipPresentationSpec != null) flg |= 0x80; dest.writeInt(flg); @@ -486,11 +436,9 @@ public final class InlineSuggestionsRequest implements Parcelable { // static FieldType unparcelFieldName(Parcel in) { ... } int flg = in.readInt(); - boolean serviceSupported = (flg & 0x100) != 0; - boolean clientSupported = (flg & 0x200) != 0; int maxSuggestionCount = in.readInt(); List<InlinePresentationSpec> inlinePresentationSpecs = new ArrayList<>(); - in.readParcelableList(inlinePresentationSpecs, InlinePresentationSpec.class.getClassLoader(), android.widget.inline.InlinePresentationSpec.class); + in.readParcelableList(inlinePresentationSpecs, InlinePresentationSpec.class.getClassLoader()); String hostPackageName = in.readString(); LocaleList supportedLocales = (LocaleList) in.readTypedObject(LocaleList.CREATOR); Bundle extras = in.readBundle(); @@ -514,8 +462,6 @@ public final class InlineSuggestionsRequest implements Parcelable { this.mHostInputToken = hostInputToken; this.mHostDisplayId = hostDisplayId; this.mInlineTooltipPresentationSpec = inlineTooltipPresentationSpec; - this.mServiceSupported = serviceSupported; - this.mClientSupported = clientSupported; onConstructed(); } @@ -549,8 +495,6 @@ public final class InlineSuggestionsRequest implements Parcelable { private @Nullable IBinder mHostInputToken; private int mHostDisplayId; private @Nullable InlinePresentationSpec mInlineTooltipPresentationSpec; - private boolean mServiceSupported; - private boolean mClientSupported; private long mBuilderFieldsSet = 0L; @@ -683,9 +627,7 @@ public final class InlineSuggestionsRequest implements Parcelable { } /** - * The {@link InlinePresentationSpec} for the inline suggestion tooltip in the response. - * - * @see android.service.autofill.InlinePresentation#createTooltipPresentation(Slice, InlinePresentationSpec)s + * Specifies the UI specification for the inline suggestion tooltip in the response. */ @DataClass.Generated.Member public @NonNull Builder setInlineTooltipPresentationSpec(@NonNull InlinePresentationSpec value) { @@ -695,38 +637,10 @@ public final class InlineSuggestionsRequest implements Parcelable { return this; } - /** - * Whether the IME supports inline suggestions from the default Autofill service that - * provides the input view. - * - * Note: The default value is {@code true}. - */ - @DataClass.Generated.Member - public @NonNull Builder setServiceSupported(boolean value) { - checkNotUsed(); - mBuilderFieldsSet |= 0x100; - mServiceSupported = value; - return this; - } - - /** - * Whether the IME supports inline suggestions from the application that provides the - * input view. - * - * Note: The default value is {@code true}. - */ - @DataClass.Generated.Member - public @NonNull Builder setClientSupported(boolean value) { - checkNotUsed(); - mBuilderFieldsSet |= 0x200; - mClientSupported = value; - return this; - } - /** Builds the instance. This builder should not be touched after calling this! */ public @NonNull InlineSuggestionsRequest build() { checkNotUsed(); - mBuilderFieldsSet |= 0x400; // Mark builder used + mBuilderFieldsSet |= 0x100; // Mark builder used if ((mBuilderFieldsSet & 0x1) == 0) { mMaxSuggestionCount = defaultMaxSuggestionCount(); @@ -749,12 +663,6 @@ public final class InlineSuggestionsRequest implements Parcelable { if ((mBuilderFieldsSet & 0x80) == 0) { mInlineTooltipPresentationSpec = defaultInlineTooltipPresentationSpec(); } - if ((mBuilderFieldsSet & 0x100) == 0) { - mServiceSupported = defaultServiceSupported(); - } - if ((mBuilderFieldsSet & 0x200) == 0) { - mClientSupported = defaultClientSupported(); - } InlineSuggestionsRequest o = new InlineSuggestionsRequest( mMaxSuggestionCount, mInlinePresentationSpecs, @@ -763,14 +671,12 @@ public final class InlineSuggestionsRequest implements Parcelable { mExtras, mHostInputToken, mHostDisplayId, - mInlineTooltipPresentationSpec, - mServiceSupported, - mClientSupported); + mInlineTooltipPresentationSpec); return o; } private void checkNotUsed() { - if ((mBuilderFieldsSet & 0x400) != 0) { + if ((mBuilderFieldsSet & 0x100) != 0) { throw new IllegalStateException( "This Builder should not be reused. Use a new Builder instance instead"); } @@ -778,10 +684,10 @@ public final class InlineSuggestionsRequest implements Parcelable { } @DataClass.Generated( - time = 1615798784918L, - codegenVersion = "1.0.22", + time = 1696889841006L, + codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java", - inputSignatures = "public static final int SUGGESTION_COUNT_UNLIMITED\nprivate final int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.widget.inline.InlinePresentationSpec> mInlinePresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\nprivate @android.annotation.NonNull android.os.LocaleList mSupportedLocales\nprivate @android.annotation.NonNull android.os.Bundle mExtras\nprivate @android.annotation.Nullable android.os.IBinder mHostInputToken\nprivate int mHostDisplayId\nprivate @android.annotation.Nullable android.widget.inline.InlinePresentationSpec mInlineTooltipPresentationSpec\nprivate boolean mServiceSupported\nprivate boolean mClientSupported\nprivate static final @android.compat.annotation.ChangeId @android.compat.annotation.EnabledSince long IME_AUTOFILL_DEFAULT_SUPPORTED_LOCALES_IS_EMPTY\npublic void setHostInputToken(android.os.IBinder)\nprivate boolean extrasEquals(android.os.Bundle)\nprivate void parcelHostInputToken(android.os.Parcel,int)\nprivate @android.annotation.Nullable android.os.IBinder unparcelHostInputToken(android.os.Parcel)\npublic void setHostDisplayId(int)\nprivate void onConstructed()\npublic void filterContentTypes()\nprivate static int defaultMaxSuggestionCount()\nprivate static java.lang.String defaultHostPackageName()\nprivate static android.widget.inline.InlinePresentationSpec defaultInlineTooltipPresentationSpec()\nprivate static android.os.LocaleList defaultSupportedLocales()\nprivate static @android.annotation.Nullable android.os.IBinder defaultHostInputToken()\nprivate static @android.annotation.Nullable int defaultHostDisplayId()\nprivate static @android.annotation.NonNull android.os.Bundle defaultExtras()\nprivate static boolean defaultServiceSupported()\nprivate static boolean defaultClientSupported()\npublic boolean isServiceSupported()\npublic boolean isClientSupported()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []") + inputSignatures = "public static final int SUGGESTION_COUNT_UNLIMITED\nprivate final int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.widget.inline.InlinePresentationSpec> mInlinePresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\nprivate @android.annotation.NonNull android.os.LocaleList mSupportedLocales\nprivate @android.annotation.NonNull android.os.Bundle mExtras\nprivate @android.annotation.Nullable android.os.IBinder mHostInputToken\nprivate int mHostDisplayId\nprivate @android.annotation.Nullable android.widget.inline.InlinePresentationSpec mInlineTooltipPresentationSpec\nprivate static final @android.compat.annotation.ChangeId @android.compat.annotation.EnabledSince long IME_AUTOFILL_DEFAULT_SUPPORTED_LOCALES_IS_EMPTY\npublic void setHostInputToken(android.os.IBinder)\nprivate boolean extrasEquals(android.os.Bundle)\nprivate void parcelHostInputToken(android.os.Parcel,int)\nprivate @android.annotation.Nullable android.os.IBinder unparcelHostInputToken(android.os.Parcel)\npublic void setHostDisplayId(int)\nprivate void onConstructed()\npublic void filterContentTypes()\nprivate static int defaultMaxSuggestionCount()\nprivate static java.lang.String defaultHostPackageName()\nprivate static android.widget.inline.InlinePresentationSpec defaultInlineTooltipPresentationSpec()\nprivate static android.os.LocaleList defaultSupportedLocales()\nprivate static @android.annotation.Nullable android.os.IBinder defaultHostInputToken()\nprivate static @android.annotation.Nullable int defaultHostDisplayId()\nprivate static @android.annotation.NonNull android.os.Bundle defaultExtras()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []") @Deprecated private void __metadata() {} 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/IRemoteTransition.aidl b/core/java/android/window/IRemoteTransition.aidl index 2efb68a33889..ec8b66d8b2f2 100644 --- a/core/java/android/window/IRemoteTransition.aidl +++ b/core/java/android/window/IRemoteTransition.aidl @@ -59,4 +59,12 @@ oneway interface IRemoteTransition { void mergeAnimation(in IBinder transition, in TransitionInfo info, in SurfaceControl.Transaction t, in IBinder mergeTarget, in IRemoteTransitionFinishedCallback finishCallback); + + /** + * Called when a different handler has consumed the transition + * + * @param transition An identifier for the transition that was consumed. + * @param aborted Whether the transition is aborted or not. + */ + void onTransitionConsumed(in IBinder transition, in boolean aborted); } 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/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index d2a16a3a9212..61f340a856c4 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() { diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java index 0ee07bbb3b5c..3d4bc2f1b51c 100644 --- a/core/java/android/window/WindowOnBackInvokedDispatcher.java +++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java @@ -16,9 +16,6 @@ package android.window; -import static java.lang.annotation.RetentionPolicy.SOURCE; - -import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Activity; @@ -37,8 +34,8 @@ import android.view.IWindowSession; import androidx.annotation.VisibleForTesting; + import java.io.PrintWriter; -import java.lang.annotation.Retention; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.HashMap; @@ -60,18 +57,6 @@ import java.util.TreeMap; * @hide */ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { - @Retention(SOURCE) - @IntDef({ - BACK_CALLBACK_ENABLED, - BACK_CALLBACK_DISABLED, - BACK_CALLBACK_DISABLED_LEGACY_WINDOW_SWIPE_TO_DISMISS - }) - public @interface OnBackInvokedCallbackType {} - - public static final int BACK_CALLBACK_ENABLED = 0; - public static final int BACK_CALLBACK_DISABLED = 1; - public static final int BACK_CALLBACK_DISABLED_LEGACY_WINDOW_SWIPE_TO_DISMISS = 2; - private IWindowSession mWindowSession; private IWindow mWindow; private static final String TAG = "WindowOnBackDispatcher"; @@ -283,6 +268,13 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { } /** + * Returns false if the legacy back behavior should be used. + */ + public boolean isOnBackInvokedCallbackEnabled() { + return Checker.isOnBackInvokedCallbackEnabled(mChecker.getContext()); + } + + /** * Dump information about this WindowOnBackInvokedDispatcher * @param prefix the prefix that will be prepended to each line of the produced output * @param writer the writer that will receive the resulting text @@ -395,20 +387,6 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { } } - /** Returns false if the legacy back behavior should be used. */ - public boolean isOnBackInvokedCallbackEnabled() { - return isOnBackInvokedCallbackEnabled(mChecker.getContext()); - } - - /** - * Returns true if system gesture exclusion is needed for global gesture compatibility with - * windowSwipeToDismiss styleable. - */ - public boolean isSystemGestureExclusionNeeded() { - return Checker.getBackCallbackType(mChecker.getContext()) - == BACK_CALLBACK_DISABLED_LEGACY_WINDOW_SWIPE_TO_DISMISS; - } - /** * Returns false if the legacy back behavior should be used. * <p> @@ -416,7 +394,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { * {@link OnBackInvokedCallback}. */ public static boolean isOnBackInvokedCallbackEnabled(@NonNull Context context) { - return Checker.getBackCallbackType(context) == BACK_CALLBACK_ENABLED; + return Checker.isOnBackInvokedCallbackEnabled(context); } @Override @@ -468,29 +446,28 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { return mContext.get(); } - @OnBackInvokedCallbackType - private static int getBackCallbackType(@Nullable Context context) { + private static boolean isOnBackInvokedCallbackEnabled(@Nullable Context context) { // new back is enabled if the feature flag is enabled AND the app does not explicitly // request legacy back. boolean featureFlagEnabled = ENABLE_PREDICTIVE_BACK; if (!featureFlagEnabled) { - return BACK_CALLBACK_DISABLED; + return false; } if (ALWAYS_ENFORCE_PREDICTIVE_BACK) { - Log.i(TAG, "getBackCallbackType: always enable"); - return BACK_CALLBACK_ENABLED; + return true; } // If the context is null, return false to use legacy back. if (context == null) { Log.w(TAG, "OnBackInvokedCallback is not enabled because context is null."); - return BACK_CALLBACK_DISABLED; + return false; } boolean requestsPredictiveBack = false; // Check if the context is from an activity. + Context originalContext = context; while ((context instanceof ContextWrapper) && !(context instanceof Activity)) { context = ((ContextWrapper) context).getBaseContext(); } @@ -539,8 +516,10 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { // 3. windowSwipeToDismiss=false should be respected for apps not opted in, // which disables PB & onBackPressed caused by BackAnimController's // setTrigger(true) + // Use the original context to resolve the styled attribute so that they stay + // true to the window. TypedArray windowAttr = - context.obtainStyledAttributes( + originalContext.obtainStyledAttributes( new int[] {android.R.attr.windowSwipeToDismiss}); boolean windowSwipeToDismiss = true; if (windowAttr.getIndexCount() > 0) { @@ -552,15 +531,11 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { Log.i(TAG, "falling back to windowSwipeToDismiss: " + windowSwipeToDismiss); } - if (!windowSwipeToDismiss) { - return BACK_CALLBACK_DISABLED_LEGACY_WINDOW_SWIPE_TO_DISMISS; - } else { - return BACK_CALLBACK_ENABLED; - } + requestsPredictiveBack = windowSwipeToDismiss; } } - return requestsPredictiveBack ? BACK_CALLBACK_ENABLED : BACK_CALLBACK_DISABLED; + return requestsPredictiveBack; } } } diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig index 1b98806a0f01..ccbf4a9b3d21 100644 --- a/core/java/android/window/flags/window_surfaces.aconfig +++ b/core/java/android/window/flags/window_surfaces.aconfig @@ -9,3 +9,11 @@ flag { is_fixed_read_only: true bug: "292032926" } + +flag { + namespace: "window_surfaces" + name: "explicit_refresh_rate_hints" + description: "Performance related hints during transitions" + is_fixed_read_only: true + bug: "300019131" +} diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig index ec5d4ff16ee5..24dc6dbfede8 100644 --- a/core/java/android/window/flags/windowing_sdk.aconfig +++ b/core/java/android/window/flags/windowing_sdk.aconfig @@ -22,3 +22,10 @@ flag { description: "Whether the TaskFragment system organizer feature is enabled" bug: "284050041" } + +flag { + namespace: "windowing_sdk" + name: "window_state_resize_item_flag" + description: "Whether to dispatch window resize through ClientTransaction is enabled" + bug: "301870955" +} diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java index 5dd558a5f850..987c14c6ab51 100644 --- a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java +++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java @@ -28,15 +28,19 @@ import static com.android.internal.accessibility.util.AccessibilityUtils.isUserS import android.annotation.Nullable; import android.app.Activity; import android.app.AlertDialog; +import android.app.KeyguardManager; import android.content.Context; import android.content.DialogInterface; import android.content.res.TypedArray; import android.os.Bundle; import android.view.View; import android.view.Window; +import android.view.WindowManager; +import android.view.accessibility.Flags; import android.widget.AdapterView; import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; import java.util.List; @@ -207,6 +211,11 @@ public class AccessibilityShortcutChooserActivity extends Activity { isEditMenuMode ? this::onTargetChecked : this::onTargetSelected); } + @VisibleForTesting + public AlertDialog getMenuDialog() { + return mMenuDialog; + } + private AlertDialog createMenuDialog() { final String dialogTitle = getString(R.string.accessibility_select_shortcut_menu_title); @@ -216,12 +225,25 @@ public class AccessibilityShortcutChooserActivity extends Activity { .setAdapter(mTargetAdapter, /* listener= */ null) .setOnDismissListener(dialog -> finish()); - if (isUserSetupCompleted(this)) { + boolean allowEditing = isUserSetupCompleted(this); + boolean showWhenLocked = false; + if (Flags.allowShortcutChooserOnLockscreen()) { + final KeyguardManager keyguardManager = getSystemService(KeyguardManager.class); + if (keyguardManager != null && keyguardManager.isKeyguardLocked()) { + allowEditing = false; + showWhenLocked = true; + } + } + if (allowEditing) { final String positiveButtonText = getString(R.string.edit_accessibility_shortcut_menu_button); builder.setPositiveButton(positiveButtonText, /* listener= */ null); } - return builder.create(); + final AlertDialog dialog = builder.create(); + if (showWhenLocked) { + dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); + } + return dialog; } } diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java index 2909b6a488b8..e494346bae5c 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java @@ -414,11 +414,6 @@ public final class SystemUiDeviceConfigFlags { "dark_launch_remote_prediction_service_enabled"; /** - * (boolean) Whether to enable pinch resizing for PIP. - */ - public static final String PIP_PINCH_RESIZE = "pip_pinch_resize"; - - /** * (boolean) Whether to enable stashing for PIP. */ public static final String PIP_STASHING = "pip_stashing"; diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java index cb2d93474971..b1d22e069d9d 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java @@ -86,6 +86,28 @@ public class SystemUiSystemPropertiesFlags { public static final Flag ENABLE_ATTENTION_HELPER_REFACTOR = devFlag( "persist.debug.sysui.notification.enable_attention_helper_refactor"); + // TODO b/291899544: for released flags, use resource config values + /** Value used by polite notif. feature */ + public static final Flag NOTIF_COOLDOWN_T1 = devFlag( + "persist.debug.sysui.notification.notif_cooldown_t1", 5000); + /** Value used by polite notif. feature */ + public static final Flag NOTIF_COOLDOWN_T2 = devFlag( + "persist.debug.sysui.notification.notif_cooldown_t2", 3000); + /** Value used by polite notif. feature */ + public static final Flag NOTIF_VOLUME1 = devFlag( + "persist.debug.sysui.notification.notif_volume1", 30); + public static final Flag NOTIF_VOLUME2 = devFlag( + "persist.debug.sysui.notification.notif_volume2", 0); + /** Value used by polite notif. feature. -1 to ignore the counter */ + public static final Flag NOTIF_COOLDOWN_COUNTER_RESET = devFlag( + "persist.debug.sysui.notification.notif_cooldown_counter_reset", 10); + /** + * Value used by polite notif. feature: cooldown behavior/strategy. Valid values: rule1, + * rule2 + */ + public static final Flag NOTIF_COOLDOWN_RULE = devFlag( + "persist.debug.sysui.notification.notif_cooldown_rule", "rule1"); + /** b/301242692: Visit extra URIs used in notifications to prevent security issues. */ public static final Flag VISIT_RISKY_URIS = devFlag( "persist.sysui.notification.visit_risky_uris"); @@ -97,6 +119,10 @@ public class SystemUiSystemPropertiesFlags { public interface FlagResolver { /** Is the flag enabled? */ boolean isEnabled(Flag flag); + /** Get the flag value (integer) */ + int getIntValue(Flag flag); + /** Get the flag value (string) */ + String getStringValue(Flag flag); } /** The primary, immutable resolver returned by getResolver() */ @@ -134,6 +160,22 @@ public class SystemUiSystemPropertiesFlags { } /** + * Creates a flag that with a default integer value in debuggable builds. + */ + @VisibleForTesting + public static Flag devFlag(String name, int defaultValue) { + return new Flag(name, defaultValue, null); + } + + /** + * Creates a flag that with a default string value in debuggable builds. + */ + @VisibleForTesting + public static Flag devFlag(String name, String defaultValue) { + return new Flag(name, defaultValue, null); + } + + /** * Creates a flag that is disabled by default in debuggable builds. * It can be enabled or force-disabled by setting this flag's SystemProperty to 1 or 0. * If this flag's SystemProperty is not set, the flag can be enabled by setting the @@ -161,6 +203,8 @@ public class SystemUiSystemPropertiesFlags { public static final class Flag { public final String mSysPropKey; public final boolean mDefaultValue; + public final int mDefaultIntValue; + public final String mDefaultStringValue; @Nullable public final Flag mDebugDefault; @@ -170,6 +214,24 @@ public class SystemUiSystemPropertiesFlags { mSysPropKey = sysPropKey; mDefaultValue = defaultValue; mDebugDefault = debugDefault; + mDefaultIntValue = 0; + mDefaultStringValue = null; + } + + public Flag(@NonNull String sysPropKey, int defaultValue, @Nullable Flag debugDefault) { + mSysPropKey = sysPropKey; + mDefaultIntValue = defaultValue; + mDebugDefault = debugDefault; + mDefaultValue = false; + mDefaultStringValue = null; + } + + public Flag(@NonNull String sysPropKey, String defaultValue, @Nullable Flag debugDefault) { + mSysPropKey = sysPropKey; + mDefaultStringValue = defaultValue; + mDebugDefault = debugDefault; + mDefaultValue = false; + mDefaultIntValue = 0; } } @@ -181,6 +243,16 @@ public class SystemUiSystemPropertiesFlags { public boolean isEnabled(Flag flag) { return flag.mDefaultValue; } + + @Override + public int getIntValue(Flag flag) { + return flag.mDefaultIntValue; + } + + @Override + public String getStringValue(Flag flag) { + return flag.mDefaultStringValue; + } } /** Implementation of the interface used in debuggable builds. */ @@ -199,5 +271,23 @@ public class SystemUiSystemPropertiesFlags { public boolean getBoolean(String key, boolean defaultValue) { return SystemProperties.getBoolean(key, defaultValue); } + + /** Look up the value; overridable for tests to avoid needing to set SystemProperties */ + @VisibleForTesting + public int getIntValue(Flag flag) { + if (flag.mDebugDefault == null) { + return SystemProperties.getInt(flag.mSysPropKey, flag.mDefaultIntValue); + } + return SystemProperties.getInt(flag.mSysPropKey, getIntValue(flag.mDebugDefault)); + } + + /** Look up the value; overridable for tests to avoid needing to set SystemProperties */ + @VisibleForTesting + public String getStringValue(Flag flag) { + if (flag.mDebugDefault == null) { + return SystemProperties.get(flag.mSysPropKey, flag.mDefaultStringValue); + } + return SystemProperties.get(flag.mSysPropKey, getStringValue(flag.mDebugDefault)); + } } } 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/foldables/Android.bp b/core/java/com/android/internal/foldables/Android.bp new file mode 100644 index 000000000000..f1d06da98186 --- /dev/null +++ b/core/java/com/android/internal/foldables/Android.bp @@ -0,0 +1,7 @@ +aconfig_declarations { + name: "fold_lock_setting_flags", + package: "com.android.internal.foldables.flags", + srcs: [ + "fold_lock_setting_flags.aconfig", + ], +} diff --git a/core/java/com/android/internal/foldables/FoldLockSettingAvailabilityProvider.java b/core/java/com/android/internal/foldables/FoldLockSettingAvailabilityProvider.java index 4e3888a9e92d..a115ecff4ac3 100644 --- a/core/java/com/android/internal/foldables/FoldLockSettingAvailabilityProvider.java +++ b/core/java/com/android/internal/foldables/FoldLockSettingAvailabilityProvider.java @@ -17,16 +17,23 @@ package com.android.internal.foldables; import android.content.res.Resources; +import android.os.Build; import android.sysprop.FoldLockBehaviorProperties; +import android.util.Slog; import com.android.internal.R; +import com.android.internal.foldables.flags.Flags; + +import java.util.function.Supplier; /** * Wrapper class to access {@link FoldLockBehaviorProperties} and also assists with testing */ public class FoldLockSettingAvailabilityProvider { - boolean mFoldLockBehaviorResourceValue; + private static final String TAG = "FoldLockSettingAvailabilityProvider"; + private final boolean mFoldLockBehaviorResourceValue; + private final Supplier<Boolean> mFoldLockSettingEnabled = Flags::foldLockSettingEnabled; public FoldLockSettingAvailabilityProvider(Resources resources) { mFoldLockBehaviorResourceValue = resources.getBoolean( @@ -35,6 +42,22 @@ public class FoldLockSettingAvailabilityProvider { public boolean isFoldLockBehaviorAvailable() { return mFoldLockBehaviorResourceValue - && FoldLockBehaviorProperties.fold_lock_setting_enabled().orElse(false); + && flagOrSystemProperty(); + } + + private boolean flagOrSystemProperty() { + if ((Build.IS_ENG || Build.IS_USERDEBUG) + && FoldLockBehaviorProperties.fold_lock_setting_enabled().orElse(false)) { + return true; + } + try { + return mFoldLockSettingEnabled.get(); + } catch (Throwable ex) { + Slog.i(TAG, + "Flags not ready yet. Return false for " + + Flags.FLAG_FOLD_LOCK_SETTING_ENABLED, + ex); + return false; + } } } diff --git a/core/java/com/android/internal/foldables/fold_lock_setting_flags.aconfig b/core/java/com/android/internal/foldables/fold_lock_setting_flags.aconfig new file mode 100644 index 000000000000..44f436eaaa19 --- /dev/null +++ b/core/java/com/android/internal/foldables/fold_lock_setting_flags.aconfig @@ -0,0 +1,9 @@ +package: "com.android.internal.foldables.flags" + +flag { + name: "fold_lock_setting_enabled" + namespace: "display_manager" + description: "Feature flag for Fold Lock Setting" + bug: "274447767" + is_fixed_read_only: true +} 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_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index f79dbe761e07..178c0d0d95be 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -127,6 +127,7 @@ static struct { jfieldID xDpi; jfieldID yDpi; jfieldID refreshRate; + jfieldID vsyncRate; jfieldID appVsyncOffsetNanos; jfieldID presentationDeadlineNanos; jfieldID group; @@ -962,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, @@ -1231,6 +1233,7 @@ static jobject convertDisplayModeToJavaObject(JNIEnv* env, const ui::DisplayMode env->SetFloatField(object, gDisplayModeClassInfo.yDpi, config.yDpi); env->SetFloatField(object, gDisplayModeClassInfo.refreshRate, config.refreshRate); + env->SetFloatField(object, gDisplayModeClassInfo.vsyncRate, config.vsyncRate); env->SetLongField(object, gDisplayModeClassInfo.appVsyncOffsetNanos, config.appVsyncOffset); env->SetLongField(object, gDisplayModeClassInfo.presentationDeadlineNanos, config.presentationDeadline); @@ -2179,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}, @@ -2394,6 +2397,7 @@ int register_android_view_SurfaceControl(JNIEnv* env) gDisplayModeClassInfo.xDpi = GetFieldIDOrDie(env, modeClazz, "xDpi", "F"); gDisplayModeClassInfo.yDpi = GetFieldIDOrDie(env, modeClazz, "yDpi", "F"); gDisplayModeClassInfo.refreshRate = GetFieldIDOrDie(env, modeClazz, "refreshRate", "F"); + gDisplayModeClassInfo.vsyncRate = GetFieldIDOrDie(env, modeClazz, "vsyncRate", "F"); gDisplayModeClassInfo.appVsyncOffsetNanos = GetFieldIDOrDie(env, modeClazz, "appVsyncOffsetNanos", "J"); gDisplayModeClassInfo.presentationDeadlineNanos = diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index a0b8cca58d9b..a54a563b1804 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2108,12 +2108,12 @@ android:protectionLevel="signature" /> <!-- Allows direct access to the <RemoteAuth>Service interfaces. - @hide @TestApi @SystemApi(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES) --> + @hide --> <permission android:name="android.permission.MANAGE_REMOTE_AUTH" android:protectionLevel="signature" /> <!-- Allows direct access to the <RemoteAuth>Service authentication methods. - @hide @TestApi @SystemApi(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES) --> + @hide --> <permission android:name="android.permission.USE_REMOTE_AUTH" android:protectionLevel="signature" /> @@ -7233,13 +7233,25 @@ android:description="@string/permdesc_fullScreenIntent" android:protectionLevel="normal|appop" /> - <!-- Required for the assistant apps targeting {@link android.os.Build.VERSION_CODES#V} - that receive voice trigger from the trusted hotword detection service. + <!-- @SystemApi Required for the privileged assistant apps targeting + {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} + that receive voice trigger from a trusted hotword detection service. <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 the sandboxed hotword detection service or visual + query detection service. + <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/attrs.xml b/core/res/res/values/attrs.xml index e54347fc2744..04fd70a96201 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -10118,6 +10118,9 @@ screen that can be used to provide more information about a provider. For longer strings it will be truncated. --> <attr name="settingsSubtitle" format="string" /> + <!-- Fully qualified class name of an activity that allows the user to modify + the settings for this service. --> + <attr name="settingsActivity" /> </declare-styleable> <!-- A list of capabilities that indicates to the OS what kinds of credentials diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 7ef81abb8f0d..b211ac2fd316 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1026,6 +1026,12 @@ <!-- Duration, in milliseconds, of the display white balance animated transitions. --> <integer name="config_displayWhiteBalanceTransitionTime">3000</integer> + <!-- Duration, in milliseconds, of the display white balance animated transitions when increasing cct. --> + <integer name="config_displayWhiteBalanceTransitionTimeIncrease">1000</integer> + + <!-- Duration, in milliseconds, of the display white balance animated transitions when decreasing cct. --> + <integer name="config_displayWhiteBalanceTransitionTimeDecrease">40000</integer> + <!-- Device states where the sensor based rotation values should be reversed around the Z axis for the default display. TODO(b/265312193): Remove this workaround when this bug is fixed.--> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 39a4dd2aed8d..efe5c3bcf2df 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3487,6 +3487,8 @@ <java-symbol type="array" name="config_displayWhiteBalanceDisplaySteps" /> <java-symbol type="bool" name="config_displayWhiteBalanceLightModeAllowed" /> <java-symbol type="integer" name="config_displayWhiteBalanceTransitionTime" /> + <java-symbol type="integer" name="config_displayWhiteBalanceTransitionTimeIncrease" /> + <java-symbol type="integer" name="config_displayWhiteBalanceTransitionTimeDecrease" /> <!-- Device states where the sensor based rotation values should be reversed around the Z axis for the default display. diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java index 824f5910f96c..75a72310db69 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java @@ -610,8 +610,6 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase { @Test public void cancel() throws Exception { openAidlClients(/* numClients= */ 1); - ProgramSelector initialSel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]); - mTunerSessions[0].tune(initialSel); mTunerSessions[0].cancel(); @@ -621,9 +619,6 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase { @Test public void cancel_forNonCurrentUser_doesNotCancel() throws Exception { openAidlClients(/* numClients= */ 1); - ProgramSelector initialSel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]); - mTunerSessions[0].tune(initialSel); - verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onCurrentProgramInfoChanged(any()); doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser()); mTunerSessions[0].cancel(); diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java index 3ec44d14b409..6edfa0294fdd 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java @@ -540,8 +540,6 @@ public final class TunerSessionHidlTest extends ExtendedRadioMockitoTestCase { @Test public void cancel() throws Exception { openAidlClients(/* numClients= */ 1); - ProgramSelector initialSel = TestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]); - mTunerSessions[0].tune(initialSel); mTunerSessions[0].cancel(); @@ -551,9 +549,6 @@ public final class TunerSessionHidlTest extends ExtendedRadioMockitoTestCase { @Test public void cancel_forNonCurrentUser_doesNotCancel() throws Exception { openAidlClients(/* numClients= */ 1); - ProgramSelector initialSel = TestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]); - mTunerSessions[0].tune(initialSel); - verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onCurrentProgramInfoChanged(any()); doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser()); mTunerSessions[0].cancel(); diff --git a/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java b/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java new file mode 100644 index 000000000000..c00eb91d752a --- /dev/null +++ b/core/tests/coretests/src/android/app/servertransaction/WindowStateResizeItemTest.java @@ -0,0 +1,80 @@ +/* + * 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.app.servertransaction; + +import static org.mockito.Mockito.verify; + +import android.app.ClientTransactionHandler; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.util.MergedConfiguration; +import android.view.IWindow; +import android.view.InsetsState; +import android.window.ClientWindowFrames; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link WindowStateResizeItem}. + * + * Build/Install/Run: + * atest FrameworksCoreTests:WindowStateResizeItemTest + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public class WindowStateResizeItemTest { + + @Mock + private ClientTransactionHandler mHandler; + @Mock + private PendingTransactionActions mPendingActions; + @Mock + private IWindow mWindow; + @Mock + private ClientWindowFrames mFrames; + @Mock + private MergedConfiguration mConfiguration; + @Mock + private InsetsState mInsetsState; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testExecute() throws RemoteException { + final WindowStateResizeItem item = WindowStateResizeItem.obtain(mWindow, mFrames, + true /* reportDraw */, mConfiguration, mInsetsState, true /* forceLayout */, + true /* alwaysConsumeSystemBars */, 123 /* displayId */, 321 /* syncSeqId */, + true /* dragResizing */); + item.execute(mHandler, mPendingActions); + + verify(mWindow).resized(mFrames, + true /* reportDraw */, mConfiguration, mInsetsState, true /* forceLayout */, + true /* alwaysConsumeSystemBars */, 123 /* displayId */, 321 /* syncSeqId */, + true /* dragResizing */); + } +} diff --git a/core/tests/coretests/src/android/content/ContentCaptureOptionsTest.java b/core/tests/coretests/src/android/content/ContentCaptureOptionsTest.java index f8348d28b7fe..eefa6e482adf 100644 --- a/core/tests/coretests/src/android/content/ContentCaptureOptionsTest.java +++ b/core/tests/coretests/src/android/content/ContentCaptureOptionsTest.java @@ -32,6 +32,8 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import java.util.List; + /** * Unit test for {@link ContentCaptureOptions}. * @@ -44,6 +46,9 @@ public class ContentCaptureOptionsTest { private static final ComponentName CONTEXT_COMPONENT = new ComponentName("marco", "polo"); private static final ComponentName COMPONENT1 = new ComponentName("comp", "one"); private static final ComponentName COMPONENT2 = new ComponentName("two", "comp"); + private static final List<List<String>> CONTENT_PROTECTION_REQUIRED_GROUPS = + List.of(List.of("first"), List.of("second", "third"), List.of()); + private static final List<List<String>> CONTENT_PROTECTION_OPTIONAL_GROUPS = List.of(); private static final ContentCaptureOptions CONTENT_CAPTURE_OPTIONS = new ContentCaptureOptions( /* loggingLevel= */ 1000, @@ -55,7 +60,10 @@ public class ContentCaptureOptionsTest { /* enableReceiver= */ false, new ContentCaptureOptions.ContentProtectionOptions( /* enableReceiver= */ true, - /* bufferSize= */ 2001), + /* bufferSize= */ 2001, + CONTENT_PROTECTION_REQUIRED_GROUPS, + CONTENT_PROTECTION_OPTIONAL_GROUPS, + /* optionalGroupsThreshold= */ 2002), /* whitelistedComponents= */ toSet(COMPONENT1, COMPONENT2)); @Mock private Context mContext; @@ -134,6 +142,19 @@ public class ContentCaptureOptionsTest { .append(CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.enableReceiver) .append(", bufferSize=") .append(CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.bufferSize) + .append(", requiredGroupsSize=") + .append( + CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.requiredGroups + .size()) + .append(", optionalGroupsSize=") + .append( + CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.optionalGroups + .size()) + .append(", optionalGroupsThreshold=") + .append( + CONTENT_CAPTURE_OPTIONS + .contentProtectionOptions + .optionalGroupsThreshold) .append("], whitelisted=") .append(CONTENT_CAPTURE_OPTIONS.whitelistedComponents) .append(']') @@ -166,6 +187,15 @@ public class ContentCaptureOptionsTest { .isEqualTo(CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.enableReceiver); assertThat(actual.contentProtectionOptions.bufferSize) .isEqualTo(CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.bufferSize); + assertThat(actual.contentProtectionOptions.requiredGroups) + .containsExactlyElementsIn( + CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.requiredGroups); + assertThat(actual.contentProtectionOptions.optionalGroups) + .containsExactlyElementsIn( + CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.optionalGroups); + assertThat(actual.contentProtectionOptions.optionalGroupsThreshold) + .isEqualTo( + CONTENT_CAPTURE_OPTIONS.contentProtectionOptions.optionalGroupsThreshold); assertThat(actual.whitelistedComponents) .containsExactlyElementsIn(CONTENT_CAPTURE_OPTIONS.whitelistedComponents); } 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/service/notification/NotificationRankingUpdateTest.java b/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java index bfdb15b7f7c8..517aeae53784 100644 --- a/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java +++ b/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java @@ -48,6 +48,8 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags; +import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.Flag; +import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.FlagResolver; import org.junit.After; import org.junit.Assert; @@ -422,11 +424,24 @@ public class NotificationRankingUpdateTest { mNotificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, "test channel", NotificationManager.IMPORTANCE_DEFAULT); - SystemUiSystemPropertiesFlags.TEST_RESOLVER = flag -> { - if (flag.mSysPropKey.equals(RANKING_UPDATE_ASHMEM.mSysPropKey)) { - return mRankingUpdateAshmem; + SystemUiSystemPropertiesFlags.TEST_RESOLVER = new FlagResolver() { + @Override + public boolean isEnabled(Flag flag) { + if (flag.mSysPropKey.equals(RANKING_UPDATE_ASHMEM.mSysPropKey)) { + return mRankingUpdateAshmem; + } + return new SystemUiSystemPropertiesFlags.DebugResolver().isEnabled(flag); + } + + @Override + public int getIntValue(Flag flag) { + return 0; + } + + @Override + public String getStringValue(Flag flag) { + return null; } - return new SystemUiSystemPropertiesFlags.DebugResolver().isEnabled(flag); }; } diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java index 101f7c21fa19..5c411d5335a7 100644 --- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java +++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java @@ -31,6 +31,8 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import java.util.Collections; + /** * Unit test for {@link ContentCaptureManager}. * @@ -69,7 +71,11 @@ public class ContentCaptureManagerTest { ContentCaptureOptions options = createOptions( new ContentCaptureOptions.ContentProtectionOptions( - /* enableReceiver= */ false, BUFFER_SIZE)); + /* enableReceiver= */ false, + BUFFER_SIZE, + /* requiredGroups= */ Collections.emptyList(), + /* optionalGroups= */ Collections.emptyList(), + /* optionalGroupsThreshold= */ 0)); ContentCaptureManager manager = new ContentCaptureManager(mMockContext, mMockContentCaptureManager, options); @@ -82,7 +88,11 @@ public class ContentCaptureManagerTest { ContentCaptureOptions options = createOptions( new ContentCaptureOptions.ContentProtectionOptions( - /* enableReceiver= */ true, /* bufferSize= */ 0)); + /* enableReceiver= */ true, + /* bufferSize= */ 0, + /* requiredGroups= */ Collections.emptyList(), + /* optionalGroups= */ Collections.emptyList(), + /* optionalGroupsThreshold= */ 0)); ContentCaptureManager manager = new ContentCaptureManager(mMockContext, mMockContentCaptureManager, options); @@ -95,7 +105,11 @@ public class ContentCaptureManagerTest { ContentCaptureOptions options = createOptions( new ContentCaptureOptions.ContentProtectionOptions( - /* enableReceiver= */ true, BUFFER_SIZE)); + /* enableReceiver= */ true, + BUFFER_SIZE, + /* requiredGroups= */ Collections.emptyList(), + /* optionalGroups= */ Collections.emptyList(), + /* optionalGroupsThreshold= */ 0)); ContentCaptureManager manager = new ContentCaptureManager(mMockContext, mMockContentCaptureManager, options); diff --git a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java index 3373b8b13273..e76d266c614c 100644 --- a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java +++ b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java @@ -47,6 +47,7 @@ import org.mockito.junit.MockitoRule; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; /** @@ -112,7 +113,11 @@ public class MainContentCaptureSessionTest { createOptions( /* enableContentCaptureReceiver= */ true, new ContentCaptureOptions.ContentProtectionOptions( - /* enableReceiver= */ true, -BUFFER_SIZE)); + /* enableReceiver= */ true, + -BUFFER_SIZE, + /* requiredGroups= */ Collections.emptyList(), + /* optionalGroups= */ Collections.emptyList(), + /* optionalGroupsThreshold= */ 0)); MainContentCaptureSession session = createSession(options); session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor; @@ -313,7 +318,11 @@ public class MainContentCaptureSessionTest { return createOptions( enableContentCaptureReceiver, new ContentCaptureOptions.ContentProtectionOptions( - enableContentProtectionReceiver, BUFFER_SIZE)); + enableContentProtectionReceiver, + BUFFER_SIZE, + /* requiredGroups= */ Collections.emptyList(), + /* optionalGroups= */ Collections.emptyList(), + /* optionalGroupsThreshold= */ 0)); } private ContentCaptureManager createManager(ContentCaptureOptions options) { 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/accessibility/AccessibilityShortcutChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java index 681ba9c353e5..06b62f85bede 100644 --- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java @@ -29,6 +29,8 @@ import static androidx.test.espresso.matcher.ViewMatchers.withClassName; import static androidx.test.espresso.matcher.ViewMatchers.withId; import static androidx.test.espresso.matcher.ViewMatchers.withText; +import static com.google.common.truth.Truth.assertThat; + import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.endsWith; import static org.mockito.ArgumentMatchers.any; @@ -39,6 +41,8 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.accessibilityservice.AccessibilityServiceInfo; +import android.app.AlertDialog; +import android.app.KeyguardManager; import android.content.ComponentName; import android.content.Context; import android.content.pm.ApplicationInfo; @@ -47,11 +51,17 @@ import android.content.pm.ParceledListSlice; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.os.Handler; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.support.test.uiautomator.By; import android.support.test.uiautomator.UiDevice; import android.support.test.uiautomator.UiObject2; import android.support.test.uiautomator.Until; +import android.view.View; +import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.Flags; import android.view.accessibility.IAccessibilityManager; import androidx.lifecycle.Lifecycle; @@ -90,6 +100,9 @@ public class AccessibilityShortcutChooserActivityTest { private TestAccessibilityShortcutChooserActivity mActivity; @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + + @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); @Mock private AccessibilityServiceInfo mAccessibilityServiceInfo; @@ -101,6 +114,8 @@ public class AccessibilityShortcutChooserActivityTest { private ApplicationInfo mApplicationInfo; @Mock private IAccessibilityManager mAccessibilityManagerService; + @Mock + private KeyguardManager mKeyguardManager; @Before public void setUp() throws Exception { @@ -116,22 +131,19 @@ public class AccessibilityShortcutChooserActivityTest { Collections.singletonList(mAccessibilityServiceInfo))); when(mAccessibilityManagerService.isAccessibilityTargetAllowed( anyString(), anyInt(), anyInt())).thenReturn(true); - TestAccessibilityShortcutChooserActivity.setupForTesting(mAccessibilityManagerService); - mScenario = ActivityScenario.launch(TestAccessibilityShortcutChooserActivity.class); - mScenario.onActivity(activity -> mActivity = activity); - mScenario.moveToState(Lifecycle.State.CREATED); - mScenario.moveToState(Lifecycle.State.STARTED); - mScenario.moveToState(Lifecycle.State.RESUMED); - InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + when(mKeyguardManager.isKeyguardLocked()).thenReturn(false); + TestAccessibilityShortcutChooserActivity.setupForTesting( + mAccessibilityManagerService, mKeyguardManager); } @After public void cleanUp() { - mScenario.moveToState(Lifecycle.State.DESTROYED); + mScenario.close(); } @Test public void doubleClickTestServiceAndClickDenyButton_permissionDialogDoesNotExist() { + launchActivity(); openShortcutsList(); // Performing the double-click is flaky so retry if needed. @@ -154,6 +166,7 @@ public class AccessibilityShortcutChooserActivityTest { throws Exception { when(mAccessibilityManagerService.isAccessibilityTargetAllowed( eq(TEST_COMPONENT_NAME.getPackageName()), anyInt(), anyInt())).thenReturn(false); + launchActivity(); openShortcutsList(); onView(withText(TEST_LABEL)).perform(scrollTo(), click()); @@ -165,6 +178,7 @@ public class AccessibilityShortcutChooserActivityTest { @Test public void popEditShortcutMenuList_oneHandedModeEnabled_shouldBeInListView() { TestUtils.setOneHandedModeEnabled(this, /* enabled= */ true); + launchActivity(); openShortcutsList(); onView(allOf(withClassName(endsWith("ListView")), isDisplayed())).perform(swipeUp()); @@ -176,6 +190,7 @@ public class AccessibilityShortcutChooserActivityTest { @Test public void popEditShortcutMenuList_oneHandedModeDisabled_shouldNotBeInListView() { TestUtils.setOneHandedModeEnabled(this, /* enabled= */ false); + launchActivity(); openShortcutsList(); onView(allOf(withClassName(endsWith("ListView")), isDisplayed())).perform(swipeUp()); @@ -184,6 +199,30 @@ public class AccessibilityShortcutChooserActivityTest { onView(withText(ONE_HANDED_MODE)).inRoot(isDialog()).check(doesNotExist()); } + @Test + @RequiresFlagsEnabled(Flags.FLAG_ALLOW_SHORTCUT_CHOOSER_ON_LOCKSCREEN) + public void createDialog_onLockscreen_hasExpectedContent() { + when(mKeyguardManager.isKeyguardLocked()).thenReturn(true); + launchActivity(); + + final AlertDialog dialog = mActivity.getMenuDialog(); + + assertThat(dialog.getButton(AlertDialog.BUTTON_POSITIVE).getVisibility()) + .isEqualTo(View.GONE); + assertThat(dialog.getWindow().getAttributes().flags + & WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED) + .isEqualTo(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); + } + + private void launchActivity() { + mScenario = ActivityScenario.launch(TestAccessibilityShortcutChooserActivity.class); + mScenario.onActivity(activity -> mActivity = activity); + mScenario.moveToState(Lifecycle.State.CREATED); + mScenario.moveToState(Lifecycle.State.STARTED); + mScenario.moveToState(Lifecycle.State.RESUMED); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + } + private void openShortcutsList() { UiObject2 editButton = mDevice.findObject(By.text(EDIT_LABEL)); if (editButton != null) { @@ -198,9 +237,13 @@ public class AccessibilityShortcutChooserActivityTest { public static class TestAccessibilityShortcutChooserActivity extends AccessibilityShortcutChooserActivity { private static IAccessibilityManager sAccessibilityManagerService; + private static KeyguardManager sKeyguardManager; - public static void setupForTesting(IAccessibilityManager accessibilityManagerService) { + public static void setupForTesting( + IAccessibilityManager accessibilityManagerService, + KeyguardManager keyguardManager) { sAccessibilityManagerService = accessibilityManagerService; + sKeyguardManager = keyguardManager; } @Override @@ -210,6 +253,9 @@ public class AccessibilityShortcutChooserActivityTest { return new AccessibilityManager(this, new Handler(getMainLooper()), sAccessibilityManagerService, /* userId= */ 0, /* serviceConnect= */ true); } + if (Context.KEYGUARD_SERVICE.equals(name)) { + return sKeyguardManager; + } return super.getSystemService(name); } diff --git a/framework-minus-apex-ravenwood-policies.txt b/framework-minus-apex-ravenwood-policies.txt new file mode 100644 index 000000000000..6bac58bf8ed7 --- /dev/null +++ b/framework-minus-apex-ravenwood-policies.txt @@ -0,0 +1 @@ +# Ravenwood "policy" file for framework-minus-apex. 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 9d32272755c0..db1cc446b2d6 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -16,8 +16,11 @@ package android.graphics; +import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE; + import android.annotation.ColorInt; import android.annotation.ColorLong; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; @@ -2125,7 +2128,7 @@ public class Paint { * @return the font's recommended interline spacing. */ public float getFontMetrics(FontMetrics metrics) { - return nGetFontMetrics(mNativePaint, metrics); + return nGetFontMetrics(mNativePaint, metrics, false); } /** @@ -2139,6 +2142,32 @@ public class Paint { } /** + * Get the font metrics used for the locale + * + * Obtain the metrics of the font that is used for the specified locale by + * {@link #setTextLocales(LocaleList)}. If multiple locales are specified, the minimum ascent + * and maximum descent will be set. + * + * This API is useful for determining the default line height of the empty text field, e.g. + * {@link android.widget.EditText}. + * + * Note, if the {@link android.graphics.Typeface} is created from the custom font files, its + * metrics are reserved, i.e. the ascent will be the custom font's ascent or smaller, the + * descent will be the custom font's descent or larger. + * + * Note, if the {@link android.graphics.Typeface} is a system fallback, e.g. + * {@link android.graphics.Typeface#SERIF}, the default font's metrics are reserved, i.e. the + * ascent will be the serif font's ascent or smaller, the descent will be the serif font's + * descent or larger. + * + * @param metrics an output parameter. All members will be set by calling this function. + */ + @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE) + public void getFontMetricsForLocale(@NonNull FontMetrics metrics) { + nGetFontMetrics(mNativePaint, metrics, true); + } + + /** * Returns the font metrics value for the given text. * * If the text is rendered with multiple font files, this function returns the large ascent and @@ -2318,7 +2347,7 @@ public class Paint { * @return the font's interline spacing. */ public int getFontMetricsInt(FontMetricsInt fmi) { - return nGetFontMetricsInt(mNativePaint, fmi); + return nGetFontMetricsInt(mNativePaint, fmi, false); } public FontMetricsInt getFontMetricsInt() { @@ -2328,6 +2357,32 @@ public class Paint { } /** + * Get the font metrics used for the locale + * + * Obtain the metrics of the font that is used for the specified locale by + * {@link #setTextLocales(LocaleList)}. If multiple locales are specified, the minimum ascent + * and maximum descent will be set. + * + * This API is useful for determining the default line height of the empty text field, e.g. + * {@link android.widget.EditText}. + * + * Note, if the {@link android.graphics.Typeface} is created from the custom font files, its + * metrics are reserved, i.e. the ascent will be the custom font's ascent or smaller, the + * descent will be the custom font's descent or larger. + * + * Note, if the {@link android.graphics.Typeface} is a system fallback, e.g. + * {@link android.graphics.Typeface#SERIF}, the default font's metrics are reserved, i.e. the + * ascent will be the serif font's ascent or smaller, the descent will be the serif font's + * descent or larger. + * + * @param metrics an output parameter. All members will be set by calling this function. + */ + @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE) + public void getFontMetricsIntForLocale(@NonNull FontMetricsInt metrics) { + nGetFontMetricsInt(mNativePaint, metrics, true); + } + + /** * Return the recommend line spacing based on the current typeface and * text size. * @@ -3446,9 +3501,11 @@ public class Paint { @FastNative private static native void nSetFontFeatureSettings(long paintPtr, String settings); @FastNative - private static native float nGetFontMetrics(long paintPtr, FontMetrics metrics); + private static native float nGetFontMetrics(long paintPtr, FontMetrics metrics, + boolean useLocale); @FastNative - private static native int nGetFontMetricsInt(long paintPtr, FontMetricsInt fmi); + private static native int nGetFontMetricsInt(long paintPtr, FontMetricsInt fmi, + boolean useLocale); // ---------------- @CriticalNative ------------------------ 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/keystore/aaid/aidl/Android.bp b/keystore/aaid/aidl/Android.bp new file mode 100644 index 000000000000..97acfb4ea4c3 --- /dev/null +++ b/keystore/aaid/aidl/Android.bp @@ -0,0 +1,31 @@ +// Copyright 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 { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +aidl_interface { + name: "android.security.aaid_aidl", + srcs: ["android/security/keystore/*.aidl"], + unstable: true, + backend: { + rust: { + enabled: true, + }, + cpp: { + enabled: true, + }, + }, +} diff --git a/core/java/android/security/keymaster/IKeyAttestationApplicationIdProvider.aidl b/keystore/aaid/aidl/android/security/keystore/IKeyAttestationApplicationIdProvider.aidl index dbffd5f57ce2..c360cb8f281a 100644 --- a/core/java/android/security/keymaster/IKeyAttestationApplicationIdProvider.aidl +++ b/keystore/aaid/aidl/android/security/keystore/IKeyAttestationApplicationIdProvider.aidl @@ -1,5 +1,5 @@ /** - * Copyright (c) 2016, 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. @@ -14,19 +14,15 @@ * limitations under the License. */ -package android.security.keymaster; +package android.security.keystore; -import android.security.keymaster.KeyAttestationApplicationId; -import android.security.keymaster.KeyAttestationPackageInfo; -import android.content.pm.Signature; +import android.security.keystore.KeyAttestationApplicationId; -/** - * This must be kept manually in sync with system/security/keystore until AIDL - * can generate both Java and C++ bindings. - * - * @hide - */ +/** @hide */ interface IKeyAttestationApplicationIdProvider { - /* keep in sync with /system/security/keystore/keystore_attestation_id.cpp */ + /** + * Provides information describing the possible applications identified by a UID. + * @hide + */ KeyAttestationApplicationId getKeyAttestationApplicationId(int uid); } diff --git a/keystore/aaid/aidl/android/security/keystore/KeyAttestationApplicationId.aidl b/keystore/aaid/aidl/android/security/keystore/KeyAttestationApplicationId.aidl new file mode 100644 index 000000000000..c33e8309b2f2 --- /dev/null +++ b/keystore/aaid/aidl/android/security/keystore/KeyAttestationApplicationId.aidl @@ -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 android.security.keystore; + +import android.security.keystore.KeyAttestationPackageInfo; + +/** + * @hide + * The information aggregated by this parcelable is used by keystore to identify a caller of the + * keystore API toward a remote party. It aggregates multiple PackageInfos because keystore + * can only determine a caller by uid granularity, and a uid can be shared by multiple packages. + * The remote party must decide if it trusts all of the packages enough to consider the + * confidentiality of the key material in question intact. + */ +parcelable KeyAttestationApplicationId { + KeyAttestationPackageInfo[] packageInfos; +} diff --git a/keystore/aaid/aidl/android/security/keystore/KeyAttestationPackageInfo.aidl b/keystore/aaid/aidl/android/security/keystore/KeyAttestationPackageInfo.aidl new file mode 100644 index 000000000000..5f647d0b1abe --- /dev/null +++ b/keystore/aaid/aidl/android/security/keystore/KeyAttestationPackageInfo.aidl @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2023, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keystore; + +import android.security.keystore.Signature; + +/** + * @hide + * This parcelable constitutes and excerpt from the PackageManager's PackageInfo for the purpose of + * key attestation. It is part of the KeyAttestationApplicationId, which is used by + * keystore to identify the caller of the keystore API towards a remote party. + */ +parcelable KeyAttestationPackageInfo { + String packageName; + + long versionCode; + + Signature[] signatures; +} diff --git a/keystore/aaid/aidl/android/security/keystore/Signature.aidl b/keystore/aaid/aidl/android/security/keystore/Signature.aidl new file mode 100644 index 000000000000..800499a13355 --- /dev/null +++ b/keystore/aaid/aidl/android/security/keystore/Signature.aidl @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2016, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keystore; + +/** + * @hide + * Represents a signature data read from the package file. Extracted from from the PackageManager's + * PackageInfo for the purpose of key attestation. It is part of the KeyAttestationPackageInfo, + * which is used by keystore to identify the caller of the keystore API towards a remote party. + */ +parcelable Signature { + /** + * Represents signing certificate data associated with application package, signatures are + * expected to be a hex-encoded ASCII string representing valid X509 certificate. + */ + byte[] data; +} diff --git a/packages/SystemUI/ktfmt_includes.txt b/ktfmt_includes.txt index d3254b7914e2..e4bf4c26dc7d 100644 --- a/packages/SystemUI/ktfmt_includes.txt +++ b/ktfmt_includes.txt @@ -1,3 +1,4 @@ ++services/permission +packages/SystemUI -packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt -packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig index d55a41fc0cf7..7c0d0e37e28c 100644 --- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig +++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig @@ -5,4 +5,18 @@ flag { namespace: "multitasking" description: "An Example Flag" bug: "300136750" -}
\ No newline at end of file +} + +flag { + name: "enable_app_pairs" + namespace: "multitasking" + description: "Enables the ability to create and save app pairs to the Home screen" + bug: "274835596" +} + +flag { + name: "desktop_windowing" + namespace: "multitasking" + description: "Enables desktop windowing" + bug: "304778354" +} diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml index 97a9d4874455..74364001b6c5 100644 --- a/libs/WindowManager/Shell/res/values/config.xml +++ b/libs/WindowManager/Shell/res/values/config.xml @@ -58,6 +58,9 @@ if a custom action is present before closing it. --> <integer name="config_pipForceCloseDelay">1000</integer> + <!-- Allow PIP to resize via pinch gesture. --> + <bool name="config_pipEnablePinchResize">true</bool> + <!-- Animation duration when using long press on recents to dock --> <integer name="long_press_dock_anim_duration">250</integer> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java index 34bf9e0dd98f..2e5448a9e8d5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java @@ -18,6 +18,7 @@ package com.android.wm.shell; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE; +import android.annotation.SuppressLint; import android.app.WindowConfiguration; import android.util.SparseArray; import android.view.SurfaceControl; @@ -29,6 +30,7 @@ import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.sysui.ShellInit; import java.io.PrintWriter; import java.util.List; @@ -44,9 +46,14 @@ public class RootDisplayAreaOrganizer extends DisplayAreaOrganizer { /** Display area leashes, which is mapped by display IDs. */ private final SparseArray<SurfaceControl> mLeashes = new SparseArray<>(); - public RootDisplayAreaOrganizer(Executor executor) { + public RootDisplayAreaOrganizer(@NonNull Executor executor, @NonNull ShellInit shellInit) { super(executor); - List<DisplayAreaAppearedInfo> infos = registerOrganizer(FEATURE_ROOT); + shellInit.addInitCallback(this::onInit, this); + } + + @SuppressLint("MissingPermission") // Only called by SysUI. + private void onInit() { + final List<DisplayAreaAppearedInfo> infos = registerOrganizer(FEATURE_ROOT); for (int i = infos.size() - 1; i >= 0; --i) { onDisplayAreaAppeared(infos.get(i).getDisplayAreaInfo(), infos.get(i).getLeash()); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java index 38550b405c0e..ab61a48a715c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java @@ -18,6 +18,7 @@ package com.android.wm.shell; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG; +import android.annotation.SuppressLint; import android.annotation.UiContext; import android.app.ResourcesManager; import android.content.Context; @@ -38,6 +39,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.sysui.ShellInit; import java.io.PrintWriter; import java.util.ArrayList; @@ -69,10 +71,17 @@ public class RootTaskDisplayAreaOrganizer extends DisplayAreaOrganizer { private final Context mContext; - public RootTaskDisplayAreaOrganizer(Executor executor, Context context) { + public RootTaskDisplayAreaOrganizer(@NonNull Executor executor, @NonNull Context context, + @NonNull ShellInit shellInit) { super(executor); mContext = context; - List<DisplayAreaAppearedInfo> infos = registerOrganizer(FEATURE_DEFAULT_TASK_CONTAINER); + shellInit.addInitCallback(this::onInit, this); + } + + @SuppressLint("MissingPermission") // Only called by SysUI. + private void onInit() { + final List<DisplayAreaAppearedInfo> infos = + registerOrganizer(FEATURE_DEFAULT_TASK_CONTAINER); for (int i = infos.size() - 1; i >= 0; --i) { onDisplayAreaAppeared(infos.get(i).getDisplayAreaInfo(), infos.get(i).getLeash()); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/common/OWNERS index 7af038999797..6519eee18e69 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/OWNERS +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/OWNERS @@ -1 +1,2 @@ madym@google.com +hwwang@google.com diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java index 3b32b6c7b083..d520ff791e07 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java @@ -126,12 +126,22 @@ public class PipBoundsState { private @Nullable TriConsumer<Boolean, Integer, Boolean> mOnShelfVisibilityChangeCallback; private List<Consumer<Rect>> mOnPipExclusionBoundsChangeCallbacks = new ArrayList<>(); + // the size of the current bounds relative to the max size spec + private float mBoundsScale; + public PipBoundsState(@NonNull Context context, @NonNull SizeSpecSource sizeSpecSource, @NonNull PipDisplayLayoutState pipDisplayLayoutState) { mContext = context; reloadResources(); mSizeSpecSource = sizeSpecSource; mPipDisplayLayoutState = pipDisplayLayoutState; + + // Update the relative proportion of the bounds compared to max possible size. Max size + // spec takes the aspect ratio of the bounds into account, so both width and height + // scale by the same factor. + addPipExclusionBoundsChangeCallback((bounds) -> { + mBoundsScale = Math.min((float) bounds.width() / mMaxSize.x, 1.0f); + }); } /** Reloads the resources. */ @@ -160,6 +170,15 @@ public class PipBoundsState { return new Rect(mBounds); } + /** + * Get the scale of the current bounds relative to the maximum size possible. + * + * @return 1.0 if {@link PipBoundsState#getBounds()} equals {@link PipBoundsState#getMaxSize()}. + */ + public float getBoundsScale() { + return mBoundsScale; + } + /** Returns the current movement bounds. */ @NonNull public Rect getMovementBounds() { @@ -622,6 +641,9 @@ public class PipBoundsState { pw.println(innerPrefix + "mShelfHeight=" + mShelfHeight); pw.println(innerPrefix + "mHasUserMovedPip=" + mHasUserMovedPip); pw.println(innerPrefix + "mHasUserResizedPip=" + mHasUserResizedPip); + pw.println(innerPrefix + "mMinSize=" + mMinSize); + pw.println(innerPrefix + "mMaxSize=" + mMaxSize); + pw.println(innerPrefix + "mBoundsScale" + mBoundsScale); if (mPipReentryState == null) { pw.println(innerPrefix + "mPipReentryState=null"); } else { 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 e6d3abcc6e1d..c51af46accdb 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 @@ -110,13 +110,13 @@ import com.android.wm.shell.unfold.UnfoldAnimationController; import com.android.wm.shell.unfold.UnfoldTransitionHandler; import com.android.wm.shell.windowdecor.WindowDecorViewModel; -import java.util.Optional; - import dagger.BindsOptionalOf; import dagger.Lazy; import dagger.Module; import dagger.Provides; +import java.util.Optional; + /** * Provides basic dependencies from {@link com.android.wm.shell}, these dependencies are only * accessible from components within the WM subcomponent (can be explicitly exposed to the @@ -658,15 +658,15 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides static RootTaskDisplayAreaOrganizer provideRootTaskDisplayAreaOrganizer( - @ShellMainThread ShellExecutor mainExecutor, Context context) { - return new RootTaskDisplayAreaOrganizer(mainExecutor, context); + @ShellMainThread ShellExecutor mainExecutor, Context context, ShellInit shellInit) { + return new RootTaskDisplayAreaOrganizer(mainExecutor, context, shellInit); } @WMSingleton @Provides static RootDisplayAreaOrganizer provideRootDisplayAreaOrganizer( - @ShellMainThread ShellExecutor mainExecutor) { - return new RootDisplayAreaOrganizer(mainExecutor); + @ShellMainThread ShellExecutor mainExecutor, ShellInit shellInit) { + return new RootDisplayAreaOrganizer(mainExecutor, shellInit); } @WMSingleton 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 5dfba5e7ff1d..14a040a40874 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 @@ -203,6 +203,7 @@ public abstract class WMShellModule { ShellTaskOrganizer taskOrganizer, DisplayController displayController, ShellController shellController, + DisplayInsetsController displayInsetsController, SyncTransactionQueue syncQueue, Transitions transitions, Optional<DesktopTasksController> desktopTasksController, @@ -218,6 +219,7 @@ public abstract class WMShellModule { taskOrganizer, displayController, shellController, + displayInsetsController, syncQueue, transitions, desktopTasksController, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index 106486714a5c..63f20fd8e997 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -797,21 +797,15 @@ public class PipController implements PipTransitionController.PipTransitionCallb mPipBoundsAlgorithm.getMovementBounds(postChangeBounds), mPipBoundsState.getStashedState()); - // Scale PiP on density dpi change, so it appears to be the same size physically. - final boolean densityDpiChanged = - mPipDisplayLayoutState.getDisplayLayout().densityDpi() != 0 - && (mPipDisplayLayoutState.getDisplayLayout().densityDpi() - != layout.densityDpi()); - if (densityDpiChanged) { - final float scale = (float) layout.densityDpi() - / mPipDisplayLayoutState.getDisplayLayout().densityDpi(); - postChangeBounds.set(0, 0, - (int) (postChangeBounds.width() * scale), - (int) (postChangeBounds.height() * scale)); - } - updateDisplayLayout.run(); + // Resize the PiP bounds to be at the same scale relative to the new size spec. For + // example, if PiP was resized to 90% of the maximum size on the previous layout, + // make sure it is 90% of the new maximum size spec. + postChangeBounds.set(0, 0, + (int) (mPipBoundsState.getMaxSize().x * mPipBoundsState.getBoundsScale()), + (int) (mPipBoundsState.getMaxSize().y * mPipBoundsState.getBoundsScale())); + // Calculate the PiP bounds in the new orientation based on same fraction along the // rotated movement bounds. final Rect postChangeMovementBounds = mPipBoundsAlgorithm.getMovementBounds( @@ -822,6 +816,15 @@ public class PipController implements PipTransitionController.PipTransitionCallb mPipDisplayLayoutState.getDisplayBounds(), mPipDisplayLayoutState.getDisplayLayout().stableInsets()); + // make sure we user resize to the updated bounds to avoid animating to any outdated + // sizes from the previous layout upon double tap CUJ + mPipBoundsState.setHasUserResizedPip(true); + mTouchHandler.setUserResizeBounds(postChangeBounds); + + final boolean densityDpiChanged = + mPipDisplayLayoutState.getDisplayLayout().densityDpi() != 0 + && (mPipDisplayLayoutState.getDisplayLayout().densityDpi() + != layout.densityDpi()); if (densityDpiChanged) { // Using PipMotionHelper#movePip directly here may cause race condition since // the app content in PiP mode may or may not be updated for the new density dpi. @@ -833,15 +836,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb // Directly move PiP to its final destination bounds without animation. mPipTaskOrganizer.scheduleFinishResizePip(postChangeBounds); } - - // if the pip window size is beyond allowed bounds user resize to normal bounds - if (mPipBoundsState.getBounds().width() < mPipBoundsState.getMinSize().x - || mPipBoundsState.getBounds().width() > mPipBoundsState.getMaxSize().x - || mPipBoundsState.getBounds().height() < mPipBoundsState.getMinSize().y - || mPipBoundsState.getBounds().height() > mPipBoundsState.getMaxSize().y) { - mTouchHandler.userResizeTo(mPipBoundsState.getNormalBounds(), snapFraction); - } - } else { updateDisplayLayout.run(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java index e5f9fdc7a740..f175775ce8b2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java @@ -15,7 +15,6 @@ */ package com.android.wm.shell.pip.phone; -import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_PINCH_RESIZE; import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_BOTTOM; import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_LEFT; import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_NONE; @@ -31,7 +30,6 @@ import android.graphics.Rect; import android.graphics.Region; import android.hardware.input.InputManager; import android.os.Looper; -import android.provider.DeviceConfig; import android.view.BatchedInputEventReceiver; import android.view.Choreographer; import android.view.InputChannel; @@ -155,21 +153,8 @@ public class PipResizeGestureHandler { mContext.getDisplay().getRealSize(mMaxSize); reloadResources(); - mEnablePinchResize = DeviceConfig.getBoolean( - DeviceConfig.NAMESPACE_SYSTEMUI, - PIP_PINCH_RESIZE, - /* defaultValue = */ true); - DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, - mMainExecutor, - new DeviceConfig.OnPropertiesChangedListener() { - @Override - public void onPropertiesChanged(DeviceConfig.Properties properties) { - if (properties.getKeyset().contains(PIP_PINCH_RESIZE)) { - mEnablePinchResize = properties.getBoolean( - PIP_PINCH_RESIZE, /* defaultValue = */ true); - } - } - }); + final Resources res = mContext.getResources(); + mEnablePinchResize = res.getBoolean(R.bool.config_pipEnablePinchResize); } public void onConfigurationChanged() { @@ -579,6 +564,12 @@ public class PipResizeGestureHandler { resizeRectAboutCenter(mLastResizeBounds, mMaxSize.x, mMaxSize.y); } + // If user resize is smaller than min size, auto resize to min + if (mLastResizeBounds.width() < mMinSize.x + || mLastResizeBounds.height() < mMinSize.y) { + resizeRectAboutCenter(mLastResizeBounds, mMinSize.x, mMinSize.y); + } + // get the current movement bounds final Rect movementBounds = mPipBoundsAlgorithm .getMovementBounds(mLastResizeBounds); @@ -679,6 +670,8 @@ public class PipResizeGestureHandler { pw.println(innerPrefix + "mEnablePinchResize=" + mEnablePinchResize); pw.println(innerPrefix + "mThresholdCrossed=" + mThresholdCrossed); pw.println(innerPrefix + "mOhmOffset=" + mOhmOffset); + pw.println(innerPrefix + "mMinSize=" + mMinSize); + pw.println(innerPrefix + "mMaxSize=" + mMaxSize); } class PipResizeInputEventReceiver extends BatchedInputEventReceiver { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java index 2ce4fb9e297b..452a41696fcf 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java @@ -779,13 +779,10 @@ public class PipTouchHandler { } /** - * Resizes the pip window and updates user resized bounds - * - * @param bounds target bounds to resize to - * @param snapFraction snap fraction to apply after resizing + * Sets the user resize bounds tracked by {@link PipResizeGestureHandler} */ - void userResizeTo(Rect bounds, float snapFraction) { - mPipResizeGestureHandler.userResizeTo(bounds, snapFraction); + void setUserResizeBounds(Rect bounds) { + mPipResizeGestureHandler.setUserResizeBounds(bounds); } /** 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 b2948667f9e6..68ca2313f709 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 @@ -2242,6 +2242,25 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return SPLIT_POSITION_UNDEFINED; } + /** + * Returns the {@link StageType} where {@param token} is being used + * {@link SplitScreen#STAGE_TYPE_UNDEFINED} otherwise + */ + @StageType + public int getSplitItemStage(@Nullable WindowContainerToken token) { + if (token == null) { + return STAGE_TYPE_UNDEFINED; + } + + if (mMainStage.containsToken(token)) { + return STAGE_TYPE_MAIN; + } else if (mSideStage.containsToken(token)) { + return STAGE_TYPE_SIDE; + } + + return STAGE_TYPE_UNDEFINED; + } + @Override public void setLayoutOffsetTarget(int offsetX, int offsetY, SplitLayout layout) { final StageTaskListener topLeftStage = @@ -2479,7 +2498,16 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mRecentTasks.ifPresent( recentTasks -> recentTasks.removeSplitPair(triggerTask.taskId)); } - prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, outWCT); + @StageType int topStage = STAGE_TYPE_UNDEFINED; + if (isSplitScreenVisible()) { + // Get the stage where a child exists to keep that stage onTop + if (mMainStage.getChildCount() != 0 && mSideStage.getChildCount() == 0) { + topStage = STAGE_TYPE_MAIN; + } else if (mSideStage.getChildCount() != 0 && mMainStage.getChildCount() == 0) { + topStage = STAGE_TYPE_SIDE; + } + } + prepareExitSplitScreen(topStage, outWCT); } } @@ -2693,7 +2721,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @NonNull Transitions.TransitionFinishCallback finishCallback) { boolean shouldAnimate = true; if (mSplitTransitions.isPendingEnter(transition)) { - shouldAnimate = startPendingEnterAnimation( + shouldAnimate = startPendingEnterAnimation(transition, mSplitTransitions.mPendingEnter, info, startTransaction, finishTransaction); } else if (mSplitTransitions.isPendingDismiss(transition)) { final SplitScreenTransitions.DismissSession dismiss = mSplitTransitions.mPendingDismiss; @@ -2732,7 +2760,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } - private boolean startPendingEnterAnimation( + private boolean startPendingEnterAnimation(@NonNull IBinder transition, @NonNull SplitScreenTransitions.EnterSession enterTransition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) { @@ -2761,21 +2789,22 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } - if (mSplitTransitions.mPendingEnter.mExtraTransitType + SplitScreenTransitions.EnterSession pendingEnter = mSplitTransitions.mPendingEnter; + if (pendingEnter.mExtraTransitType == TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE) { // Open to side should only be used when split already active and foregorund. if (mainChild == null && sideChild == null) { Log.w(TAG, splitFailureMessage("startPendingEnterAnimation", "Launched a task in split, but didn't receive any task in transition.")); // This should happen when the target app is already on front, so just cancel. - mSplitTransitions.mPendingEnter.cancel(null); + pendingEnter.cancel(null); return true; } } else { if (mainChild == null || sideChild == null) { final int dismissTop = mainChild != null ? STAGE_TYPE_MAIN : (sideChild != null ? STAGE_TYPE_SIDE : STAGE_TYPE_UNDEFINED); - mSplitTransitions.mPendingEnter.cancel( + pendingEnter.cancel( (cancelWct, cancelT) -> prepareExitSplitScreen(dismissTop, cancelWct)); Log.w(TAG, splitFailureMessage("startPendingEnterAnimation", "launched 2 tasks in split, but didn't receive " @@ -2786,6 +2815,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (mRecentTasks.isPresent() && sideChild != null) { mRecentTasks.get().removeSplitPair(sideChild.getTaskInfo().taskId); } + if (pendingEnter.mRemoteHandler != null) { + // Pass false for aborted since WM didn't abort, business logic chose to + // terminate/exit early + pendingEnter.mRemoteHandler.onTransitionConsumed(transition, + false /*aborted*/, finishT); + } mSplitUnsupportedToast.show(); return true; } @@ -2896,7 +2931,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return SPLIT_POSITION_UNDEFINED; } - /** Synchronize split-screen state with transition and make appropriate preparations. */ + /** + * Synchronize split-screen state with transition and make appropriate preparations. + * @param toStage The stage that will not be dismissed. If set to + * {@link SplitScreen#STAGE_TYPE_UNDEFINED} then both stages will be dismissed + */ public void prepareDismissAnimation(@StageType int toStage, @ExitReason int dismissReason, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) { 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 e828eedc275c..451e61855943 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java @@ -50,6 +50,7 @@ import com.android.wm.shell.keyguard.KeyguardTransitionHandler; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.recents.RecentsTransitionHandler; +import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.splitscreen.StageCoordinator; import com.android.wm.shell.sysui.ShellInit; @@ -344,6 +345,8 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, // Keyguard handler cannot handle it, process through original mixed mActiveTransitions.remove(keyguardMixed); } + } else if (mPipHandler != null) { + mPipHandler.syncPipSurfaceState(info, startTransaction, finishTransaction); } } @@ -511,8 +514,26 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, // make a new startTransaction because pip's startEnterAnimation "consumes" it so // we need a separate one to send over to launcher. SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction(); + @SplitScreen.StageType int topStageToKeep = STAGE_TYPE_UNDEFINED; + if (mSplitHandler.isSplitScreenVisible()) { + // The non-going home case, we could be pip-ing one of the split stages and keep + // showing the other + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + TransitionInfo.Change change = info.getChanges().get(i); + if (change == pipChange) { + // Ignore the change/task that's going into Pip + continue; + } + @SplitScreen.StageType int splitItemStage = + mSplitHandler.getSplitItemStage(change.getLastParent()); + if (splitItemStage != STAGE_TYPE_UNDEFINED) { + topStageToKeep = splitItemStage; + break; + } + } + } // Let split update internal state for dismiss. - mSplitHandler.prepareDismissAnimation(STAGE_TYPE_UNDEFINED, + mSplitHandler.prepareDismissAnimation(topStageToKeep, EXIT_REASON_CHILD_TASK_ENTER_PIP, everythingElse, otherStartT, finishTransaction); 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 fab2dd2bf3e1..8b050e524038 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 @@ -152,6 +152,16 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler { } @Override + public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted, + @Nullable SurfaceControl.Transaction finishTransaction) { + try { + mRemote.getRemoteTransition().onTransitionConsumed(transition, aborted); + } catch (RemoteException e) { + Log.e(Transitions.TAG, "Error calling onTransitionConsumed()", e); + } + } + + @Override public String toString() { return "OneShotRemoteHandler:" + mRemote.getDebugName() + ":" + mRemote.getRemoteTransition(); 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 a90edf20f94e..592b22a47bc4 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 @@ -86,7 +86,16 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { @Override public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted, @Nullable SurfaceControl.Transaction finishT) { - mRequestedRemotes.remove(transition); + RemoteTransition remoteTransition = mRequestedRemotes.remove(transition); + if (remoteTransition == null) { + return; + } + + try { + remoteTransition.getRemoteTransition().onTransitionConsumed(transition, aborted); + } catch (RemoteException e) { + Log.e(TAG, "Error delegating onTransitionConsumed()", e); + } } @Override 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 c74b3f30e52d..0d9a9e9f07ff 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 @@ -277,7 +277,7 @@ public class Transitions implements RemoteCallable<Transitions>, @NonNull ShellExecutor animExecutor) { this(context, shellInit, shellController, organizer, pool, displayController, mainExecutor, mainHandler, animExecutor, null, - new RootTaskDisplayAreaOrganizer(mainExecutor, context)); + new RootTaskDisplayAreaOrganizer(mainExecutor, context, shellInit)); } public Transitions(@NonNull Context context, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java index 82fc0f49c143..aff35a347183 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java @@ -231,4 +231,9 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL int getCaptionHeightId(@WindowingMode int windowingMode) { return R.dimen.freeform_decor_caption_height; } + + @Override + int getCaptionViewId() { + return R.id.caption; + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index bf99ab35cdd7..ca91d580b4ac 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -21,6 +21,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.view.WindowInsets.Type.statusBars; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; @@ -50,6 +51,8 @@ import android.view.InputChannel; import android.view.InputEvent; import android.view.InputEventReceiver; import android.view.InputMonitor; +import android.view.InsetsSource; +import android.view.InsetsState; import android.view.MotionEvent; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; @@ -67,6 +70,7 @@ import com.android.wm.shell.R; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; 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.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; @@ -131,6 +135,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private final DesktopModeKeyguardChangeListener mDesktopModeKeyguardChangeListener = new DesktopModeKeyguardChangeListener(); private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer; + private final DisplayInsetsController mDisplayInsetsController; + private boolean mInImmersiveMode; public DesktopModeWindowDecorViewModel( Context context, @@ -141,6 +147,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { ShellTaskOrganizer taskOrganizer, DisplayController displayController, ShellController shellController, + DisplayInsetsController displayInsetsController, SyncTransactionQueue syncQueue, Transitions transitions, Optional<DesktopTasksController> desktopTasksController, @@ -156,6 +163,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { taskOrganizer, displayController, shellController, + displayInsetsController, syncQueue, transitions, desktopTasksController, @@ -176,6 +184,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { ShellTaskOrganizer taskOrganizer, DisplayController displayController, ShellController shellController, + DisplayInsetsController displayInsetsController, SyncTransactionQueue syncQueue, Transitions transitions, Optional<DesktopTasksController> desktopTasksController, @@ -191,6 +200,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mTaskOrganizer = taskOrganizer; mShellController = shellController; mDisplayController = displayController; + mDisplayInsetsController = displayInsetsController; mSyncQueue = syncQueue; mTransitions = transitions; mDesktopTasksController = desktopTasksController; @@ -213,6 +223,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } }); mShellCommandHandler.addDumpCallback(this::dump, this); + mDisplayInsetsController.addInsetsChangedListener(mContext.getDisplayId(), + new DesktopModeOnInsetsChangedListener()); } @Override @@ -655,9 +667,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private void handleReceivedMotionEvent(MotionEvent ev, InputMonitor inputMonitor) { final DesktopModeWindowDecoration relevantDecor = getRelevantWindowDecor(ev); if (DesktopModeStatus.isEnabled()) { - if (relevantDecor == null + if (!mInImmersiveMode && (relevantDecor == null || relevantDecor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM - || mTransitionDragActive) { + || mTransitionDragActive)) { handleCaptionThroughStatusBar(ev, relevantDecor); } } @@ -1051,6 +1063,35 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { return mIsKeyguardVisible && mIsKeyguardOccluded; } } + + @VisibleForTesting + class DesktopModeOnInsetsChangedListener implements + DisplayInsetsController.OnInsetsChangedListener { + @Override + public void insetsChanged(InsetsState insetsState) { + for (int i = 0; i < insetsState.sourceSize(); i++) { + final InsetsSource source = insetsState.sourceAt(i); + if (source.getType() != statusBars()) { + continue; + } + + final DesktopModeWindowDecoration decor = getFocusedDecor(); + if (decor == null) { + return; + } + // If status bar inset is visible, top task is not in immersive mode + final boolean inImmersiveMode = !source.isVisible(); + // Calls WindowDecoration#relayout if decoration visibility needs to be updated + if (inImmersiveMode != mInImmersiveMode) { + decor.relayout(decor.mTaskInfo); + mInImmersiveMode = inImmersiveMode; + } + + return; + } + } + } + } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index 380b59e84485..248e83747c48 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -638,6 +638,11 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin return loadDimensionPixelSize(mContext.getResources(), getCaptionHeightId(windowingMode)); } + @Override + int getCaptionViewId() { + return R.id.desktop_mode_caption; + } + /** * Add transition to mTransitionsPausingRelayout */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java index 09fc3dacf6f3..368231e2d7f0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java @@ -132,6 +132,13 @@ public class ResizeVeil { t.setAlpha(mVeilSurface, mVeilAnimator.getAnimatedFraction()); t.apply(); }); + mVeilAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + t.setAlpha(mVeilSurface, 1); + t.apply(); + } + }); final ValueAnimator iconAnimator = new ValueAnimator(); iconAnimator.setFloatValues(0f, 1f); @@ -192,8 +199,8 @@ public class ResizeVeil { */ public void updateResizeVeil(SurfaceControl.Transaction t, Rect newBounds) { if (mVeilAnimator != null && mVeilAnimator.isStarted()) { - // TODO(b/300145351): Investigate why ValueAnimator#end does not work here. - mVeilAnimator.setCurrentPlayTime(RESIZE_ALPHA_DURATION); + mVeilAnimator.removeAllUpdateListeners(); + mVeilAnimator.end(); } relayout(newBounds, t); mViewHost.getView().getViewRootImpl().applyTransactionOnDraw(t); 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 335a5886ba28..0548a8e751cc 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 @@ -18,6 +18,7 @@ package com.android.wm.shell.windowdecor; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.view.WindowInsets.Type.statusBars; import android.app.ActivityManager.RunningTaskInfo; import android.app.WindowConfiguration.WindowingMode; @@ -30,6 +31,8 @@ import android.graphics.Point; import android.graphics.Rect; import android.os.Binder; import android.view.Display; +import android.view.InsetsSource; +import android.view.InsetsState; import android.view.LayoutInflater; import android.view.SurfaceControl; import android.view.SurfaceControlViewHost; @@ -119,6 +122,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> private WindowlessWindowManager mCaptionWindowManager; private SurfaceControlViewHost mViewHost; private Configuration mWindowDecorConfig; + private boolean mIsCaptionVisible; private final Binder mOwner = new Binder(); private final Rect mCaptionInsetsRect = new Rect(); @@ -225,6 +229,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> .inflate(params.mLayoutResId, null); } + updateCaptionVisibility(outResult.mRootView, mTaskInfo.displayId); + final Resources resources = mDecorWindowContext.getResources(); final Configuration taskConfig = mTaskInfo.getConfiguration(); final Rect taskBounds = taskConfig.windowConfiguration.getBounds(); @@ -272,12 +278,20 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> // Caption insets mCaptionInsetsRect.set(taskBounds); - mCaptionInsetsRect.bottom = mCaptionInsetsRect.top + captionHeight + params.mCaptionY; - wct.addInsetsSource(mTaskInfo.token, - mOwner, 0 /* index */, WindowInsets.Type.captionBar(), mCaptionInsetsRect); - wct.addInsetsSource(mTaskInfo.token, - mOwner, 0 /* index */, WindowInsets.Type.mandatorySystemGestures(), - mCaptionInsetsRect); + if (mIsCaptionVisible) { + mCaptionInsetsRect.bottom = + mCaptionInsetsRect.top + captionHeight + params.mCaptionY; + wct.addInsetsSource(mTaskInfo.token, + mOwner, 0 /* index */, WindowInsets.Type.captionBar(), mCaptionInsetsRect); + wct.addInsetsSource(mTaskInfo.token, + mOwner, 0 /* index */, WindowInsets.Type.mandatorySystemGestures(), + mCaptionInsetsRect); + } else { + wct.removeInsetsSource(mTaskInfo.token, mOwner, 0 /* index */, + WindowInsets.Type.captionBar()); + wct.removeInsetsSource(mTaskInfo.token, mOwner, 0 /* index */, + WindowInsets.Type.mandatorySystemGestures()); + } } else { startT.hide(mCaptionContainerSurface); } @@ -348,10 +362,41 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> } } + /** + * Checks if task has entered/exited immersive mode and requires a change in caption visibility. + */ + private void updateCaptionVisibility(View rootView, int displayId) { + final InsetsState insetsState = mDisplayController.getInsetsState(displayId); + for (int i = 0; i < insetsState.sourceSize(); i++) { + final InsetsSource source = insetsState.sourceAt(i); + if (source.getType() != statusBars()) { + continue; + } + + mIsCaptionVisible = source.isVisible(); + setCaptionVisibility(rootView, mIsCaptionVisible); + + return; + } + } + + private void setCaptionVisibility(View rootView, boolean visible) { + if (rootView == null) { + return; + } + final int v = visible ? View.VISIBLE : View.GONE; + final View captionView = rootView.findViewById(getCaptionViewId()); + captionView.setVisibility(v); + } + int getCaptionHeightId(@WindowingMode int windowingMode) { return Resources.ID_NULL; } + int getCaptionViewId() { + return Resources.ID_NULL; + } + /** * Obtains the {@link Display} instance for the display ID in {@link #mTaskInfo} if it exists or * registers {@link #mOnDisplaysChangedListener} if it doesn't. @@ -466,7 +511,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> */ public void addCaptionInset(WindowContainerTransaction wct) { final int captionHeightId = getCaptionHeightId(mTaskInfo.getWindowingMode()); - if (!ViewRootImpl.CAPTION_ON_SHELL || captionHeightId == Resources.ID_NULL) { + if (!ViewRootImpl.CAPTION_ON_SHELL || captionHeightId == Resources.ID_NULL + || !mIsCaptionVisible) { return; } diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp index 0058d115ce56..7f020725d61f 100644 --- a/libs/WindowManager/Shell/tests/flicker/Android.bp +++ b/libs/WindowManager/Shell/tests/flicker/Android.bp @@ -116,6 +116,7 @@ java_library { "wm-flicker-common-assertions", "launcher-helper-lib", "launcher-aosp-tapl", + "com_android_wm_shell_flags_lib", ], } 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/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt index 6748626d4e46..0fd1b2c3f0de 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt @@ -18,6 +18,7 @@ package com.android.wm.shell.flicker.pip import android.platform.test.annotations.Presubmit import android.tools.common.Rotation +import android.tools.common.flicker.subject.exceptions.IncorrectRegionException import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest @@ -40,14 +41,26 @@ class PipPinchInTest(flicker: LegacyFlickerTest) : PipTransition(flicker) { transitions { pipApp.pinchInPipWindow(wmHelper, 0.4f, 30) } } - /** Checks that the visible region area of [pipApp] always decreases during the animation. */ + /** + * Checks that the visible region area of [pipApp] decreases + * and then increases during the animation. + */ @Presubmit @Test - fun pipLayerAreaDecreases() { + fun pipLayerAreaDecreasesThenIncreases() { + val isAreaDecreasing = arrayOf(true) flicker.assertLayers { val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible } pipLayerList.zipWithNext { previous, current -> - current.visibleRegion.notBiggerThan(previous.visibleRegion.region) + if (isAreaDecreasing[0]) { + try { + current.visibleRegion.notBiggerThan(previous.visibleRegion.region) + } catch (e: IncorrectRegionException) { + isAreaDecreasing[0] = false + } + } else { + previous.visibleRegion.notBiggerThan(current.visibleRegion.region) + } } } } diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp index d09a90cd7dc7..aadadd604d3e 100644 --- a/libs/WindowManager/Shell/tests/unittest/Android.bp +++ b/libs/WindowManager/Shell/tests/unittest/Android.bp @@ -35,6 +35,7 @@ android_test { static_libs: [ "WindowManager-Shell", "junit", + "flag-junit-base", "androidx.test.runner", "androidx.test.rules", "androidx.test.ext.junit", @@ -49,6 +50,7 @@ android_test { "testables", "platform-test-annotations", "servicestests-utils", + "com_android_wm_shell_flags_lib", ], libs: [ diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java index d34e27b57071..db98abbbcbf1 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java @@ -24,6 +24,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import android.content.ComponentName; +import android.graphics.Point; import android.graphics.Rect; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -60,6 +61,9 @@ public class PipBoundsStateTest extends ShellTestCase { /** The minimum possible size of the override min size's width or height */ private static final int OVERRIDABLE_MIN_SIZE = 40; + /** The margin of error for floating point results. */ + private static final float MARGIN_OF_ERROR = 0.05f; + private PipBoundsState mPipBoundsState; private SizeSpecSource mSizeSpecSource; private ComponentName mTestComponentName1; @@ -88,6 +92,27 @@ public class PipBoundsStateTest extends ShellTestCase { } @Test + public void testBoundsScale() { + mPipBoundsState.setMaxSize(300, 300); + mPipBoundsState.setBounds(new Rect(0, 0, 100, 100)); + + final int currentWidth = mPipBoundsState.getBounds().width(); + final Point maxSize = mPipBoundsState.getMaxSize(); + final float expectedBoundsScale = Math.min((float) currentWidth / maxSize.x, 1.0f); + + // test for currentWidth < maxWidth + assertEquals(expectedBoundsScale, mPipBoundsState.getBoundsScale(), MARGIN_OF_ERROR); + + // reset the bounds to be at the maximum size spec + mPipBoundsState.setBounds(new Rect(0, 0, maxSize.x, maxSize.y)); + assertEquals(1.0f, mPipBoundsState.getBoundsScale(), /* delta */ 0f); + + // reset the bounds to be over the maximum size spec + mPipBoundsState.setBounds(new Rect(0, 0, maxSize.x * 2, maxSize.y * 2)); + assertEquals(1.0f, mPipBoundsState.getBoundsScale(), /* delta */ 0f); + } + + @Test public void testSetReentryState() { final Size size = new Size(100, 100); final float snapFraction = 0.5f; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java index 6777a5bd8ceb..9719ba89b4bb 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java @@ -28,11 +28,13 @@ import static org.mockito.Mockito.verify; import android.graphics.Rect; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import android.testing.TestableResources; import android.view.MotionEvent; import android.view.ViewConfiguration; import androidx.test.filters.SmallTest; +import com.android.wm.shell.R; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.ShellExecutor; @@ -98,6 +100,9 @@ public class PipResizeGestureHandlerTest extends ShellTestCase { @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + final TestableResources res = mContext.getOrCreateTestableResources(); + res.addOverride(R.bool.config_pipEnablePinchResize, true); + mPipDisplayLayoutState = new PipDisplayLayoutState(mContext); mSizeSpecSource = new PhoneSizeSpecSource(mContext, mPipDisplayLayoutState); mPipBoundsState = new PipBoundsState(mContext, mSizeSpecSource, mPipDisplayLayoutState); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java index ebc284b1b77e..befc702b01aa 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java @@ -208,6 +208,30 @@ public class SplitTransitionTests extends ShellTestCase { @Test @UiThreadTest + public void testRemoteTransitionConsumed() { + // Omit side child change + TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0) + .addChange(TRANSIT_OPEN, mMainChild) + .build(); + TestRemoteTransition testRemote = new TestRemoteTransition(); + + IBinder transition = mSplitScreenTransitions.startEnterTransition( + TRANSIT_OPEN, new WindowContainerTransaction(), + new RemoteTransition(testRemote, "Test"), mStageCoordinator, + TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false); + mMainStage.onTaskAppeared(mMainChild, createMockSurface()); + boolean accepted = mStageCoordinator.startAnimation(transition, info, + mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class), + mock(Transitions.TransitionFinishCallback.class)); + assertTrue(accepted); + + assertTrue(testRemote.isConsumed()); + + } + + @Test + @UiThreadTest public void testMonitorInSplit() { enterSplit(); 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 d5986780d5d8..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 @@ -50,6 +50,7 @@ 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.ArgumentMatchers.isA; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; @@ -92,6 +93,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; +import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.DisplayController; @@ -145,7 +147,9 @@ public class ShellTransitionTests extends ShellTestCase { final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class), mOrganizer, mTransactionPool, createTestDisplayController(), mMainExecutor, mMainHandler, mAnimExecutor); - verify(shellInit, times(1)).addInitCallback(any(), eq(t)); + // One from Transitions, one from RootTaskDisplayAreaOrganizer + verify(shellInit).addInitCallback(any(), eq(t)); + verify(shellInit).addInitCallback(any(), isA(RootTaskDisplayAreaOrganizer.class)); } @Test @@ -285,6 +289,10 @@ public class ShellTransitionTests extends ShellTestCase { SurfaceControl.Transaction t, IBinder mergeTarget, IRemoteTransitionFinishedCallback finishCallback) throws RemoteException { } + + @Override + public void onTransitionConsumed(IBinder iBinder, boolean b) throws RemoteException { + } }; IBinder transitToken = new Binder(); transitions.requestStartTransition(transitToken, @@ -427,6 +435,10 @@ public class ShellTransitionTests extends ShellTestCase { SurfaceControl.Transaction t, IBinder mergeTarget, IRemoteTransitionFinishedCallback finishCallback) throws RemoteException { } + + @Override + public void onTransitionConsumed(IBinder iBinder, boolean b) throws RemoteException { + } }; TransitionFilter filter = new TransitionFilter(); @@ -473,6 +485,10 @@ public class ShellTransitionTests extends ShellTestCase { SurfaceControl.Transaction t, IBinder mergeTarget, IRemoteTransitionFinishedCallback finishCallback) throws RemoteException { } + + @Override + public void onTransitionConsumed(IBinder iBinder, boolean b) throws RemoteException { + } }; final int transitType = TRANSIT_FIRST_CUSTOM + 1; @@ -1153,7 +1169,7 @@ public class ShellTransitionTests extends ShellTestCase { } @Test - public void testEmptyTransitionStillReportsKeyguardGoingAway() { + public void testEmptyTransition_withKeyguardGoingAway_plays() { Transitions transitions = createTestTransitions(); transitions.replaceDefaultHandlerForTest(mDefaultHandler); @@ -1172,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/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TestRemoteTransition.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TestRemoteTransition.java index 39ab23877c68..87330d2dc877 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TestRemoteTransition.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TestRemoteTransition.java @@ -31,6 +31,7 @@ import android.window.WindowContainerTransaction; */ public class TestRemoteTransition extends IRemoteTransition.Stub { private boolean mCalled = false; + private boolean mConsumed = false; final WindowContainerTransaction mRemoteFinishWCT = new WindowContainerTransaction(); @Override @@ -48,6 +49,11 @@ public class TestRemoteTransition extends IRemoteTransition.Stub { IRemoteTransitionFinishedCallback finishCallback) throws RemoteException { } + @Override + public void onTransitionConsumed(IBinder iBinder, boolean b) throws RemoteException { + mConsumed = true; + } + /** * Check whether this remote transition * {@link #startAnimation(IBinder, TransitionInfo, SurfaceControl.Transaction, @@ -56,4 +62,12 @@ public class TestRemoteTransition extends IRemoteTransition.Stub { public boolean isCalled() { return mCalled; } + + /** + * Check whether this remote transition's {@link #onTransitionConsumed(IBinder, boolean)} + * is called + */ + public boolean isConsumed() { + return mConsumed; + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt index 8eaf5a004c0a..57aa47e85556 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt @@ -33,8 +33,12 @@ import android.view.Choreographer import android.view.Display.DEFAULT_DISPLAY import android.view.InputChannel import android.view.InputMonitor +import android.view.InsetsSource +import android.view.InsetsState import android.view.SurfaceControl import android.view.SurfaceView +import android.view.WindowInsets.Type.navigationBars +import android.view.WindowInsets.Type.statusBars import androidx.core.content.getSystemService import androidx.test.filters.SmallTest import com.android.wm.shell.RootTaskDisplayAreaOrganizer @@ -42,6 +46,7 @@ import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase import com.android.wm.shell.TestRunningTaskInfoBuilder 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.ShellExecutor import com.android.wm.shell.common.SyncTransactionQueue @@ -53,10 +58,12 @@ import com.android.wm.shell.sysui.ShellCommandHandler import com.android.wm.shell.sysui.ShellController import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions +import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock +import org.mockito.Mockito.anyInt import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.times @@ -68,6 +75,7 @@ import org.mockito.kotlin.whenever import java.util.Optional import java.util.function.Supplier + /** Tests of [DesktopModeWindowDecorViewModel] */ @SmallTest @RunWith(AndroidTestingRunner::class) @@ -80,6 +88,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { @Mock private lateinit var mockTaskOrganizer: ShellTaskOrganizer @Mock private lateinit var mockDisplayController: DisplayController @Mock private lateinit var mockDisplayLayout: DisplayLayout + @Mock private lateinit var displayInsetsController: DisplayInsetsController @Mock private lateinit var mockSyncQueue: SyncTransactionQueue @Mock private lateinit var mockDesktopTasksController: DesktopTasksController @Mock private lateinit var mockInputMonitor: InputMonitor @@ -97,6 +106,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { } private lateinit var shellInit: ShellInit + private lateinit var desktopModeOnInsetsChangedListener: DesktopModeOnInsetsChangedListener private lateinit var desktopModeWindowDecorViewModel: DesktopModeWindowDecorViewModel @Before @@ -111,6 +121,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { mockTaskOrganizer, mockDisplayController, mockShellController, + displayInsetsController, mockSyncQueue, mockTransitions, Optional.of(mockDesktopTasksController), @@ -131,6 +142,11 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { whenever(mockInputMonitor.inputChannel).thenReturn(inputChannels[1]) shellInit.init() + + val listenerCaptor = + argumentCaptor<DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener>() + verify(displayInsetsController).addInsetsChangedListener(anyInt(), listenerCaptor.capture()) + desktopModeOnInsetsChangedListener = listenerCaptor.firstValue } @Test @@ -274,6 +290,67 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { verify(decoration).addTransitionPausingRelayout(transition) } + @Test + fun testRelayoutRunsWhenStatusBarsInsetsSourceVisibilityChanges() { + val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM, focused = true) + val decoration = setUpMockDecorationForTask(task) + + onTaskOpening(task) + + // Add status bar insets source + val insetsState = InsetsState() + val statusBarInsetsSourceId = 0 + val statusBarInsetsSource = InsetsSource(statusBarInsetsSourceId, statusBars()) + statusBarInsetsSource.isVisible = false + insetsState.addSource(statusBarInsetsSource) + + desktopModeOnInsetsChangedListener.insetsChanged(insetsState) + + // Verify relayout occurs when status bar inset visibility changes + verify(decoration, times(1)).relayout(task) + } + + @Test + fun testRelayoutDoesNotRunWhenNonStatusBarsInsetsSourceVisibilityChanges() { + val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM, focused = true) + val decoration = setUpMockDecorationForTask(task) + + onTaskOpening(task) + + // Add navigation bar insets source + val insetsState = InsetsState() + val navigationBarInsetsSourceId = 1 + val navigationBarInsetsSource = InsetsSource(navigationBarInsetsSourceId, navigationBars()) + navigationBarInsetsSource.isVisible = false + insetsState.addSource(navigationBarInsetsSource) + + desktopModeOnInsetsChangedListener.insetsChanged(insetsState) + + // Verify relayout does not occur when non-status bar inset changes visibility + verify(decoration, never()).relayout(task) + } + + @Test + fun testRelayoutDoesNotRunWhenNonStatusBarsInsetSourceVisibilityDoesNotChange() { + val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM, focused = true) + val decoration = setUpMockDecorationForTask(task) + + onTaskOpening(task) + + // Add status bar insets source + val insetsState = InsetsState() + val statusBarInsetsSourceId = 0 + val statusBarInsetsSource = InsetsSource(statusBarInsetsSourceId, statusBars()) + statusBarInsetsSource.isVisible = false + insetsState.addSource(statusBarInsetsSource) + + desktopModeOnInsetsChangedListener.insetsChanged(insetsState) + desktopModeOnInsetsChangedListener.insetsChanged(insetsState) + + // Verify relayout runs only once when status bar inset visibility changes. + verify(decoration, times(1)).relayout(task) + } + private fun onTaskOpening(task: RunningTaskInfo, leash: SurfaceControl = SurfaceControl()) { desktopModeWindowDecorViewModel.onTaskOpening( task, @@ -313,6 +390,8 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { whenever(mockDesktopModeWindowDecorFactory.create( any(), any(), any(), eq(task), any(), any(), any(), any(), any()) ).thenReturn(decoration) + decoration.mTaskInfo = task + whenever(decoration.isFocused).thenReturn(task.isFocused) return decoration } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java index fcb7863429d6..8061aa3f844a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java @@ -18,6 +18,9 @@ package com.android.wm.shell.windowdecor; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.view.WindowInsets.Type.captionBar; +import static android.view.WindowInsets.Type.mandatorySystemGestures; +import static android.view.WindowInsets.Type.statusBars; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.wm.shell.MockSurfaceControlHelper.createMockSurfaceControlBuilder; @@ -25,6 +28,8 @@ import static com.android.wm.shell.MockSurfaceControlHelper.createMockSurfaceCon import static com.google.common.truth.Truth.assertThat; +import static junit.framework.Assert.assertTrue; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.anyInt; @@ -51,6 +56,7 @@ import android.testing.AndroidTestingRunner; import android.util.DisplayMetrics; import android.view.AttachedSurfaceControl; import android.view.Display; +import android.view.InsetsState; import android.view.SurfaceControl; import android.view.SurfaceControlViewHost; import android.view.View; @@ -94,6 +100,7 @@ public class WindowDecorationTests extends ShellTestCase { private static final Rect TASK_BOUNDS = new Rect(100, 300, 400, 400); private static final Point TASK_POSITION_IN_PARENT = new Point(40, 60); private static final int CORNER_RADIUS = 20; + private static final int STATUS_BAR_INSET_SOURCE_ID = 0; private final WindowDecoration.RelayoutResult<TestView> mRelayoutResult = new WindowDecoration.RelayoutResult<>(); @@ -118,6 +125,7 @@ public class WindowDecorationTests extends ShellTestCase { private final List<SurfaceControl.Transaction> mMockSurfaceControlTransactions = new ArrayList<>(); private final List<SurfaceControl.Builder> mMockSurfaceControlBuilders = new ArrayList<>(); + private final InsetsState mInsetsState = new InsetsState(); private SurfaceControl.Transaction mMockSurfaceControlStartT; private SurfaceControl.Transaction mMockSurfaceControlFinishT; private SurfaceControl.Transaction mMockSurfaceControlAddWindowT; @@ -141,6 +149,11 @@ public class WindowDecorationTests extends ShellTestCase { .create(any(), any(), any()); when(mMockSurfaceControlViewHost.getRootSurfaceControl()) .thenReturn(mMockRootSurfaceControl); + when(mMockView.findViewById(anyInt())).thenReturn(mMockView); + + // Add status bar inset so that WindowDecoration does not think task is in immersive mode + mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, statusBars()).setVisible(true); + doReturn(mInsetsState).when(mMockDisplayController).getInsetsState(anyInt()); } @Test @@ -537,12 +550,41 @@ public class WindowDecorationTests extends ShellTestCase { windowDecor.relayout(taskInfo); - verify(mMockSurfaceControlStartT).setColor(taskSurface, new float[] {1.f, 1.f, 0.f}); + verify(mMockSurfaceControlStartT).setColor(taskSurface, new float[]{1.f, 1.f, 0.f}); mockitoSession.finishMocking(); } @Test + public void testInsetsAddedWhenCaptionIsVisible() { + final Display defaultDisplay = mock(Display.class); + doReturn(defaultDisplay).when(mMockDisplayController) + .getDisplay(Display.DEFAULT_DISPLAY); + + final ActivityManager.TaskDescription.Builder taskDescriptionBuilder = + new ActivityManager.TaskDescription.Builder(); + final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder() + .setDisplayId(Display.DEFAULT_DISPLAY) + .setTaskDescriptionBuilder(taskDescriptionBuilder) + .setVisible(true) + .build(); + final SurfaceControl taskSurface = mock(SurfaceControl.class); + final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface); + + assertTrue(mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, statusBars()) + .isVisible()); + assertTrue(mInsetsState.sourceSize() == 1); + assertTrue(mInsetsState.sourceAt(0).getType() == statusBars()); + + windowDecor.relayout(taskInfo); + + verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(), + eq(0) /* index */, eq(captionBar()), any()); + verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(), + eq(0) /* index */, eq(mandatorySystemGestures()), any()); + } + + @Test public void testRelayout_fluidResizeEnabled_fullscreenTask_clearTaskSurfaceColor() { StaticMockitoSession mockitoSession = mockitoSession().mockStatic( DesktopModeStatus.class).strictness(LENIENT).startMocking(); @@ -581,6 +623,33 @@ public class WindowDecorationTests extends ShellTestCase { mockitoSession.finishMocking(); } + + @Test + public void testInsetsRemovedWhenCaptionIsHidden() { + final Display defaultDisplay = mock(Display.class); + doReturn(defaultDisplay).when(mMockDisplayController) + .getDisplay(Display.DEFAULT_DISPLAY); + + mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, captionBar()).setVisible(false); + + final ActivityManager.TaskDescription.Builder taskDescriptionBuilder = + new ActivityManager.TaskDescription.Builder(); + final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder() + .setDisplayId(Display.DEFAULT_DISPLAY) + .setTaskDescriptionBuilder(taskDescriptionBuilder) + .setVisible(true) + .build(); + final SurfaceControl taskSurface = mock(SurfaceControl.class); + final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface); + + windowDecor.relayout(taskInfo); + + verify(mMockWindowContainerTransaction).removeInsetsSource(eq(taskInfo.token), any(), + eq(0) /* index */, eq(captionBar())); + verify(mMockWindowContainerTransaction).removeInsetsSource(eq(taskInfo.token), any(), + eq(0) /* index */, eq(mandatorySystemGestures())); + } + private TestWindowDecoration createWindowDecoration( ActivityManager.RunningTaskInfo taskInfo, SurfaceControl testSurface) { return new TestWindowDecoration(mContext, mMockDisplayController, mMockShellTaskOrganizer, 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 c396a2032eed..794082124344 100644 --- a/libs/hwui/api/current.txt +++ b/libs/hwui/api/current.txt @@ -1,4 +1,6 @@ // 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 d802177e249b..14191ebcb080 100644 --- a/libs/hwui/api/module-lib-current.txt +++ b/libs/hwui/api/module-lib-current.txt @@ -1 +1,3 @@ // 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 d802177e249b..14191ebcb080 100644 --- a/libs/hwui/api/module-lib-removed.txt +++ b/libs/hwui/api/module-lib-removed.txt @@ -1 +1,3 @@ // 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 d802177e249b..14191ebcb080 100644 --- a/libs/hwui/api/removed.txt +++ b/libs/hwui/api/removed.txt @@ -1 +1,3 @@ // 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 d802177e249b..14191ebcb080 100644 --- a/libs/hwui/api/system-current.txt +++ b/libs/hwui/api/system-current.txt @@ -1 +1,3 @@ // 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 d802177e249b..14191ebcb080 100644 --- a/libs/hwui/api/system-removed.txt +++ b/libs/hwui/api/system-removed.txt @@ -1 +1,3 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp index 7aef7a51b90c..14639456f13b 100644 --- a/libs/hwui/jni/Paint.cpp +++ b/libs/hwui/jni/Paint.cpp @@ -593,7 +593,7 @@ namespace PaintGlue { return result; } - static SkScalar getMetricsInternal(jlong paintHandle, SkFontMetrics *metrics) { + static SkScalar getMetricsInternal(jlong paintHandle, SkFontMetrics* metrics, bool useLocale) { const int kElegantTop = 2500; const int kElegantBottom = -1000; const int kElegantAscent = 1900; @@ -622,6 +622,17 @@ namespace PaintGlue { metrics->fLeading = size * kElegantLeading / 2048; spacing = metrics->fDescent - metrics->fAscent + metrics->fLeading; } + + if (useLocale) { + minikin::MinikinPaint minikinPaint = MinikinUtils::prepareMinikinPaint(paint, typeface); + minikin::MinikinExtent extent = + typeface->fFontCollection->getReferenceExtentForLocale(minikinPaint); + metrics->fAscent = std::min(extent.ascent, metrics->fAscent); + metrics->fDescent = std::max(extent.descent, metrics->fDescent); + metrics->fTop = std::min(metrics->fAscent, metrics->fTop); + metrics->fBottom = std::max(metrics->fDescent, metrics->fBottom); + } + return spacing; } @@ -634,7 +645,7 @@ namespace PaintGlue { MinikinUtils::getFontExtent(paint, bidiFlags, typeface, buf, start, count, bufSize); SkFontMetrics metrics; - getMetricsInternal(paintHandle, &metrics); + getMetricsInternal(paintHandle, &metrics, false /* useLocale */); metrics.fAscent = extent.ascent; metrics.fDescent = extent.descent; @@ -686,20 +697,21 @@ namespace PaintGlue { } } - static jfloat getFontMetrics(JNIEnv* env, jobject, jlong paintHandle, jobject metricsObj) { + static jfloat getFontMetrics(JNIEnv* env, jobject, jlong paintHandle, jobject metricsObj, + jboolean useLocale) { SkFontMetrics metrics; - SkScalar spacing = getMetricsInternal(paintHandle, &metrics); + SkScalar spacing = getMetricsInternal(paintHandle, &metrics, useLocale); GraphicsJNI::set_metrics(env, metricsObj, metrics); return SkScalarToFloat(spacing); } - static jint getFontMetricsInt(JNIEnv* env, jobject, jlong paintHandle, jobject metricsObj) { + static jint getFontMetricsInt(JNIEnv* env, jobject, jlong paintHandle, jobject metricsObj, + jboolean useLocale) { SkFontMetrics metrics; - getMetricsInternal(paintHandle, &metrics); + getMetricsInternal(paintHandle, &metrics, useLocale); return GraphicsJNI::set_metrics_int(env, metricsObj, metrics); } - // ------------------ @CriticalNative --------------------------- static void reset(CRITICAL_JNI_PARAMS_COMMA jlong objHandle) { @@ -1002,19 +1014,19 @@ namespace PaintGlue { static jfloat ascent(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) { SkFontMetrics metrics; - getMetricsInternal(paintHandle, &metrics); + getMetricsInternal(paintHandle, &metrics, false /* useLocale */); return SkScalarToFloat(metrics.fAscent); } static jfloat descent(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) { SkFontMetrics metrics; - getMetricsInternal(paintHandle, &metrics); + getMetricsInternal(paintHandle, &metrics, false /* useLocale */); return SkScalarToFloat(metrics.fDescent); } static jfloat getUnderlinePosition(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) { SkFontMetrics metrics; - getMetricsInternal(paintHandle, &metrics); + getMetricsInternal(paintHandle, &metrics, false /* useLocale */); SkScalar position; if (metrics.hasUnderlinePosition(&position)) { return SkScalarToFloat(position); @@ -1026,7 +1038,7 @@ namespace PaintGlue { static jfloat getUnderlineThickness(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) { SkFontMetrics metrics; - getMetricsInternal(paintHandle, &metrics); + getMetricsInternal(paintHandle, &metrics, false /* useLocale */); SkScalar thickness; if (metrics.hasUnderlineThickness(&thickness)) { return SkScalarToFloat(thickness); @@ -1121,9 +1133,9 @@ static const JNINativeMethod methods[] = { {"nSetTextLocales", "(JLjava/lang/String;)I", (void*)PaintGlue::setTextLocales}, {"nSetFontFeatureSettings", "(JLjava/lang/String;)V", (void*)PaintGlue::setFontFeatureSettings}, - {"nGetFontMetrics", "(JLandroid/graphics/Paint$FontMetrics;)F", + {"nGetFontMetrics", "(JLandroid/graphics/Paint$FontMetrics;Z)F", (void*)PaintGlue::getFontMetrics}, - {"nGetFontMetricsInt", "(JLandroid/graphics/Paint$FontMetricsInt;)I", + {"nGetFontMetricsInt", "(JLandroid/graphics/Paint$FontMetricsInt;Z)I", (void*)PaintGlue::getFontMetricsInt}, // --------------- @CriticalNative ------------------ 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/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java index 230fb07f9f43..bf9419fe6603 100644 --- a/media/java/android/media/AudioAttributes.java +++ b/media/java/android/media/AudioAttributes.java @@ -875,18 +875,7 @@ public final class AudioAttributes implements Parcelable { /** * Sets the attribute describing what is the intended use of the audio signal, * such as alarm or ringtone. - * @param usage one of {@link AttributeSdkUsage#USAGE_UNKNOWN}, - * {@link AttributeSdkUsage#USAGE_MEDIA}, - * {@link AttributeSdkUsage#USAGE_VOICE_COMMUNICATION}, - * {@link AttributeSdkUsage#USAGE_VOICE_COMMUNICATION_SIGNALLING}, - * {@link AttributeSdkUsage#USAGE_ALARM}, {@link AudioAttributes#USAGE_NOTIFICATION}, - * {@link AttributeSdkUsage#USAGE_NOTIFICATION_RINGTONE}, - * {@link AttributeSdkUsage#USAGE_NOTIFICATION_EVENT}, - * {@link AttributeSdkUsage#USAGE_ASSISTANT}, - * {@link AttributeSdkUsage#USAGE_ASSISTANCE_ACCESSIBILITY}, - * {@link AttributeSdkUsage#USAGE_ASSISTANCE_NAVIGATION_GUIDANCE}, - * {@link AttributeSdkUsage#USAGE_ASSISTANCE_SONIFICATION}, - * {@link AttributeSdkUsage#USAGE_GAME}. + * @param usage the usage to set. * @return the same Builder instance. */ public Builder setUsage(@AttributeSdkUsage int usage) { diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java index a311296dd90c..b002bbf20c08 100644 --- a/media/java/android/media/AudioFormat.java +++ b/media/java/android/media/AudioFormat.java @@ -1282,11 +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 {@link AudioSystem#OUT_CHANNEL_COUNT_MAX} 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/AudioManager.java b/media/java/android/media/AudioManager.java index adc0e16448ee..5b880797b7fd 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -1473,8 +1473,7 @@ public class AudioManager { * Returns the volume group id associated to the given {@link AudioAttributes}. * * @param attributes The {@link AudioAttributes} to consider. - * @return {@link android.media.audiopolicy.AudioVolumeGroup} id supporting the given - * {@link AudioAttributes} if found, + * @return audio volume group id supporting the given {@link AudioAttributes} if found, * {@code android.media.audiopolicy.AudioVolumeGroup.DEFAULT_VOLUME_GROUP} otherwise. */ public int getVolumeGroupIdForAttributes(@NonNull AudioAttributes attributes) { @@ -1589,7 +1588,7 @@ public class AudioManager { * <p> Call first in prior {@link #getVolumeGroupIdForAttributes(AudioAttributes)} to retrieve * the volume group id supporting the given {@link AudioAttributes}. * - * @param groupId of the {@link android.media.audiopolicy.AudioVolumeGroup} to consider. + * @param groupId of the audio volume group to consider. * @param direction The direction to adjust the volume. One of * {@link #ADJUST_LOWER}, {@link #ADJUST_RAISE}, or * {@link #ADJUST_SAME}. @@ -1633,8 +1632,8 @@ public class AudioManager { * <p> Call first in prior {@link #getVolumeGroupIdForAttributes(AudioAttributes)} to retrieve * the volume group id supporting the given {@link AudioAttributes}. * - * @param groupId of the {@link android.media.audiopolicy.AudioVolumeGroup} to consider. - * @return The mute state for the given {@link android.media.audiopolicy.AudioVolumeGroup} id. + * @param groupId of the audio volume group to consider. + * @return The mute state for the given audio volume group id. * @see #adjustVolumeGroupVolume(int, int, int) */ public boolean isVolumeGroupMuted(int groupId) { @@ -4659,24 +4658,24 @@ public class AudioManager { Objects.requireNonNull(afr); Objects.requireNonNull(clientFakeId); int status; - try { - status = getService().requestAudioFocusForTest(afr.getAudioAttributes(), - afr.getFocusGain(), - mICallBack, - mAudioFocusDispatcher, - clientFakeId, "com.android.test.fakeclient", - afr.getFlags() | AudioManager.AUDIOFOCUS_FLAG_TEST, - clientFakeUid, clientTargetSdk); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - if (status != AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY) { - // default path with no external focus policy - return status; - } - BlockingFocusResultReceiver focusReceiver; synchronized (mFocusRequestsLock) { + try { + status = getService().requestAudioFocusForTest(afr.getAudioAttributes(), + afr.getFocusGain(), + mICallBack, + mAudioFocusDispatcher, + clientFakeId, "com.android.test.fakeclient", + afr.getFlags() | AudioManager.AUDIOFOCUS_FLAG_TEST, + clientFakeUid, clientTargetSdk); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + if (status != AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY) { + // default path with no external focus policy + return status; + } + focusReceiver = addClientIdToFocusReceiverLocked(clientFakeId); } diff --git a/media/java/android/media/AudioMetadata.java b/media/java/android/media/AudioMetadata.java index 0f962f9e9d4b..4e61549a5e5a 100644 --- a/media/java/android/media/AudioMetadata.java +++ b/media/java/android/media/AudioMetadata.java @@ -226,16 +226,15 @@ public final class AudioMetadata { * * An Integer value representing presentation content classifier. * - * @see AudioPresentation.ContentClassifier - * One of {@link AudioPresentation#CONTENT_UNKNOWN}, - * {@link AudioPresentation#CONTENT_MAIN}, - * {@link AudioPresentation#CONTENT_MUSIC_AND_EFFECTS}, - * {@link AudioPresentation#CONTENT_VISUALLY_IMPAIRED}, - * {@link AudioPresentation#CONTENT_HEARING_IMPAIRED}, - * {@link AudioPresentation#CONTENT_DIALOG}, - * {@link AudioPresentation#CONTENT_COMMENTARY}, - * {@link AudioPresentation#CONTENT_EMERGENCY}, - * {@link AudioPresentation#CONTENT_VOICEOVER}. + * @see AudioPresentation#CONTENT_UNKNOWN + * @see AudioPresentation#CONTENT_MAIN + * @see AudioPresentation#CONTENT_MUSIC_AND_EFFECTS + * @see AudioPresentation#CONTENT_VISUALLY_IMPAIRED + * @see AudioPresentation#CONTENT_HEARING_IMPAIRED + * @see AudioPresentation#CONTENT_DIALOG + * @see AudioPresentation#CONTENT_COMMENTARY + * @see AudioPresentation#CONTENT_EMERGENCY + * @see AudioPresentation#CONTENT_VOICEOVER */ @NonNull public static final Key<Integer> KEY_PRESENTATION_CONTENT_CLASSIFIER = createKey("presentation-content-classifier", Integer.class); diff --git a/media/java/android/media/IRingtonePlayer.aidl b/media/java/android/media/IRingtonePlayer.aidl index 73f15f21596c..1e57be2c1e22 100644 --- a/media/java/android/media/IRingtonePlayer.aidl +++ b/media/java/android/media/IRingtonePlayer.aidl @@ -49,7 +49,7 @@ interface IRingtonePlayer { oneway void setHapticGeneratorEnabled(IBinder token, boolean hapticGeneratorEnabled); /** Used for Notification sound playback. */ - oneway void playAsync(in Uri uri, in UserHandle user, boolean looping, in AudioAttributes aa); + oneway void playAsync(in Uri uri, in UserHandle user, boolean looping, in AudioAttributes aa, float volume); oneway void stopAsync(); /** Return the title of the media. */ 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/midi/MidiUmpDeviceService.java b/media/java/android/media/midi/MidiUmpDeviceService.java index 6e2aaabf4b04..bbbe7f683b05 100644 --- a/media/java/android/media/midi/MidiUmpDeviceService.java +++ b/media/java/android/media/midi/MidiUmpDeviceService.java @@ -38,7 +38,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 diff --git a/media/java/android/media/projection/IMediaProjectionManager.aidl b/media/java/android/media/projection/IMediaProjectionManager.aidl index d294601b44cc..80e22477efed 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)") + void notifyPermissionRequestStateChange(int hostUid, int state, int sessionCreationSource); } diff --git a/media/java/android/media/projection/OWNERS b/media/java/android/media/projection/OWNERS index cc9be9c5126a..880ec8fdef88 100644 --- a/media/java/android/media/projection/OWNERS +++ b/media/java/android/media/projection/OWNERS @@ -4,3 +4,4 @@ michaelwr@google.com santoscordon@google.com chaviw@google.com nmusgrave@google.com +dakinola@google.com diff --git a/media/java/android/media/tv/SectionRequest.java b/media/java/android/media/tv/SectionRequest.java index 078e83222e4e..ec0d7f7a2ce4 100644 --- a/media/java/android/media/tv/SectionRequest.java +++ b/media/java/android/media/tv/SectionRequest.java @@ -81,7 +81,7 @@ public final class SectionRequest extends BroadcastInfoRequest implements Parcel /** * Gets the version number of requested session. If it is null, value will be -1. * <p>The consistency of version numbers between request and response depends on - * {@link BroadcastInfoRequest.RequestOption}. If the request has RequestOption value + * {@link BroadcastInfoRequest#getOption()}. If the request has RequestOption value * REQUEST_OPTION_AUTO_UPDATE, then the response may be set to the latest version which may be * different from the version of the request. Otherwise, response with a different version from * its request will be considered invalid. diff --git a/media/java/android/media/tv/SectionResponse.java b/media/java/android/media/tv/SectionResponse.java index f38ea9dfac99..10333fe424a6 100644 --- a/media/java/android/media/tv/SectionResponse.java +++ b/media/java/android/media/tv/SectionResponse.java @@ -76,7 +76,7 @@ public final class SectionResponse extends BroadcastInfoResponse implements Parc /** * Gets the Version number of requested session. If it is null, value will be -1. * <p>The consistency of version numbers between request and response depends on - * {@link BroadcastInfoRequest.RequestOption}. If the request has RequestOption value + * {@link BroadcastInfoRequest#getOption()}. If the request has RequestOption value * REQUEST_OPTION_AUTO_UPDATE, then the response may be set to the latest version which may be * different from the version of the request. Otherwise, response with a different version from * its request will be considered invalid. diff --git a/media/java/android/media/tv/TableRequest.java b/media/java/android/media/tv/TableRequest.java index d9587f6ac089..06df07fbc899 100644 --- a/media/java/android/media/tv/TableRequest.java +++ b/media/java/android/media/tv/TableRequest.java @@ -129,7 +129,7 @@ public final class TableRequest extends BroadcastInfoRequest implements Parcelab /** * Gets the version number of requested table. If it is null, value will be -1. * <p>The consistency of version numbers between request and response depends on - * {@link BroadcastInfoRequest.RequestOption}. If the request has RequestOption value + * {@link BroadcastInfoRequest#getOption()}. If the request has RequestOption value * REQUEST_OPTION_AUTO_UPDATE, then the response may be set to the latest version which may be * different from the version of the request. Otherwise, response with a different version from * its request will be considered invalid. diff --git a/media/java/android/media/tv/TableResponse.java b/media/java/android/media/tv/TableResponse.java index c4fc26ef1932..1daf452fa422 100644 --- a/media/java/android/media/tv/TableResponse.java +++ b/media/java/android/media/tv/TableResponse.java @@ -269,7 +269,7 @@ public final class TableResponse extends BroadcastInfoResponse implements Parcel /** * Gets the version number of requested table. If it is null, value will be -1. * <p>The consistency of version numbers between request and response depends on - * {@link BroadcastInfoRequest.RequestOption}. If the request has RequestOption value + * {@link BroadcastInfoRequest#getOption()}. If the request has RequestOption value * REQUEST_OPTION_AUTO_UPDATE, then the response may be set to the latest version which may be * different from the version of the request. Otherwise, response with a different version from * its request will be considered invalid. diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java index 667a9aef59f3..2db4be86bf91 100644 --- a/media/java/android/media/tv/TvInputInfo.java +++ b/media/java/android/media/tv/TvInputInfo.java @@ -939,9 +939,8 @@ public final class TvInputInfo implements Parcelable { type = TYPE_HDMI; isHardwareInput = true; hdmiConnectionRelativePosition = getRelativePosition(mContext, mHdmiDeviceInfo); - isConnectedToHdmiSwitch = - hdmiConnectionRelativePosition - != HdmiUtils.HDMI_RELATIVE_POSITION_DIRECTLY_BELOW; + isConnectedToHdmiSwitch = hdmiConnectionRelativePosition + == HdmiUtils.HDMI_RELATIVE_POSITION_BELOW; } else if (mTvInputHardwareInfo != null) { id = generateInputId(componentName, mTvInputHardwareInfo); type = sHardwareTypeToTvInputType.get(mTvInputHardwareInfo.getType(), TYPE_TUNER); diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp index c616b84fa6fb..1c25080939da 100644 --- a/media/jni/android_media_MediaDrm.cpp +++ b/media/jni/android_media_MediaDrm.cpp @@ -38,6 +38,8 @@ #include <mediadrm/IDrmMetricsConsumer.h> #include <mediadrm/IDrm.h> #include <utils/Vector.h> +#include <map> +#include <string> using ::android::os::PersistableBundle; namespace drm = ::android::hardware::drm; @@ -193,6 +195,11 @@ struct LogMessageFields { jclass classId; }; +struct DrmExceptionFields { + jmethodID init; + jclass classId; +}; + struct fields_t { jfieldID context; jmethodID post_event; @@ -215,6 +222,7 @@ struct fields_t { jclass parcelCreatorClassId; KeyStatusFields keyStatus; LogMessageFields logMessage; + std::map<std::string, DrmExceptionFields> exceptionCtors; }; static fields_t gFields; @@ -245,18 +253,32 @@ jobject hidlLogMessagesToJavaList(JNIEnv *env, const Vector<drm::V1_4::LogMessag return arrayList; } -int drmThrowException(JNIEnv* env, const char *className, const DrmStatus &err, const char *msg) { +void resolveDrmExceptionCtor(JNIEnv *env, const char *className) { + jclass clazz; + jmethodID init; + FIND_CLASS(clazz, className); + GET_METHOD_ID(init, clazz, "<init>", "(Ljava/lang/String;III)V"); + gFields.exceptionCtors[std::string(className)] = { + .init = init, + .classId = static_cast<jclass>(env->NewGlobalRef(clazz)) + }; +} + +void drmThrowException(JNIEnv* env, const char *className, const DrmStatus &err, const char *msg) { using namespace android::jnihelp; - jstring _detailMessage = CreateExceptionMsg(env, msg); - int _status = ThrowException(env, className, "(Ljava/lang/String;III)V", - _detailMessage, - err.getCdmErr(), - err.getOemErr(), - err.getContext()); - if (_detailMessage != NULL) { - env->DeleteLocalRef(_detailMessage); + + if (gFields.exceptionCtors.count(std::string(className)) == 0) { + jniThrowException(env, className, msg); + } else { + jstring _detailMessage = CreateExceptionMsg(env, msg); + jobject exception = env->NewObject(gFields.exceptionCtors[std::string(className)].classId, + gFields.exceptionCtors[std::string(className)].init, _detailMessage, + err.getCdmErr(), err.getOemErr(), err.getContext()); + env->Throw(static_cast<jthrowable>(exception)); + if (_detailMessage != NULL) { + env->DeleteLocalRef(_detailMessage); + } } - return _status; } } // namespace anonymous @@ -952,6 +974,10 @@ static void android_media_MediaDrm_native_init(JNIEnv *env) { FIND_CLASS(clazz, "android/media/MediaDrm$LogMessage"); gFields.logMessage.classId = static_cast<jclass>(env->NewGlobalRef(clazz)); GET_METHOD_ID(gFields.logMessage.init, clazz, "<init>", "(JILjava/lang/String;)V"); + + resolveDrmExceptionCtor(env, "android/media/NotProvisionedException"); + resolveDrmExceptionCtor(env, "android/media/ResourceBusyException"); + resolveDrmExceptionCtor(env, "android/media/DeniedByServerException"); } static void android_media_MediaDrm_native_setup( @@ -2192,4 +2218,4 @@ static const JNINativeMethod gMethods[] = { int register_android_media_Drm(JNIEnv *env) { return AndroidRuntime::registerNativeMethods(env, "android/media/MediaDrm", gMethods, NELEM(gMethods)); -} +}
\ No newline at end of file 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/CompanionDeviceManager/res/values/styles.xml b/packages/CompanionDeviceManager/res/values/styles.xml index 222877bbe9e9..88f1204641ff 100644 --- a/packages/CompanionDeviceManager/res/values/styles.xml +++ b/packages/CompanionDeviceManager/res/values/styles.xml @@ -102,6 +102,8 @@ <item name="android:layout_height">36dp</item> <item name="android:textAllCaps">false</item> <item name="android:textSize">14sp</item> + <item name="android:paddingLeft">6dp</item> + <item name="android:paddingRight">6dp</item> <item name="android:background">@drawable/btn_negative_multiple_devices</item> <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item> </style> diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt index 473d7b6f32df..477e61d2a7b1 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt @@ -61,14 +61,20 @@ import androidx.credentials.provider.PasswordCredentialEntry import androidx.credentials.provider.PublicKeyCredentialEntry import androidx.credentials.provider.RemoteEntry import org.json.JSONObject +import android.credentials.flags.Flags import java.time.Instant + fun getAppLabel( pm: PackageManager, appPackageName: String ): String? { return try { - val pkgInfo = getPackageInfo(pm, appPackageName) + val pkgInfo = if (Flags.instantAppsEnabled()) { + getPackageInfo(pm, appPackageName) + } else { + pm.getPackageInfo(appPackageName, PackageManager.PackageInfoFlags.of(0)) + } val applicationInfo = checkNotNull(pkgInfo.applicationInfo) applicationInfo.loadSafeLabel( pm, 0f, @@ -91,7 +97,14 @@ private fun getServiceLabelAndIcon( // Test data has only package name not component name. // For test data usage only. try { - val pkgInfo = getPackageInfo(pm, providerFlattenedComponentName) + val pkgInfo = if (Flags.instantAppsEnabled()) { + getPackageInfo(pm, providerFlattenedComponentName) + } else { + pm.getPackageInfo( + providerFlattenedComponentName, + PackageManager.PackageInfoFlags.of(0) + ) + } val applicationInfo = checkNotNull(pkgInfo.applicationInfo) providerLabel = applicationInfo.loadSafeLabel( @@ -115,7 +128,14 @@ private fun getServiceLabelAndIcon( // Added for mdoc use case where the provider may not need to register a service and // instead only relies on the registration api. try { - val pkgInfo = getPackageInfo(pm, providerFlattenedComponentName) + val pkgInfo = if (Flags.instantAppsEnabled()) { + getPackageInfo(pm, providerFlattenedComponentName) + } else { + pm.getPackageInfo( + component.packageName, + PackageManager.PackageInfoFlags.of(0) + ) + } val applicationInfo = checkNotNull(pkgInfo.applicationInfo) providerLabel = applicationInfo.loadSafeLabel( @@ -143,12 +163,12 @@ private fun getPackageInfo( pm: PackageManager, packageName: String ): PackageInfo { - val flags = PackageManager.MATCH_INSTANT + val packageManagerFlags = PackageManager.MATCH_INSTANT return pm.getPackageInfo( packageName, PackageManager.PackageInfoFlags.of( - (flags).toLong()) + (packageManagerFlags).toLong()) ) } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt index 2318bb95dabb..9355517d8bf9 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt @@ -27,14 +27,22 @@ import android.os.Bundle import android.os.CancellationSignal import android.os.OutcomeReceiver import android.service.autofill.AutofillService +import android.service.autofill.Dataset +import android.service.autofill.Field import android.service.autofill.FillCallback import android.service.autofill.FillRequest import android.service.autofill.FillResponse +import android.service.autofill.InlinePresentation +import android.service.autofill.Presentations import android.service.autofill.SaveCallback import android.service.autofill.SaveRequest import android.service.credentials.CredentialProviderService import android.util.Log import android.view.autofill.AutofillId +import org.json.JSONException +import android.widget.inline.InlinePresentationSpec +import androidx.autofill.inline.v1.InlineSuggestionUi +import com.android.credentialmanager.GetFlowUtils import org.json.JSONObject import java.util.concurrent.Executors @@ -49,11 +57,9 @@ class CredentialAutofillService : AutofillService() { private const val SYS_PROVIDER_REQ_KEY = "isSystemProviderRequired" private const val CRED_OPTIONS_KEY = "credentialOptions" private const val TYPE_KEY = "type" + private const val REQ_TYPE_KEY = "get" } - private val credentialManager: CredentialManager = - getSystemService(Context.CREDENTIAL_SERVICE) as CredentialManager - override fun onFillRequest( request: FillRequest, cancellationSignal: CancellationSignal, @@ -66,16 +72,24 @@ class CredentialAutofillService : AutofillService() { val getCredRequest: GetCredentialRequest? = getCredManRequest(structure) if (getCredRequest == null) { + Log.i(TAG, "No credential manager request found") callback.onFailure("No credential manager request found") return } + val credentialManager: CredentialManager = + getSystemService(Context.CREDENTIAL_SERVICE) as CredentialManager val outcome = object : OutcomeReceiver<GetCandidateCredentialsResponse, GetCandidateCredentialsException> { override fun onResult(result: GetCandidateCredentialsResponse) { Log.i(TAG, "getCandidateCredentials onResponse") - val fillResponse: FillResponse? = convertToFillResponse(result, request) - callback.onSuccess(fillResponse) + val fillResponse = convertToFillResponse(result, request) + if (fillResponse != null) { + callback.onSuccess(fillResponse) + } else { + Log.e(TAG, "Failed to create a FillResponse from the CredentialResponse.") + callback.onFailure("No dataset was created from the CredentialResponse") + } } override fun onError(error: GetCandidateCredentialsException) { @@ -97,7 +111,74 @@ class CredentialAutofillService : AutofillService() { getCredResponse: GetCandidateCredentialsResponse, filLRequest: FillRequest ): FillResponse? { - TODO("Not yet implemented") + val providerList = GetFlowUtils.toProviderList( + getCredResponse.candidateProviderDataList, + this@CredentialAutofillService) + var totalEntryCount = 0 + providerList.forEach { provider -> + totalEntryCount += provider.credentialEntryList.size + } + val inlineSuggestionsRequest = filLRequest.inlineSuggestionsRequest + val inlineMaxSuggestedCount = inlineSuggestionsRequest?.maxSuggestionCount ?: 0 + val inlinePresentationSpecs = inlineSuggestionsRequest?.inlinePresentationSpecs + val inlinePresentationSpecsCount = inlinePresentationSpecs?.size ?: 0 + var maxItemCount = totalEntryCount + if (inlineMaxSuggestedCount > 0) { + maxItemCount = maxItemCount.coerceAtMost(inlineMaxSuggestedCount) + } + var i = 0 + val fillResponseBuilder = FillResponse.Builder() + var emptyFillResponse = true + providerList.forEach {provider -> + // TODO(b/299321128): Before iterating the list, sort the list so that + // the relevant entries don't get truncated + provider.credentialEntryList.forEach entryLoop@ {entry -> + val autofillId: AutofillId? = entry.fillInIntent?.getParcelableExtra( + CredentialProviderService.EXTRA_AUTOFILL_ID, + AutofillId::class.java) + val pendingIntent = entry.pendingIntent + if (autofillId == null || pendingIntent == null) { + return@entryLoop + } + var inlinePresentation: InlinePresentation? = null + // Create inline presentation + if (inlinePresentationSpecs != null && i < maxItemCount) { + val spec: InlinePresentationSpec + if (i < inlinePresentationSpecsCount) { + spec = inlinePresentationSpecs[i] + } else { + spec = inlinePresentationSpecs[inlinePresentationSpecsCount - 1] + } + val sliceBuilder = InlineSuggestionUi + .newContentBuilder(pendingIntent) + .setTitle(entry.userName) + inlinePresentation = InlinePresentation( + sliceBuilder.build().slice, spec, /* pinned= */ false) + } + i++ + + val dataSetBuilder = Dataset.Builder() + val presentationBuilder = Presentations.Builder() + if (inlinePresentation != null) { + presentationBuilder.setInlinePresentation(inlinePresentation) + } + fillResponseBuilder.addDataset( + dataSetBuilder + .setField( + autofillId, + Field.Builder().setPresentations( + presentationBuilder.build()) + .build()) + .setAuthentication(entry.pendingIntent.intentSender) + .setAuthenticationExtras(entry.fillInIntent.extras) + .build()) + emptyFillResponse = false + } + } + if (emptyFillResponse) { + return null + } + return fillResponseBuilder.build() } override fun onSaveRequest(request: SaveRequest, callback: SaveCallback) { @@ -165,8 +246,12 @@ class CredentialAutofillService : AutofillService() { val credentialOptions: MutableList<CredentialOption> = mutableListOf() for (credentialHint in credentialHints) { - convertJsonToCredentialOption(credentialHint, autofillId) - .let { credentialOptions.addAll(it) } + try { + convertJsonToCredentialOption(credentialHint, autofillId) + .let { credentialOptions.addAll(it) } + } catch (e: JSONException) { + Log.i(TAG, "Exception while parsing response: " + e.message) + } } return credentialOptions } @@ -178,7 +263,8 @@ class CredentialAutofillService : AutofillService() { val credentialOptions: MutableList<CredentialOption> = mutableListOf() val json = JSONObject(jsonString) - val options = json.getJSONArray(CRED_OPTIONS_KEY) + val jsonGet = json.getJSONObject(REQ_TYPE_KEY) + val options = jsonGet.getJSONArray(CRED_OPTIONS_KEY) for (i in 0 until options.length()) { val option = options.getJSONObject(i) val candidateBundle = convertJsonToBundle(option.getJSONObject(CANDIDATE_DATA_KEY)) 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/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java index d97fb5440cbd..c5ae4a37b355 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java @@ -381,7 +381,7 @@ public class PackageInstallerActivity extends AlertActivity { final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1 /* defaultValue */); final SessionInfo info = mInstaller.getSessionInfo(sessionId); - String resolvedPath = info.getResolvedBaseApkPath(); + String resolvedPath = info != null ? info.getResolvedBaseApkPath() : null; if (info == null || !info.isSealed() || resolvedPath == null) { Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring"); finish(); @@ -609,7 +609,7 @@ public class PackageInstallerActivity extends AlertActivity { CharSequence label = mPm.getApplicationLabel(mPkgInfo.applicationInfo); if (mLocalLOGV) Log.i(TAG, "creating snippet for " + label); mAppSnippet = new PackageUtil.AppSnippet(label, - mPm.getApplicationIcon(mPkgInfo.applicationInfo)); + mPm.getApplicationIcon(mPkgInfo.applicationInfo), getBaseContext()); } break; case ContentResolver.SCHEME_FILE: { @@ -647,7 +647,7 @@ public class PackageInstallerActivity extends AlertActivity { mPkgInfo = generateStubPackageInfo(info.getAppPackageName()); mAppSnippet = new PackageUtil.AppSnippet(info.getAppLabel(), info.getAppIcon() != null ? new BitmapDrawable(getResources(), info.getAppIcon()) - : getPackageManager().getDefaultActivityIcon()); + : getPackageManager().getDefaultActivityIcon(), getBaseContext()); return true; } diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java index 334886fe5561..976a3ad69901 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java @@ -18,6 +18,7 @@ package com.android.packageinstaller; import android.app.Activity; +import android.app.ActivityManager; import android.app.AlertDialog; import android.app.Dialog; import android.app.DialogFragment; @@ -135,15 +136,20 @@ public class PackageUtil { static final class AppSnippet implements Parcelable { @NonNull public CharSequence label; @Nullable public Drawable icon; - public AppSnippet(@NonNull CharSequence label, @Nullable Drawable icon) { + public int iconSize; + + AppSnippet(@NonNull CharSequence label, @Nullable Drawable icon, Context context) { this.label = label; this.icon = icon; + final ActivityManager am = context.getSystemService(ActivityManager.class); + this.iconSize = am.getLauncherLargeIconSize(); } private AppSnippet(Parcel in) { label = in.readString(); Bitmap bmp = in.readParcelable(getClass().getClassLoader(), Bitmap.class); icon = new BitmapDrawable(Resources.getSystem(), bmp); + iconSize = in.readInt(); } @Override @@ -161,6 +167,7 @@ public class PackageUtil { dest.writeString(label.toString()); Bitmap bmp = getBitmapFromDrawable(icon); dest.writeParcelable(bmp, 0); + dest.writeInt(iconSize); } private Bitmap getBitmapFromDrawable(Drawable drawable) { @@ -174,6 +181,14 @@ public class PackageUtil { // bitmap held within drawable.draw(canvas); + // Scale it down if the icon is too large + if ((bmp.getWidth() > iconSize * 2) || (bmp.getHeight() > iconSize * 2)) { + Bitmap scaledBitmap = Bitmap.createScaledBitmap(bmp, iconSize, iconSize, true); + if (scaledBitmap != bmp) { + bmp.recycle(); + } + return scaledBitmap; + } return bmp; } @@ -241,7 +256,7 @@ public class PackageUtil { } catch (OutOfMemoryError e) { Log.i(LOG_TAG, "Could not load app icon", e); } - return new PackageUtil.AppSnippet(label, icon); + return new PackageUtil.AppSnippet(label, icon, pContext); } private static String findFilePath(File[] files, String postfix) { diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp index 2d231f2f55da..8964adaf586e 100644 --- a/packages/SettingsLib/Android.bp +++ b/packages/SettingsLib/Android.bp @@ -11,50 +11,14 @@ android_library { name: "SettingsLib", static_libs: [ - "androidx.annotation_annotation", - "androidx.appcompat_appcompat", - "androidx.coordinatorlayout_coordinatorlayout", - "androidx.core_core", - "androidx.fragment_fragment", - "androidx.lifecycle_lifecycle-runtime", - "androidx.loader_loader", "androidx.localbroadcastmanager_localbroadcastmanager", - "androidx.preference_preference", - "androidx.recyclerview_recyclerview", - "com.google.android.material_material", - "iconloader", + "androidx.room_room-runtime", + "zxing-core", "WifiTrackerLibRes", - "SettingsLibDeviceStateRotationLock", - "SettingsLibDisplayUtils", - "SettingsLibEmergencyNumber", - "SettingsLibSearchWidget", - "SettingsLibUtils", - "SettingsLibWidget", + "iconloader", "setupdesign", - "zxing-core-1.7", - "androidx.room_room-runtime", - "settingslib_flags_lib", - ], - - plugins: ["androidx.room_room-compiler-plugin"], - use_resource_processor: true, - resource_dirs: ["res"], - - srcs: [ - "src/**/*.java", - "src/**/*.kt", - ], -} -// Group all the libraries with namespace "com.android.settingslib.widget", to allow SettingsLib to -// set use_resource_processor = true. -// We can remove SettingsLibWidget when all these libraries have its own namespace. -android_library { - name: "SettingsLibWidget", - visibility: ["//visibility:private"], - manifest: "AndroidManifest-SettingsLibWidget.xml", - static_libs: [ "SettingsLibActionBarShadow", "SettingsLibActionButtonsPreference", "SettingsLibAdaptiveIcon", @@ -63,6 +27,9 @@ android_library { "SettingsLibBarChartPreference", "SettingsLibButtonPreference", "SettingsLibCollapsingToolbarBaseActivity", + "SettingsLibDeviceStateRotationLock", + "SettingsLibDisplayUtils", + "SettingsLibEmergencyNumber", "SettingsLibEntityHeaderWidgets", "SettingsLibFooterPreference", "SettingsLibHelpUtils", @@ -72,31 +39,31 @@ android_library { "SettingsLibProfileSelector", "SettingsLibProgressBar", "SettingsLibRestrictedLockUtils", + "SettingsLibSearchWidget", "SettingsLibSelectorWithWidgetPreference", "SettingsLibSettingsSpinner", "SettingsLibSettingsTransition", "SettingsLibTopIntroPreference", "SettingsLibTwoTargetPreference", "SettingsLibUsageProgressBarPreference", + "SettingsLibUtils", + "settingslib_flags_lib", ], - resource_dirs: [], + plugins: ["androidx.room_room-compiler-plugin"], + use_resource_processor: true, + resource_dirs: ["res"], + + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], } // NOTE: Keep this module in sync with ./common.mk java_defaults { name: "SettingsLibDefaults", static_libs: [ - "androidx.annotation_annotation", - "androidx.appcompat_appcompat", - "androidx.coordinatorlayout_coordinatorlayout", - "androidx.core_core", - "androidx.fragment_fragment", - "androidx.lifecycle_lifecycle-runtime", - "androidx.loader_loader", - "androidx.localbroadcastmanager_localbroadcastmanager", - "androidx.preference_preference", - "androidx.recyclerview_recyclerview", "SettingsLib", ], } diff --git a/packages/SettingsLib/AndroidManifest-SettingsLibWidget.xml b/packages/SettingsLib/AndroidManifest-SettingsLibWidget.xml deleted file mode 100644 index 38a7d6ace1f8..000000000000 --- a/packages/SettingsLib/AndroidManifest-SettingsLibWidget.xml +++ /dev/null @@ -1,18 +0,0 @@ -<?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 package="com.android.settingslib.widget" /> diff --git a/packages/SettingsLib/MainSwitchPreference/Android.bp b/packages/SettingsLib/MainSwitchPreference/Android.bp index 33aa985b32e8..4871ef3097a5 100644 --- a/packages/SettingsLib/MainSwitchPreference/Android.bp +++ b/packages/SettingsLib/MainSwitchPreference/Android.bp @@ -9,6 +9,7 @@ package { android_library { name: "SettingsLibMainSwitchPreference", + use_resource_processor: true, srcs: ["src/**/*.java"], resource_dirs: ["res"], diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java index 56b3eacf6c1f..600115545a16 100644 --- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java +++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java @@ -31,12 +31,11 @@ import android.widget.TextView; import androidx.annotation.ColorInt; import com.android.settingslib.utils.BuildCompatUtils; +import com.android.settingslib.widget.mainswitch.R; import java.util.ArrayList; import java.util.List; -import com.android.settingslib.widget.mainswitch.R; - /** * MainSwitchBar is a View with a customized Switch. * This component is used as the main switch of the page @@ -77,7 +76,7 @@ public class MainSwitchBar extends LinearLayout implements CompoundButton.OnChec final TypedArray a = context.obtainStyledAttributes( new int[]{android.R.attr.colorAccent}); mBackgroundActivatedColor = a.getColor(0, 0); - mBackgroundColor = context.getColor(R.color.material_grey_600); + mBackgroundColor = context.getColor(androidx.appcompat.R.color.material_grey_600); a.recycle(); } 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 0552c408a8eb..1ad075c11985 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 @@ -31,7 +31,6 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Delete -import androidx.compose.material.icons.outlined.Launch import androidx.compose.material.icons.outlined.WarningAmber import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.FilledTonalButton @@ -52,6 +51,7 @@ import com.android.settingslib.spa.framework.theme.SettingsDimension import com.android.settingslib.spa.framework.theme.SettingsShape import com.android.settingslib.spa.framework.theme.SettingsTheme import com.android.settingslib.spa.framework.theme.divider +import androidx.compose.material.icons.automirrored.outlined.Launch data class ActionButton( val text: String, @@ -101,7 +101,9 @@ private fun RowScope.ActionButton(actionButton: ActionButton) { modifier = Modifier.size(SettingsDimension.itemIconSize), ) Box( - modifier = Modifier.padding(top = 4.dp).fillMaxHeight(), + modifier = Modifier + .padding(top = 4.dp) + .fillMaxHeight(), contentAlignment = Alignment.Center, ) { Text( @@ -129,7 +131,7 @@ private fun ActionButtonsPreview() { SettingsTheme { 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/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 62189dccc9bf..6ef45900a103 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,7 +18,6 @@ package com.android.settingslib.spa.widget.scaffold import androidx.appcompat.R import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.ArrowBack import androidx.compose.material.icons.outlined.Clear import androidx.compose.material.icons.outlined.FindInPage import androidx.compose.material3.Icon @@ -31,6 +30,7 @@ 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 @@ -53,7 +53,7 @@ internal fun CollapseAction(onClick: () -> Unit) { private fun BackAction(contentDescription: String, onClick: () -> Unit) { IconButton(onClick) { Icon( - imageVector = Icons.Outlined.ArrowBack, + imageVector = Icons.AutoMirrored.Outlined.ArrowBack, contentDescription = contentDescription, modifier = Modifier.autoMirrored(), ) diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt index aa148b022b92..9f7f040be7ce 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt @@ -21,7 +21,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState -import androidx.compose.material3.TabRow +import androidx.compose.material3.PrimaryTabRow import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier @@ -43,7 +43,7 @@ fun SettingsPager(titles: List<String>, content: @Composable (page: Int) -> Unit val coroutineScope = rememberCoroutineScope() val pagerState = rememberPagerState { titles.size } - TabRow( + PrimaryTabRow( selectedTabIndex = pagerState.currentPage, modifier = Modifier.padding(horizontal = SettingsDimension.itemPaddingEnd), containerColor = Color.Transparent, diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.kt index f59b0decf1e5..8d9bac64b078 100644 --- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.kt +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.kt @@ -17,8 +17,8 @@ package com.android.settingslib.spa.widget.button import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.outlined.Launch import androidx.compose.material.icons.outlined.Close -import androidx.compose.material.icons.outlined.Launch import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue @@ -43,7 +43,10 @@ class ActionButtonsTest { composeTestRule.setContent { ActionButtons( listOf( - ActionButton(text = "Open", imageVector = Icons.Outlined.Launch) {}, + ActionButton( + text = "Open", + imageVector = Icons.AutoMirrored.Outlined.Launch + ) {}, ) ) } @@ -57,7 +60,7 @@ class ActionButtonsTest { composeTestRule.setContent { ActionButtons( listOf( - ActionButton(text = "Open", imageVector = Icons.Outlined.Launch) { + ActionButton(text = "Open", imageVector = Icons.AutoMirrored.Outlined.Launch) { clicked = true }, ) @@ -74,7 +77,10 @@ class ActionButtonsTest { composeTestRule.setContent { ActionButtons( listOf( - ActionButton(text = "Open", imageVector = Icons.Outlined.Launch) {}, + ActionButton( + text = "Open", + imageVector = Icons.AutoMirrored.Outlined.Launch + ) {}, ActionButton(text = "Close", imageVector = Icons.Outlined.Close) {}, ) ) diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt index dfd8f6b8e373..352503742b9c 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt @@ -18,8 +18,10 @@ package com.android.settingslib.spaprivileged.model.enterprise import android.app.admin.DevicePolicyManager import android.app.admin.DevicePolicyResources.Strings.Settings.PERSONAL_CATEGORY_HEADER +import android.app.admin.DevicePolicyResources.Strings.Settings.PRIVATE_CATEGORY_HEADER import android.app.admin.DevicePolicyResources.Strings.Settings.WORK_CATEGORY_HEADER import android.content.Context +import android.content.pm.UserInfo import com.android.settingslib.R class EnterpriseRepository(private val context: Context) { @@ -30,8 +32,10 @@ class EnterpriseRepository(private val context: Context) { fun getEnterpriseString(updatableStringId: String, resId: Int): String = checkNotNull(resources.getString(updatableStringId) { context.getString(resId) }) - fun getProfileTitle(isManagedProfile: Boolean): String = if (isManagedProfile) { + fun getProfileTitle(userInfo: UserInfo): String = if (userInfo.isManagedProfile) { getEnterpriseString(WORK_CATEGORY_HEADER, R.string.category_work) + } else if (userInfo.isPrivateProfile) { + getEnterpriseString(PRIVATE_CATEGORY_HEADER, R.string.category_private) } else { getEnterpriseString(PERSONAL_CATEGORY_HEADER, R.string.category_personal) } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/UserProfilePager.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/UserProfilePager.kt index 6a76c93ac9a9..5447f21fd417 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/UserProfilePager.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/UserProfilePager.kt @@ -45,7 +45,7 @@ fun UserProfilePager(content: @Composable (userGroup: UserGroup) -> Unit) { val enterpriseRepository = EnterpriseRepository(context) userGroups.map { userGroup -> enterpriseRepository.getProfileTitle( - isManagedProfile = userGroup.userInfos.first().isManagedProfile, + userGroup.userInfos.first(), ) } } diff --git a/packages/SettingsLib/common.mk b/packages/SettingsLib/common.mk index d9596569a0da..431fd44cd952 100644 --- a/packages/SettingsLib/common.mk +++ b/packages/SettingsLib/common.mk @@ -18,18 +18,5 @@ # to the corresponding module. # NOTE: keep this file and ./Android.bp in sync. -LOCAL_STATIC_JAVA_LIBRARIES += \ - androidx.annotation_annotation - LOCAL_STATIC_ANDROID_LIBRARIES += \ - androidx.appcompat_appcompat \ - androidx.coordinatorlayout_coordinatorlayout \ - androidx.core_core \ - androidx.fragment_fragment \ - androidx.lifecycle_lifecycle-runtime \ - androidx.loader_loader \ - androidx.localbroadcastmanager_localbroadcastmanager \ - androidx.preference_preference \ - androidx.recyclerview_recyclerview \ SettingsLib - diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 49bd9d994088..96876747c082 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -518,6 +518,8 @@ <string name="category_personal">Personal</string> <!-- Header for items under the work user [CHAR LIMIT=30] --> <string name="category_work">Work</string> + <!-- Header for items under the private profile user [CHAR LIMIT=30] --> + <string name="category_private">Private</string> <!-- Header for items under the clone user [CHAR LIMIT=30] --> <string name="category_clone">Clone</string> diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java index 412a3424bbd1..ce0772ff84a1 100644 --- a/packages/SettingsLib/src/com/android/settingslib/Utils.java +++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java @@ -54,6 +54,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.UserIcons; import com.android.launcher3.icons.BaseIconFactory.IconOptions; import com.android.launcher3.icons.IconFactory; +import com.android.launcher3.util.UserIconInfo; import com.android.settingslib.drawable.UserIconDrawable; import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.settingslib.utils.BuildCompatUtils; @@ -67,8 +68,7 @@ public class Utils { static final String STORAGE_MANAGER_ENABLED_PROPERTY = "ro.storage_manager.enabled"; - @VisibleForTesting - static final String INCOMPATIBLE_CHARGER_WARNING_DISABLED = + public static final String INCOMPATIBLE_CHARGER_WARNING_DISABLED = "incompatible_charger_warning_disabled"; private static Signature[] sSystemSignature; @@ -598,15 +598,25 @@ public class Utils { /** Get the corresponding adaptive icon drawable. */ public static Drawable getBadgedIcon(Context context, Drawable icon, UserHandle user) { - UserManager um = context.getSystemService(UserManager.class); - boolean isClone = um.getProfiles(user.getIdentifier()).stream() - .anyMatch(profile -> - profile.isCloneProfile() && profile.id == user.getIdentifier()); + int userType = UserIconInfo.TYPE_MAIN; + try { + UserInfo ui = context.getSystemService(UserManager.class).getUserInfo( + user.getIdentifier()); + if (ui != null) { + if (ui.isCloneProfile()) { + userType = UserIconInfo.TYPE_CLONED; + } else if (ui.isManagedProfile()) { + userType = UserIconInfo.TYPE_WORK; + } + } + } catch (Exception e) { + // Ignore + } try (IconFactory iconFactory = IconFactory.obtain(context)) { return iconFactory .createBadgedIconBitmap( icon, - new IconOptions().setUser(user).setIsCloneProfile(isClone)) + new IconOptions().setUser(new UserIconInfo(user, userType))) .newIcon(context); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractSimStatusImeiInfoPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractSimStatusImeiInfoPreferenceController.java deleted file mode 100644 index a78440c271c9..000000000000 --- a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractSimStatusImeiInfoPreferenceController.java +++ /dev/null @@ -1,36 +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.settingslib.deviceinfo; - -import android.content.Context; -import android.os.UserManager; - -import com.android.settingslib.Utils; -import com.android.settingslib.core.AbstractPreferenceController; - -public abstract class AbstractSimStatusImeiInfoPreferenceController - extends AbstractPreferenceController { - public AbstractSimStatusImeiInfoPreferenceController(Context context) { - super(context); - } - - @Override - public boolean isAvailable() { - return mContext.getSystemService(UserManager.class).isAdminUser() - && !Utils.isWifiOnly(mContext); - } -} diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java index 7f1f3f613bce..2032328cbc04 100644 --- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java +++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java @@ -110,7 +110,8 @@ public class BatteryStatus { } /** - * Determine whether the device is plugged in wireless. */ + * Determine whether the device is plugged in wireless. + */ public boolean isPluggedInWireless() { return plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS; } @@ -185,6 +186,22 @@ public class BatteryStatus { return status == BATTERY_STATUS_FULL || level >= 100; } + /** + * Whether or not the device is charged. Note that some devices never return 100% for battery + * level, so this allows either battery level or status to determine if the battery is charged. + * + * @param status the value from extra {@link BatteryManager.EXTRA_STATUS} of + * {@link Intent.ACTION_BATTERY_CHANGED} intent + * @param level the value from extra {@link BatteryManager.EXTRA_LEVEL} of + * {@link Intent.ACTION_BATTERY_CHANGED} intent + * @param scale the value from extra {@link BatteryManager.EXTRA_SCALE} of + * {@link Intent.ACTION_BATTERY_CHANGED} intent + */ + public static boolean isCharged(int status, int level, int scale) { + var batteryLevel = getBatteryLevel(level, scale); + return isCharged(status, batteryLevel); + } + /** Gets the battery level from the intent. */ public static int getBatteryLevel(Intent batteryChangedIntent) { if (batteryChangedIntent == null) { @@ -193,6 +210,14 @@ public class BatteryStatus { final int level = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, BATTERY_LEVEL_UNKNOWN); final int scale = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_SCALE, 0); + return getBatteryLevel(level, scale); + } + + /** + * Gets the battery level from the value of {@link Intent.BATTERY_CHANGED_INTENT}'s EXTRA_LEVEL + * and EXTRA_SCALE. + */ + public static int getBatteryLevel(int level, int scale) { return scale == 0 ? BATTERY_LEVEL_UNKNOWN : Math.round((level / (float) scale) * 100f); @@ -253,11 +278,22 @@ public class BatteryStatus { * * @param batteryChangedIntent the ACTION_BATTERY_CHANGED intent * @return {@code true} if the battery level is less or equal to {@link - * SEVERE_LOW_BATTERY_THRESHOLD} + * SEVERE_LOW_BATTERY_THRESHOLD} */ public static boolean isSevereLowBattery(Intent batteryChangedIntent) { - int level = getBatteryLevel(batteryChangedIntent); - return level <= SEVERE_LOW_BATTERY_THRESHOLD; + int batteryLevel = getBatteryLevel(batteryChangedIntent); + return isSevereLowBattery(batteryLevel); + } + + /** + * Whether the battery is severe low or not. + * + * @param batteryLevel the value of battery level + * @return {@code true} if the battery level is less or equal to {@link + * SEVERE_LOW_BATTERY_THRESHOLD} + */ + public static boolean isSevereLowBattery(int batteryLevel) { + return batteryLevel <= SEVERE_LOW_BATTERY_THRESHOLD; } /** @@ -265,11 +301,21 @@ public class BatteryStatus { * * @param batteryChangedIntent the ACTION_BATTERY_CHANGED intent * @return {@code true} if the battery level is less or equal to {@link - * EXTREME_LOW_BATTERY_THRESHOLD} + * EXTREME_LOW_BATTERY_THRESHOLD} */ public static boolean isExtremeLowBattery(Intent batteryChangedIntent) { int level = getBatteryLevel(batteryChangedIntent); - return level <= EXTREME_LOW_BATTERY_THRESHOLD; + return isExtremeLowBattery(level); + } + + /** + * Whether the battery is extreme low or not. + * + * @return {@code true} if the {@code batteryLevel} is less or equal to + * {@link EXTREME_LOW_BATTERY_THRESHOLD} + */ + public static boolean isExtremeLowBattery(int batteryLevel) { + return batteryLevel <= EXTREME_LOW_BATTERY_THRESHOLD; } /** @@ -277,7 +323,7 @@ public class BatteryStatus { * * @param batteryChangedIntent the ACTION_BATTERY_CHANGED intent * @return {@code true} if the battery defender is enabled. It could be dock defend, dwell - * defend, or temp defend + * defend, or temp defend */ public static boolean isBatteryDefender(Intent batteryChangedIntent) { int chargingStatus = @@ -298,9 +344,8 @@ public class BatteryStatus { } /** - * Gets the max charging current and max charging voltage form {@link - * Intent.ACTION_BATTERY_CHANGED} and calculates the charging speed based on the {@link - * R.integer.config_chargingSlowlyThreshold} and {@link R.integer.config_chargingFastThreshold}. + * Calculates the charging speed based on the {@link R.integer.config_chargingSlowlyThreshold} + * and {@link R.integer.config_chargingFastThreshold}. * * @param context the application context * @param batteryChangedIntent the intent from {@link Intent.ACTION_BATTERY_CHANGED} @@ -308,7 +353,29 @@ public class BatteryStatus { * CHARGING_SLOWLY} or {@link CHARGING_UNKNOWN} */ public static int getChargingSpeed(Context context, Intent batteryChangedIntent) { - final int maxChargingMicroWatt = calculateMaxChargingMicroWatt(batteryChangedIntent); + final int maxChargingMicroCurrent = + batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_CURRENT, -1); + int maxChargingMicroVolt = batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_VOLTAGE, -1); + + return calculateChargingSpeed(context, maxChargingMicroCurrent, maxChargingMicroVolt); + } + + /** + * Calculates the charging speed based on the {@link R.integer.config_chargingSlowlyThreshold} + * and {@link R.integer.config_chargingFastThreshold}. + * + * @param maxChargingMicroCurrent the max charging micro current that is retrieved form the + * extra of {@link Intent.Action_BATTERY_CHANGED} + * @param maxChargingMicroVolt the max charging micro voltage that is retrieved form the extra + * of {@link Intent.Action_BATTERY_CHANGED} + * @return the charging speed. {@link CHARGING_REGULAR}, {@link CHARGING_FAST}, {@link + * CHARGING_SLOWLY} or {@link CHARGING_UNKNOWN} + */ + public static int calculateChargingSpeed( + Context context, int maxChargingMicroCurrent, int maxChargingMicroVolt) { + final int maxChargingMicroWatt = + calculateMaxChargingMicroWatt(maxChargingMicroCurrent, maxChargingMicroVolt); + if (maxChargingMicroWatt <= 0) { return CHARGING_UNKNOWN; } else if (maxChargingMicroWatt @@ -326,6 +393,12 @@ public class BatteryStatus { final int maxChargingMicroAmp = batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_CURRENT, -1); int maxChargingMicroVolt = batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_VOLTAGE, -1); + + return calculateMaxChargingMicroWatt(maxChargingMicroAmp, maxChargingMicroVolt); + } + + private static int calculateMaxChargingMicroWatt(int maxChargingMicroAmp, + int maxChargingMicroVolt) { if (maxChargingMicroVolt <= 0) { maxChargingMicroVolt = DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT; } diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java index 41afc7b8b194..a63bbdf36fa8 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java @@ -26,6 +26,7 @@ import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET; import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER; +import android.annotation.NonNull; import android.content.Context; import android.graphics.drawable.Drawable; import android.media.MediaRoute2Info; @@ -51,6 +52,34 @@ public class PhoneMediaDevice extends MediaDevice { private final DeviceIconUtil mDeviceIconUtil; + /** Returns the device name for the given {@code routeInfo}. */ + public static String getSystemRouteNameFromType( + @NonNull Context context, @NonNull MediaRoute2Info routeInfo) { + CharSequence name; + switch (routeInfo.getType()) { + case TYPE_WIRED_HEADSET: + case TYPE_WIRED_HEADPHONES: + case TYPE_USB_DEVICE: + case TYPE_USB_HEADSET: + case TYPE_USB_ACCESSORY: + name = context.getString(R.string.media_transfer_wired_usb_device_name); + break; + case TYPE_DOCK: + name = context.getString(R.string.media_transfer_dock_speaker_device_name); + break; + case TYPE_BUILTIN_SPEAKER: + name = context.getString(R.string.media_transfer_this_device_name); + break; + case TYPE_HDMI: + name = context.getString(R.string.media_transfer_external_device_name); + break; + default: + name = context.getString(R.string.media_transfer_default_device_name); + break; + } + return name.toString(); + } + PhoneMediaDevice(Context context, MediaRoute2Info info, String packageName) { this(context, info, packageName, null); } @@ -69,29 +98,7 @@ public class PhoneMediaDevice extends MediaDevice { @SuppressWarnings("NewApi") @Override public String getName() { - CharSequence name; - switch (mRouteInfo.getType()) { - case TYPE_WIRED_HEADSET: - case TYPE_WIRED_HEADPHONES: - case TYPE_USB_DEVICE: - case TYPE_USB_HEADSET: - case TYPE_USB_ACCESSORY: - name = mContext.getString(R.string.media_transfer_wired_usb_device_name); - break; - case TYPE_DOCK: - name = mContext.getString(R.string.media_transfer_dock_speaker_device_name); - break; - case TYPE_BUILTIN_SPEAKER: - name = mContext.getString(R.string.media_transfer_this_device_name); - break; - case TYPE_HDMI: - name = mContext.getString(R.string.media_transfer_external_device_name); - break; - default: - name = mContext.getString(R.string.media_transfer_default_device_name); - break; - } - return name.toString(); + return getSystemRouteNameFromType(mContext, mRouteInfo); } @Override diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayoutTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayoutTest.java index 2b1e8080d389..507dcbc1d8a2 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayoutTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayoutTest.java @@ -55,8 +55,7 @@ public class CollapsingCoordinatorLayoutTest { @Test public void onCreate_userAddedChildViewsBeMovedToContentFrame() { CollapsingCoordinatorLayout layout = mActivity.getCollapsingCoordinatorLayout(); - View contentFrameView = - layout.findViewById(com.android.settingslib.widget.R.id.content_frame); + View contentFrameView = layout.findViewById(R.id.content_frame); TextView textView = contentFrameView.findViewById(com.android.settingslib.robotests.R.id.text_hello_world); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/SimStatusImeiInfoPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/SimStatusImeiInfoPreferenceControllerTest.java deleted file mode 100644 index 52d243a14e2f..000000000000 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/SimStatusImeiInfoPreferenceControllerTest.java +++ /dev/null @@ -1,114 +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.settingslib.deviceinfo; - -import static com.google.common.truth.Truth.assertThat; - -import static org.robolectric.shadow.api.Shadow.extract; - -import android.os.UserManager; -import android.telephony.TelephonyManager; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.Config; -import org.robolectric.annotation.Implementation; -import org.robolectric.annotation.Implements; - -@RunWith(RobolectricTestRunner.class) -@Config(shadows = {SimStatusImeiInfoPreferenceControllerTest.ShadowUserManager.class, - SimStatusImeiInfoPreferenceControllerTest.ShadowTelephonyManager.class}) -public class SimStatusImeiInfoPreferenceControllerTest { - - private AbstractSimStatusImeiInfoPreferenceController mController; - - @Before - public void setUp() { - mController = new AbstractSimStatusImeiInfoPreferenceController( - RuntimeEnvironment.application) { - @Override - public String getPreferenceKey() { - return null; - } - }; - } - - @Test - public void testIsAvailable_isAdminAndHasMobile_shouldReturnTrue() { - ShadowUserManager userManager = - extract(RuntimeEnvironment.application.getSystemService(UserManager.class)); - userManager.setIsAdminUser(true); - ShadowTelephonyManager telephonyManager = - extract(RuntimeEnvironment.application.getSystemService(TelephonyManager.class)); - telephonyManager.setDataCapable(true); - - assertThat(mController.isAvailable()).isTrue(); - } - - @Test - public void testIsAvailable_isAdminButNoMobile_shouldReturnFalse() { - ShadowUserManager userManager = - extract(RuntimeEnvironment.application.getSystemService(UserManager.class)); - userManager.setIsAdminUser(true); - ShadowTelephonyManager telephonyManager = - extract(RuntimeEnvironment.application.getSystemService(TelephonyManager.class)); - telephonyManager.setDataCapable(false); - - assertThat(mController.isAvailable()).isFalse(); - } - - @Test - public void testIsAvailable_isNotAdmin_shouldReturnFalse() { - ShadowUserManager userManager = - extract(RuntimeEnvironment.application.getSystemService(UserManager.class)); - userManager.setIsAdminUser(false); - - assertThat(mController.isAvailable()).isFalse(); - } - - @Implements(UserManager.class) - public static class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager { - - private boolean mAdminUser; - - public void setIsAdminUser(boolean isAdminUser) { - mAdminUser = isAdminUser; - } - - @Implementation - public boolean isAdminUser() { - return mAdminUser; - } - } - - @Implements(TelephonyManager.class) - public static class ShadowTelephonyManager - extends org.robolectric.shadows.ShadowTelephonyManager { - private boolean mDataCapable = false; - private void setDataCapable(boolean capable) { - mDataCapable = capable; - } - - @Implementation - public boolean isDataCapable() { - return mDataCapable; - } - } -} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveIconTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveIconTest.java index 6195d754f8f0..71545b75989a 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveIconTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveIconTest.java @@ -36,10 +36,10 @@ import android.graphics.drawable.Icon; import android.graphics.drawable.ShapeDrawable; import android.os.Bundle; -import com.android.settingslib.widget.adaptiveicon.R; import com.android.settingslib.drawer.ActivityTile; import com.android.settingslib.drawer.CategoryKey; import com.android.settingslib.drawer.Tile; +import com.android.settingslib.widget.adaptiveicon.R; import org.junit.Before; import org.junit.Test; @@ -105,15 +105,15 @@ public class AdaptiveIconTest { icon.setBackgroundColor(mContext, tile); - assertThat(icon.mBackgroundColor).isEqualTo(mContext.getColor( - com.android.settingslib.widget.R.color.homepage_generic_icon_background)); + assertThat(icon.mBackgroundColor).isEqualTo( + mContext.getColor(R.color.homepage_generic_icon_background)); } @Test public void onBindTile_externalTileWithBackgroundColorHint_shouldUpdateIcon() { final Tile tile = spy(new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE)); mActivityInfo.metaData.putInt(META_DATA_PREFERENCE_ICON_BACKGROUND_HINT, - com.android.settingslib.widget.R.color.bt_outline_color); + R.color.bt_outline_color); doReturn(Icon.createWithResource(mContext, com.android.settingslib.R.drawable.ic_system_update)) .when(tile).getIcon(mContext); @@ -121,8 +121,7 @@ public class AdaptiveIconTest { new AdaptiveIcon(mContext, new ColorDrawable(Color.BLACK)); icon.setBackgroundColor(mContext, tile); - assertThat(icon.mBackgroundColor).isEqualTo(mContext.getColor( - com.android.settingslib.widget.R.color.bt_outline_color)); + assertThat(icon.mBackgroundColor).isEqualTo(mContext.getColor(R.color.bt_outline_color)); } @Test diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java index ccbe4f03e80c..0ce83c6220ff 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java @@ -58,16 +58,14 @@ public class FooterPreferenceTest { @Test public void setLearnMoreText_shouldSetAsTextInLearnMore() { final PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests( - LayoutInflater.from(mContext) - .inflate(com.android.settingslib.widget.R.layout.preference_footer, null)); + LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null)); mFooterPreference.setLearnMoreText("Custom learn more"); mFooterPreference.setLearnMoreAction(view -> { /* do nothing */ } /* listener */); mFooterPreference.onBindViewHolder(holder); - assertThat(((TextView) holder.findViewById( - com.android.settingslib.widget.R.id.settingslib_learn_more)).getText().toString()) - .isEqualTo("Custom learn more"); + TextView learnMoreView = (TextView) holder.findViewById(R.id.settingslib_learn_more); + assertThat(learnMoreView.getText().toString()).isEqualTo("Custom learn more"); } @Test @@ -95,8 +93,7 @@ public class FooterPreferenceTest { @Test public void onBindViewHolder_whenTitleIsNull_shouldNotRaiseNpe() { PreferenceViewHolder viewHolder = spy(PreferenceViewHolder.createInstanceForTests( - LayoutInflater.from(mContext) - .inflate(R.layout.preference_footer, null))); + LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null))); when(viewHolder.findViewById(androidx.core.R.id.title)).thenReturn(null); Throwable actualThrowable = null; @@ -112,10 +109,8 @@ public class FooterPreferenceTest { @Test public void onBindViewHolder_whenLearnMoreIsNull_shouldNotRaiseNpe() { PreferenceViewHolder viewHolder = spy(PreferenceViewHolder.createInstanceForTests( - LayoutInflater.from(mContext) - .inflate(com.android.settingslib.widget.R.layout.preference_footer, null))); - when(viewHolder.findViewById(com.android.settingslib.widget.R.id.settingslib_learn_more)) - .thenReturn(null); + LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null))); + when(viewHolder.findViewById(R.id.settingslib_learn_more)).thenReturn(null); Throwable actualThrowable = null; try { @@ -130,8 +125,7 @@ public class FooterPreferenceTest { @Test public void onBindViewHolder_whenIconFrameIsNull_shouldNotRaiseNpe() { PreferenceViewHolder viewHolder = spy(PreferenceViewHolder.createInstanceForTests( - LayoutInflater.from(mContext) - .inflate(com.android.settingslib.widget.R.layout.preference_footer, null))); + LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null))); when(viewHolder.findViewById(R.id.icon_frame)).thenReturn(null); Throwable actualThrowable = null; diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowActivityManager.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowActivityManager.java deleted file mode 100644 index 0b9ba8d044ce..000000000000 --- a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowActivityManager.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES 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 static android.os.Build.VERSION_CODES.O; - -import android.app.ActivityManager; -import android.app.IActivityManager; - -import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.Implementation; -import org.robolectric.annotation.Implements; -import org.robolectric.annotation.Resetter; -import org.robolectric.shadow.api.Shadow; -import org.robolectric.util.ReflectionHelpers; - -@Implements(ActivityManager.class) -public class ShadowActivityManager { - private static int sCurrentUserId = 0; - private static int sUserSwitchedTo = -1; - - @Resetter - public static void reset() { - sCurrentUserId = 0; - sUserSwitchedTo = 0; - } - - @Implementation - protected static int getCurrentUser() { - return sCurrentUserId; - } - - @Implementation - protected boolean switchUser(int userId) { - sUserSwitchedTo = userId; - return true; - } - - @Implementation(minSdk = O) - protected static IActivityManager getService() { - return ReflectionHelpers.createNullProxy(IActivityManager.class); - } - - public boolean getSwitchUserCalled() { - return sUserSwitchedTo != -1; - } - - public int getUserSwitchedTo() { - return sUserSwitchedTo; - } - - public static void setCurrentUser(int userId) { - sCurrentUserId = userId; - } - - public static ShadowActivityManager getShadow() { - return (ShadowActivityManager) Shadow.extract( - RuntimeEnvironment.application.getSystemService(ActivityManager.class)); - } -} diff --git a/packages/SettingsProvider/OWNERS b/packages/SettingsProvider/OWNERS index 5ade9716c8cb..86ae5818e91c 100644 --- a/packages/SettingsProvider/OWNERS +++ b/packages/SettingsProvider/OWNERS @@ -1,5 +1 @@ -hackbod@android.com -hackbod@google.com -narayan@google.com -svetoslavganov@google.com include /PACKAGE_MANAGER_OWNERS diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java index 1d25ac78e7f9..e5dbe5f14cf4 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java @@ -69,6 +69,7 @@ public class GlobalSettings { Settings.Global.PRIVATE_DNS_SPECIFIER, Settings.Global.SOFT_AP_TIMEOUT_ENABLED, Settings.Global.ZEN_DURATION, + Settings.Global.REVERSE_CHARGING_AUTO_ON, Settings.Global.CHARGING_VIBRATION_ENABLED, Settings.Global.AWARE_ALLOWED, Settings.Global.CUSTOM_BUGREPORT_HANDLER_APP, // moved to secure diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java index f5d9475fb049..fe39c4febc80 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java @@ -103,5 +103,8 @@ public class SystemSettings { Settings.System.SCREEN_FLASH_NOTIFICATION_COLOR, Settings.System.PEAK_REFRESH_RATE, Settings.System.MIN_REFRESH_RATE, + Settings.System.NOTIFICATION_COOLDOWN_ENABLED, + Settings.System.NOTIFICATION_COOLDOWN_ALL, + Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED, }; } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java index ba06185e5663..7e8fe7e09d74 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java @@ -171,6 +171,7 @@ public class GlobalSettingsValidators { VALIDATORS.put(Global.WIFI_SCAN_THROTTLE_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Global.APP_AUTO_RESTRICTION_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Global.ZEN_DURATION, ANY_INTEGER_VALIDATOR); + VALIDATORS.put(Global.REVERSE_CHARGING_AUTO_ON, ANY_INTEGER_VALIDATOR); VALIDATORS.put(Global.CHARGING_VIBRATION_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Global.DEVICE_PROVISIONING_MOBILE_DATA_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Global.REQUIRE_PASSWORD_TO_DECRYPT, BOOLEAN_VALIDATOR); diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java index 410269f240e0..eba74ab14f3d 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java @@ -31,6 +31,7 @@ import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.hardware.display.ColorDisplayManager; import android.os.BatteryManager; +import android.provider.Settings.Global; import android.provider.Settings.System; import android.util.ArrayMap; @@ -239,5 +240,8 @@ public class SystemSettingsValidators { VALIDATORS.put(System.SCREEN_FLASH_NOTIFICATION_COLOR, ANY_INTEGER_VALIDATOR); VALIDATORS.put(System.PEAK_REFRESH_RATE, NON_NEGATIVE_FLOAT_VALIDATOR); VALIDATORS.put(System.MIN_REFRESH_RATE, NON_NEGATIVE_FLOAT_VALIDATOR); + VALIDATORS.put(System.NOTIFICATION_COOLDOWN_ENABLED, BOOLEAN_VALIDATOR); + VALIDATORS.put(System.NOTIFICATION_COOLDOWN_ALL, BOOLEAN_VALIDATOR); + VALIDATORS.put(System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED, BOOLEAN_VALIDATOR); } } diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index e40fcb2a633b..4954cb482120 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -60,7 +60,9 @@ systemui_compose_java_defaults { // except for SystemUI-core. // Copied from compose/features/Android.bp. static_libs: [ + "CommunalLayoutLib", "PlatformComposeCore", + "PlatformComposeSceneTransitionLayout", "androidx.compose.runtime_runtime", "androidx.compose.material3_material3", diff --git a/packages/SystemUI/aconfig/Android.bp b/packages/SystemUI/aconfig/Android.bp index c1390b252418..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", ], } diff --git a/packages/SystemUI/aconfig/communal.aconfig b/packages/SystemUI/aconfig/communal.aconfig new file mode 100644 index 000000000000..2c6ff979cc7f --- /dev/null +++ b/packages/SystemUI/aconfig/communal.aconfig @@ -0,0 +1,8 @@ +package: "com.android.systemui" + +flag { + name: "communal_hub" + namespace: "communal" + description: "Enables the communal hub experience" + bug: "304584416" +} diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 18117a890298..2509cfd4af40 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -21,3 +21,11 @@ flag { "(containing the \"Clear all\" button). Should not bring any behavior changes" bug: "293167744" } + +flag { + name: "notification_lifetime_extension_refactor" + namespace: "systemui" + description: "Enables moving notification lifetime extension management from SystemUI to " + "Notification Manager Service" + bug: "299448097" +} diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt index 4aac27932924..4ea57a8cc007 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt @@ -893,7 +893,7 @@ class ActivityLaunchAnimator( return } - Log.i(TAG, "Remote animation timed out") + Log.wtf(TAG, "Remote animation timed out") timedOut = true if (DEBUG_LAUNCH_ANIMATION) { diff --git a/packages/SystemUI/communal/layout/Android.bp b/packages/SystemUI/communal/layout/Android.bp new file mode 100644 index 000000000000..88dad6623d03 --- /dev/null +++ b/packages/SystemUI/communal/layout/Android.bp @@ -0,0 +1,35 @@ +// 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 { + default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], +} + +android_library { + name: "CommunalLayoutLib", + srcs: [ + "src/**/*.kt", + ], + static_libs: [ + "androidx.arch.core_core-runtime", + "androidx.compose.animation_animation-graphics", + "androidx.compose.runtime_runtime", + "androidx.compose.material3_material3", + "jsr330", + "kotlinx-coroutines-android", + "kotlinx-coroutines-core", + ], + manifest: "AndroidManifest.xml", + kotlincflags: ["-Xjvm-default=all"], +} diff --git a/packages/SystemUI/communal/layout/AndroidManifest.xml b/packages/SystemUI/communal/layout/AndroidManifest.xml new file mode 100644 index 000000000000..141be0762ae9 --- /dev/null +++ b/packages/SystemUI/communal/layout/AndroidManifest.xml @@ -0,0 +1,18 @@ +<?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 package="com.android.systemui.communal.layout" /> diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/CommunalLayoutEngine.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/CommunalLayoutEngine.kt new file mode 100644 index 000000000000..df87d19db5a6 --- /dev/null +++ b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/CommunalLayoutEngine.kt @@ -0,0 +1,66 @@ +/* + * 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.layout + +import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutCard + +/** Computes the arrangement of cards. */ +class CommunalLayoutEngine { + companion object { + /** + * Determines the size that each card should be rendered in, and distributes the cards into + * columns. + * + * Returns a nested list where the outer list contains columns, and the inner list contains + * cards in each column. + * + * Currently treats the first supported size as the size to be rendered in, ignoring other + * supported sizes. + */ + fun distributeCardsIntoColumns( + cards: List<CommunalGridLayoutCard>, + ): List<List<CommunalGridLayoutCardInfo>> { + val result = ArrayList<ArrayList<CommunalGridLayoutCardInfo>>() + + var capacityOfLastColumn = 0 + for (card in cards) { + val cardSize = card.supportedSizes.first() + if (capacityOfLastColumn >= cardSize.value) { + // Card fits in last column + capacityOfLastColumn -= cardSize.value + } else { + // Create a new column + result.add(arrayListOf()) + capacityOfLastColumn = CommunalGridLayoutCard.Size.FULL.value - cardSize.value + } + + result.last().add(CommunalGridLayoutCardInfo(card, cardSize)) + } + + return result + } + } + + /** + * A data class that wraps around a [CommunalGridLayoutCard] and also contains the size that the + * card should be rendered in. + */ + data class CommunalGridLayoutCardInfo( + val card: CommunalGridLayoutCard, + val size: CommunalGridLayoutCard.Size, + ) +} diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt new file mode 100644 index 000000000000..4ed78b3b95ed --- /dev/null +++ b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt @@ -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.systemui.communal.layout.ui.compose + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.android.systemui.communal.layout.CommunalLayoutEngine +import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutCard +import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutConfig + +/** + * An arrangement of cards with a horizontal scroll, where each card is displayed in the right size + * and follows a specific order based on its priority, ensuring a seamless layout without any gaps. + */ +@Composable +fun CommunalGridLayout( + modifier: Modifier, + layoutConfig: CommunalGridLayoutConfig, + communalCards: List<CommunalGridLayoutCard>, +) { + val columns = CommunalLayoutEngine.distributeCardsIntoColumns(communalCards) + LazyRow( + modifier = modifier.height(layoutConfig.gridHeight), + horizontalArrangement = Arrangement.spacedBy(layoutConfig.gridGutter), + ) { + for (column in columns) { + item { + Column( + modifier = Modifier.width(layoutConfig.cardWidth), + verticalArrangement = Arrangement.spacedBy(layoutConfig.gridGutter), + ) { + for (cardInfo in column) { + Row( + modifier = Modifier.height(layoutConfig.cardHeight(cardInfo.size)), + ) { + cardInfo.card.Content(Modifier.fillMaxSize()) + } + } + } + } + } + } +} diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt new file mode 100644 index 000000000000..ac8aa67fa4bf --- /dev/null +++ b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt @@ -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.systemui.communal.layout.ui.compose.config + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +/** A card that hosts content to be rendered in the communal grid layout. */ +abstract class CommunalGridLayoutCard { + /** + * Content to be hosted by the card. + * + * To host non-Compose views, see + * https://developer.android.com/jetpack/compose/migrate/interoperability-apis/views-in-compose. + */ + @Composable abstract fun Content(modifier: Modifier) + + /** + * Sizes supported by the card. + * + * If multiple sizes are available, they should be ranked in order of preference, from most to + * least preferred. + */ + abstract val supportedSizes: List<Size> + + /** + * Priority of the content hosted by the card. + * + * The value of priority is relative to other cards. Cards with a higher priority are generally + * ordered first. + */ + open val priority: Int = 0 + + /** + * Size of the card. + * + * @param value A numeric value that represents the size. Must be less than or equal to + * [Size.FULL]. + */ + enum class Size(val value: Int) { + /** The card takes up full height of the grid layout. */ + FULL(value = 6), + + /** The card takes up half of the vertical space of the grid layout. */ + HALF(value = 3), + + /** The card takes up a third of the vertical space of the grid layout. */ + THIRD(value = 2), + } +} diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfig.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfig.kt new file mode 100644 index 000000000000..143df838169b --- /dev/null +++ b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfig.kt @@ -0,0 +1,82 @@ +/* + * 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.layout.ui.compose.config + +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.times + +/** + * Configurations of the communal grid layout. + * + * The communal grid layout follows Material Design's responsive layout grid (see + * https://m2.material.io/design/layout/responsive-layout-grid.html), in which the layout is divided + * up by columns and gutters, and each card occupies one or multiple columns. + */ +data class CommunalGridLayoutConfig( + /** + * Size in dp of each grid column. + * + * Every card occupies one or more grid columns, which means that the width of each card is + * influenced by the size of the grid columns. + */ + val gridColumnSize: Dp, + + /** + * Size in dp of each grid gutter. + * + * A gutter is the space between columns that helps separate content. This is, therefore, also + * the size of the gaps between cards, both horizontally and vertically. + */ + val gridGutter: Dp, + + /** + * Height in dp of the grid layout. + * + * Cards with a full size take up the entire height of the grid layout. + */ + val gridHeight: Dp, + + /** + * Number of grid columns that each card occupies. + * + * It's important to note that all the cards take up the same number of grid columns, or in + * simpler terms, they all have the same width. + */ + val gridColumnsPerCard: Int, +) { + /** + * Width in dp of each card. + * + * It's important to note that all the cards take up the same number of grid columns, or in + * simpler terms, they all have the same width. + */ + val cardWidth = gridColumnSize * gridColumnsPerCard + gridGutter * (gridColumnsPerCard - 1) + + /** Returns the height of a card in dp, based on its size. */ + fun cardHeight(cardSize: CommunalGridLayoutCard.Size): Dp { + return when (cardSize) { + CommunalGridLayoutCard.Size.FULL -> cardHeightBy(denominator = 1) + CommunalGridLayoutCard.Size.HALF -> cardHeightBy(denominator = 2) + CommunalGridLayoutCard.Size.THIRD -> cardHeightBy(denominator = 3) + } + } + + /** Returns the height of a card in dp when the layout is evenly divided by [denominator]. */ + private fun cardHeightBy(denominator: Int): Dp { + return (gridHeight - (denominator - 1) * gridGutter) / denominator + } +} diff --git a/packages/SystemUI/communal/layout/tests/Android.bp b/packages/SystemUI/communal/layout/tests/Android.bp new file mode 100644 index 000000000000..a60b1de5b501 --- /dev/null +++ b/packages/SystemUI/communal/layout/tests/Android.bp @@ -0,0 +1,47 @@ +// 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 { + default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], +} + +android_test { + name: "CommunalLayoutLibTests", + srcs: [ + "**/*.kt", + ], + static_libs: [ + "CommunalLayoutLib", + "androidx.test.runner", + "androidx.test.rules", + "androidx.test.ext.junit", + "frameworks-base-testutils", + "junit", + "kotlinx_coroutines_test", + "mockito-target-extended-minus-junit4", + "platform-test-annotations", + "testables", + "truth-prebuilt", + ], + libs: [ + "android.test.mock", + "android.test.base", + "android.test.runner", + ], + jni_libs: [ + "libdexmakerjvmtiagent", + "libstaticjvmtiagent", + ], + manifest: "AndroidManifest.xml", +} diff --git a/packages/SystemUI/communal/layout/tests/AndroidManifest.xml b/packages/SystemUI/communal/layout/tests/AndroidManifest.xml new file mode 100644 index 000000000000..b19007c1ff1b --- /dev/null +++ b/packages/SystemUI/communal/layout/tests/AndroidManifest.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest + xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.systemui.communal.layout.tests"> + + <application android:debuggable="true" android:largeHeap="true"> + <uses-library android:name="android.test.mock" /> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="android.testing.TestableInstrumentation" + android:targetPackage="com.android.systemui.communal.layout.tests" + android:label="Tests for CommunalLayoutLib"> + </instrumentation> + +</manifest> diff --git a/packages/SystemUI/communal/layout/tests/AndroidTest.xml b/packages/SystemUI/communal/layout/tests/AndroidTest.xml new file mode 100644 index 000000000000..1352b238f6fe --- /dev/null +++ b/packages/SystemUI/communal/layout/tests/AndroidTest.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<configuration description="Runs tests for CommunalLayoutLib"> + + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="install-arg" value="-t" /> + <option name="test-file-name" value="CommunalLayoutLibTests.apk" /> + </target_preparer> + + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="framework-base-presubmit" /> + <option name="test-tag" value="CommunalLayoutLibTests" /> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.systemui.communal.layout.tests" /> + <option name="runner" value="android.testing.TestableInstrumentation" /> + <option name="hidden-api-checks" value="false"/> + </test> + +</configuration> diff --git a/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt b/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt new file mode 100644 index 000000000000..fdf65f5d5cc7 --- /dev/null +++ b/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt @@ -0,0 +1,99 @@ +package com.android.systemui.communal.layout + +import androidx.compose.material3.Card +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutCard +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class CommunalLayoutEngineTest { + @Test + fun distribution_fullLayout() { + val cards = + listOf( + generateCard(CommunalGridLayoutCard.Size.FULL), + generateCard(CommunalGridLayoutCard.Size.HALF), + generateCard(CommunalGridLayoutCard.Size.HALF), + generateCard(CommunalGridLayoutCard.Size.THIRD), + generateCard(CommunalGridLayoutCard.Size.THIRD), + generateCard(CommunalGridLayoutCard.Size.THIRD), + ) + val expected = + listOf( + listOf( + CommunalGridLayoutCard.Size.FULL, + ), + listOf( + CommunalGridLayoutCard.Size.HALF, + CommunalGridLayoutCard.Size.HALF, + ), + listOf( + CommunalGridLayoutCard.Size.THIRD, + CommunalGridLayoutCard.Size.THIRD, + CommunalGridLayoutCard.Size.THIRD, + ), + ) + + assertDistribution(cards, expected) + } + + @Test + fun distribution_layoutWithGaps() { + val cards = + listOf( + generateCard(CommunalGridLayoutCard.Size.HALF), + generateCard(CommunalGridLayoutCard.Size.THIRD), + generateCard(CommunalGridLayoutCard.Size.HALF), + generateCard(CommunalGridLayoutCard.Size.FULL), + generateCard(CommunalGridLayoutCard.Size.THIRD), + ) + val expected = + listOf( + listOf( + CommunalGridLayoutCard.Size.HALF, + CommunalGridLayoutCard.Size.THIRD, + ), + listOf( + CommunalGridLayoutCard.Size.HALF, + ), + listOf( + CommunalGridLayoutCard.Size.FULL, + ), + listOf( + CommunalGridLayoutCard.Size.THIRD, + ), + ) + + assertDistribution(cards, expected) + } + + private fun assertDistribution( + cards: List<CommunalGridLayoutCard>, + expected: List<List<CommunalGridLayoutCard.Size>>, + ) { + val result = CommunalLayoutEngine.distributeCardsIntoColumns(cards) + + for (c in expected.indices) { + for (r in expected[c].indices) { + assertThat(result[c][r].size).isEqualTo(expected[c][r]) + } + } + } + + private fun generateCard(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/communal/layout/tests/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfigTest.kt b/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfigTest.kt new file mode 100644 index 000000000000..946eeecbec5d --- /dev/null +++ b/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfigTest.kt @@ -0,0 +1,63 @@ +package com.android.systemui.communal.layout.ui.compose.config + +import androidx.compose.ui.unit.dp +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.google.common.truth.Truth +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class CommunalGridLayoutConfigTest { + @Test + fun cardWidth() { + Truth.assertThat( + CommunalGridLayoutConfig( + gridColumnSize = 5.dp, + gridGutter = 3.dp, + gridHeight = 17.dp, + gridColumnsPerCard = 1, + ) + .cardWidth + ) + .isEqualTo(5.dp) + + Truth.assertThat( + CommunalGridLayoutConfig( + gridColumnSize = 5.dp, + gridGutter = 3.dp, + gridHeight = 17.dp, + gridColumnsPerCard = 2, + ) + .cardWidth + ) + .isEqualTo(13.dp) + + Truth.assertThat( + CommunalGridLayoutConfig( + gridColumnSize = 5.dp, + gridGutter = 3.dp, + gridHeight = 17.dp, + gridColumnsPerCard = 3, + ) + .cardWidth + ) + .isEqualTo(21.dp) + } + + @Test + fun cardHeight() { + val config = + CommunalGridLayoutConfig( + gridColumnSize = 5.dp, + gridGutter = 2.dp, + gridHeight = 10.dp, + gridColumnsPerCard = 3, + ) + + Truth.assertThat(config.cardHeight(CommunalGridLayoutCard.Size.FULL)).isEqualTo(10.dp) + Truth.assertThat(config.cardHeight(CommunalGridLayoutCard.Size.HALF)).isEqualTo(4.dp) + Truth.assertThat(config.cardHeight(CommunalGridLayoutCard.Size.THIRD)).isEqualTo(2.dp) + } +} 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 5b4a8fb6ab7a..3d670b809d15 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 @@ -64,6 +64,12 @@ object ComposeFacade : BaseComposeFacade { throwComposeUnavailableError() } + override fun createCommunalView( + context: Context, + ): View { + throwComposeUnavailableError() + } + private fun throwComposeUnavailableError(): Nothing { error( "Compose is not available. Make sure to check isComposeAvailable() before calling any" + 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 ac599897553a..7b11ac7f4e1e 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 @@ -30,6 +30,7 @@ import com.android.compose.theme.PlatformTheme 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.people.ui.compose.PeopleScreen import com.android.systemui.people.ui.viewmodel.PeopleViewModel import com.android.systemui.qs.footer.ui.compose.FooterActions @@ -93,6 +94,12 @@ object ComposeFacade : BaseComposeFacade { } } + override fun createCommunalView( + context: Context, + ): View { + return ComposeView(context).apply { setContent { PlatformTheme { CommunalHub() } } } + } + // TODO(b/298525212): remove once Compose exposes window inset bounds. private fun displayCutoutFromWindowInsets( scope: CoroutineScope, diff --git a/packages/SystemUI/compose/features/Android.bp b/packages/SystemUI/compose/features/Android.bp index e4426fe97859..796abf4b52d6 100644 --- a/packages/SystemUI/compose/features/Android.bp +++ b/packages/SystemUI/compose/features/Android.bp @@ -33,6 +33,7 @@ android_library { static_libs: [ "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 new file mode 100644 index 000000000000..4d2978df7b1b --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt @@ -0,0 +1,22 @@ +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.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color + +@Composable +fun CommunalHub(modifier: Modifier = Modifier) { + Box( + modifier = modifier.fillMaxSize().background(Color.White), + ) { + Text( + modifier = Modifier.align(Alignment.Center), + text = "Hello Communal!", + ) + } +} 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 0d2ba2824846..d1c12ac85cc5 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 @@ -16,14 +16,8 @@ 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.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import com.android.compose.animation.scene.SceneScope import com.android.systemui.dagger.SysUISingleton import com.android.systemui.scene.shared.model.Direction @@ -51,13 +45,6 @@ class CommunalScene @Inject constructor() : ComposableScene { @Composable override fun SceneScope.Content(modifier: Modifier) { - Box( - modifier = modifier.fillMaxSize().background(Color.White), - ) { - Text( - modifier = Modifier.align(Alignment.Center), - text = "Hello Communal!", - ) - } + CommunalHub(modifier) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt index e12b7eae96e7..73cb72ca062e 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt @@ -47,10 +47,10 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.android.compose.theme.LocalAndroidColorScheme -import com.android.systemui.res.R import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.people.ui.viewmodel.PeopleTileViewModel import com.android.systemui.people.ui.viewmodel.PeopleViewModel +import com.android.systemui.res.R /** * Compose the screen associated to a [PeopleViewModel]. @@ -86,9 +86,9 @@ fun PeopleScreen( modifier = Modifier.fillMaxSize(), ) { if (priorityTiles.isNotEmpty() || recentTiles.isNotEmpty()) { - PeopleScreenWithConversations(priorityTiles, recentTiles, viewModel::onTileClicked) + PeopleScreenWithConversations(priorityTiles, recentTiles, viewModel.onTileClicked) } else { - PeopleScreenEmpty(viewModel::onUserJourneyCancelled) + PeopleScreenEmpty(viewModel.onUserJourneyCancelled) } } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt index 0da562bcb3bb..2e93a09deb30 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt @@ -161,8 +161,7 @@ private fun SceneTransitionObservableTransitionState.toModel(): ObservableTransi fromScene = fromScene.toModel().key, toScene = toScene.toModel().key, progress = progress, - isInitiatedByUserInput = isInitiatedByUserInput, - isUserInputOngoing = isUserInputOngoing, + isUserInputDriven = isUserInputDriven, ) } } 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 60c3fd3c4cdb..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 @@ -108,7 +108,7 @@ private fun CoroutineScope.animate( ) { val fromScene = layoutImpl.state.transitionState.currentScene val isUserInput = - (layoutImpl.state.transitionState as? TransitionState.Transition)?.isInitiatedByUserInput + (layoutImpl.state.transitionState as? TransitionState.Transition)?.isUserInputDriven ?: false val animationSpec = layoutImpl.transitions.transitionSpec(fromScene, target).spec @@ -119,23 +119,9 @@ private fun CoroutineScope.animate( val targetProgress = if (reversed) 0f else 1f val transition = if (reversed) { - OneOffTransition( - fromScene = target, - toScene = fromScene, - currentScene = target, - isUserInput, - isUserInputOngoing = false, - animatable, - ) + OneOffTransition(target, fromScene, currentScene = target, isUserInput, animatable) } else { - OneOffTransition( - fromScene = fromScene, - toScene = target, - currentScene = target, - isUserInput, - isUserInputOngoing = false, - animatable, - ) + OneOffTransition(fromScene, target, currentScene = target, isUserInput, animatable) } // Change the current layout state to use this new transition. @@ -156,8 +142,7 @@ private class OneOffTransition( override val fromScene: SceneKey, override val toScene: SceneKey, override val currentScene: SceneKey, - override val isInitiatedByUserInput: Boolean, - override val isUserInputOngoing: Boolean, + override val isUserInputDriven: Boolean, private val animatable: Animatable<Float, AnimationVector1D>, ) : TransitionState.Transition { override val progress: Float 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 3bcd920fb02b..ce96bbfc7976 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 @@ -21,6 +21,7 @@ import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.SideEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue +import androidx.compose.runtime.movableContentOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -59,6 +60,17 @@ internal class Element(val key: ElementKey) { /** The mapping between a scene and the values/state this element has in that scene, if any. */ val sceneValues = SnapshotStateMap<SceneKey, TargetValues>() + /** + * The movable content of this element, if this element is composed using + * [SceneScope.MovableElement]. + */ + val movableContent by + // This is only accessed from the composition (main) thread, so no need to use the default + // lock of lazy {} to synchronize. + lazy(mode = LazyThreadSafetyMode.NONE) { + movableContentOf { content: @Composable () -> Unit -> content() } + } + override fun toString(): String { return "Element(key=$key)" } 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 b7acc48e2865..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 @@ -22,7 +22,7 @@ import androidx.annotation.VisibleForTesting * A base class to create unique keys, associated to an [identity] that is used to check the * equality of two key instances. */ -sealed class Key(val name: String, val identity: Any) { +sealed class Key(val debugName: String, val identity: Any) { override fun equals(other: Any?): Boolean { if (this === other) return true if (this.javaClass != other?.javaClass) return false @@ -34,7 +34,7 @@ sealed class Key(val name: String, val identity: Any) { } override fun toString(): String { - return "Key(name=$name)" + return "Key(debugName=$debugName)" } } @@ -49,7 +49,7 @@ class SceneKey( val rootElementKey = ElementKey(name, identity) override fun toString(): String { - return "SceneKey(name=$name)" + return "SceneKey(debugName=$debugName)" } } @@ -71,7 +71,7 @@ class ElementKey( } override fun toString(): String { - return "ElementKey(name=$name)" + return "ElementKey(debugName=$debugName)" } companion object { @@ -89,6 +89,6 @@ class ElementKey( /** Key for a shared value of an element. */ class ValueKey(name: String, identity: Any = Object()) : Key(name, identity) { override fun toString(): String { - return "ValueKey(name=$name)" + return "ValueKey(debugName=$debugName)" } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt new file mode 100644 index 000000000000..6dbeb69ff450 --- /dev/null +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt @@ -0,0 +1,200 @@ +/* + * 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 android.graphics.Picture +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.remember +import androidx.compose.runtime.snapshots.Snapshot +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.draw.drawWithCache +import androidx.compose.ui.graphics.Canvas +import androidx.compose.ui.graphics.drawscope.draw +import androidx.compose.ui.graphics.drawscope.drawIntoCanvas +import androidx.compose.ui.graphics.nativeCanvas +import androidx.compose.ui.layout.layout +import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.IntSize + +private const val TAG = "MovableElement" + +@Composable +internal fun MovableElement( + layoutImpl: SceneTransitionLayoutImpl, + scene: Scene, + key: ElementKey, + modifier: Modifier, + content: @Composable MovableElementScope.() -> Unit, +) { + Box(modifier.element(layoutImpl, scene, key)) { + // Get the Element from the map. It will always be the same and we don't want to recompose + // 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)) { + Box( + Modifier.drawWithCache { + val width = size.width.toInt() + val height = size.height.toInt() + + onDrawWithContent { + // Save the draw commands into [picture] for later to draw the last content + // even when this movable content is not composed. + val pictureCanvas = Canvas(picture.beginRecording(width, height)) + draw(this, this.layoutDirection, pictureCanvas, this.size) { + this@onDrawWithContent.drawContent() + } + picture.endRecording() + + // Draw the content. + drawIntoCanvas { canvas -> canvas.nativeCanvas.drawPicture(picture) } + } + } + ) { + element.movableContent { movableElementScope.content() } + } + } else { + // If we are not composed, we draw the previous drawing commands at the same size as the + // movable content when it was composed in this scene. + val sceneValues = element.sceneValues.getValue(scene.key) + + Spacer( + Modifier.layout { measurable, _ -> + val size = + sceneValues.targetSize.takeIf { it != Element.SizeUnspecified } + ?: IntSize.Zero + val placeable = + measurable.measure(Constraints.fixed(size.width, size.height)) + layout(size.width, size.height) { placeable.place(0, 0) } + } + .drawBehind { + drawIntoCanvas { canvas -> canvas.nativeCanvas.drawPicture(picture) } + } + ) + } + } +} + +private fun shouldComposeMovableElement( + layoutImpl: SceneTransitionLayoutImpl, + scene: SceneKey, + element: Element, +): Boolean { + val transitionState = layoutImpl.state.transitionState + + // If we are idle, there is only one [scene] that is composed so we can compose our movable + // content here. + if (transitionState is TransitionState.Idle) { + check(transitionState.currentScene == scene) + return true + } + + val fromScene = (transitionState as TransitionState.Transition).fromScene + val toScene = transitionState.toScene + if (fromScene == toScene) { + check(fromScene == scene) + return true + } + + val fromReady = layoutImpl.isSceneReady(fromScene) + val toReady = layoutImpl.isSceneReady(toScene) + + val otherScene = + when (scene) { + fromScene -> toScene + toScene -> fromScene + else -> + error( + "shouldComposeMovableElement(scene=$scene) called with fromScene=$fromScene " + + "and toScene=$toScene" + ) + } + + val isShared = otherScene in element.sceneValues + + if (isShared && !toReady && !fromReady) { + // This should usually not happen given that fromScene should be ready, but let's log a + // warning here in case it does so it helps debugging flicker issues caused by this part of + // the code. + Log.w( + TAG, + "MovableElement $element might have to be composed for the first time in both " + + "fromScene=$fromScene and toScene=$toScene. This will probably lead to a flicker " + + "where the size of the element will jump from IntSize.Zero to its actual size " + + "during the transition." + ) + } + + // Element is not shared in this transition. + if (!isShared) { + return true + } + + // toScene is not ready (because we are composing it for the first time), so we compose it there + // first. This is the most common scenario when starting a transition that has a shared movable + // element. + if (!toReady) { + return scene == toScene + } + + // This should usually not happen, but if we are also composing for the first time in fromScene + // then we should compose it there only. + if (!fromReady) { + 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 + } +} + +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 1b79dbdee510..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 @@ -52,14 +52,7 @@ sealed class ObservableTransitionState { * scene, this value will remain true after the pointer is no longer touching the screen and * will be true in any transition created to animate back to the original position. */ - val isInitiatedByUserInput: Boolean, - - /** - * Whether user input is currently driving the transition. For example, if a user is - * dragging a pointer, this emits true. Once they lift their finger, this emits false while - * the transition completes/settles. - */ - val isUserInputOngoing: Flow<Boolean>, + val isUserInputDriven: Boolean, ) : ObservableTransitionState() } @@ -80,8 +73,7 @@ fun SceneTransitionLayoutState.observableTransitionState(): Flow<ObservableTrans fromScene = state.fromScene, toScene = state.toScene, progress = snapshotFlow { state.progress }, - isInitiatedByUserInput = state.isInitiatedByUserInput, - isUserInputOngoing = snapshotFlow { state.isUserInputOngoing }, + isUserInputDriven = state.isUserInputDriven, ) } } 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 3985233bd197..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 @@ -90,4 +90,13 @@ private class SceneScopeImpl( canOverflow, ) } + + @Composable + override fun MovableElement( + key: ElementKey, + modifier: Modifier, + content: @Composable MovableElementScope.() -> Unit, + ) { + MovableElement(layoutImpl, scene, key, modifier, content) + } } 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 58c7bdbf3d71..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 @@ -85,6 +85,13 @@ interface SceneTransitionLayoutScope { ) } +/** + * A DSL marker to prevent people from nesting calls to Modifier.element() inside a MovableElement, + * which is not supported. + */ +@DslMarker annotation class ElementDsl + +@ElementDsl interface SceneScope { /** * Tag an element identified by [key]. @@ -95,12 +102,37 @@ interface SceneScope { * Additionally, this [key] will be used to detect elements that are shared between scenes to * automatically interpolate their size, offset and [shared values][animateSharedValueAsState]. * + * Note that shared elements tagged using this function will be duplicated in each scene they + * are part of, so any **internal** state (e.g. state created using `remember { + * mutableStateOf(...) }`) will be lost. If you need to preserve internal state, you should use + * [MovableElement] instead. + * + * @see MovableElement + * * TODO(b/291566282): Migrate this to the new Modifier Node API and remove the @Composable * constraint. */ @Composable fun Modifier.element(key: ElementKey): Modifier /** + * Create a *movable* element identified by [key]. + * + * This creates an element that will be automatically shared when present in multiple scenes and + * that can be transformed during transitions, the same way that [element] does. The major + * difference with [element] is that elements created with [MovableElement] will be "moved" and + * composed only once during transitions (as opposed to [element] that duplicates shared + * elements) so that any internal state is preserved during and after the transition. + * + * @see element + */ + @Composable + fun MovableElement( + key: ElementKey, + modifier: Modifier, + content: @Composable MovableElementScope.() -> Unit, + ) + + /** * Animate some value of a shared element. * * @param value the value of this shared value in the current scene. @@ -126,6 +158,19 @@ interface SceneScope { ): State<T> } +// TODO(b/291053742): Add animateSharedValueAsState(targetValue) without any ValueKey and ElementKey +// arguments to allow sharing values inside a movable element. +@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 b3a7a8e9f874..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 @@ -199,4 +199,6 @@ internal class SceneTransitionLayoutImpl( return readyScenes.containsKey(transition.fromScene) && readyScenes.containsKey(transition.toScene) } + + internal fun isSceneReady(scene: SceneKey): Boolean = readyScenes.containsKey(scene) } 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 b9f83c545122..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 @@ -70,9 +70,6 @@ sealed interface TransitionState { val progress: Float /** Whether the transition was triggered by user input rather than being programmatic. */ - val isInitiatedByUserInput: Boolean - - /** Whether user input is currently driving the transition. */ - val isUserInputOngoing: Boolean + val isUserInputDriven: Boolean } } 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 e275fcaf4572..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 @@ -66,7 +66,7 @@ internal fun Modifier.swipeToScene( // swipe in the other direction. val startDragImmediately = state == transition && - !transition.isUserInputOngoing && + transition.isAnimatingOffset && !currentScene.shouldEnableSwipes(orientation.opposite()) // The velocity threshold at which the intent of the user is to swipe up or down. It is the same @@ -126,7 +126,7 @@ private class SwipeTransition(initialScene: Scene) : TransitionState.Transition override val progress: Float get() { - val offset = if (isUserInputOngoing) dragOffset else offsetAnimatable.value + val offset = if (isAnimatingOffset) offsetAnimatable.value else dragOffset if (distance == 0f) { // This can happen only if fromScene == toScene. error( @@ -137,15 +137,16 @@ private class SwipeTransition(initialScene: Scene) : TransitionState.Transition return offset / distance } - override val isInitiatedByUserInput = true - - var _isUserInputOngoing by mutableStateOf(false) - override val isUserInputOngoing: Boolean - get() = _isUserInputOngoing + override val isUserInputDriven = true /** The current offset caused by the drag gesture. */ var dragOffset by mutableFloatStateOf(0f) + /** + * Whether the offset is animated (the user lifted their finger) or if it is driven by gesture. + */ + var isAnimatingOffset by mutableStateOf(false) + /** The animatable used to animate the offset once the user lifted its finger. */ val offsetAnimatable = Animatable(0f, visibilityThreshold = OffsetVisibilityThreshold) @@ -208,11 +209,9 @@ private fun onDragStarted( transition: SwipeTransition, orientation: Orientation, ) { - transition._isUserInputOngoing = true - if (layoutImpl.state.transitionState == transition) { // This [transition] was already driving the animation: simply take over it. - if (!transition.isUserInputOngoing) { + if (transition.isAnimatingOffset) { // Stop animating and start from where the current offset. Setting the animation job to // `null` will effectively cancel the animation. transition.stopOffsetAnimation() @@ -457,29 +456,30 @@ private fun CoroutineScope.animateOffset( ) { transition.startOffsetAnimation { launch { - if (transition.isUserInputOngoing) { - transition.offsetAnimatable.snapTo(transition.dragOffset) - } - transition._isUserInputOngoing = false - - transition.offsetAnimatable.animateTo( - targetOffset, - // TODO(b/290184746): Make this spring spec configurable. - spring( - stiffness = Spring.StiffnessMediumLow, - visibilityThreshold = OffsetVisibilityThreshold - ), - initialVelocity = initialVelocity, - ) + if (!transition.isAnimatingOffset) { + transition.offsetAnimatable.snapTo(transition.dragOffset) + } + transition.isAnimatingOffset = true + + transition.offsetAnimatable.animateTo( + targetOffset, + // TODO(b/290184746): Make this spring spec configurable. + spring( + stiffness = Spring.StiffnessMediumLow, + visibilityThreshold = OffsetVisibilityThreshold + ), + initialVelocity = initialVelocity, + ) - // Now that the animation is done, the state should be idle. Note that if the state - // was changed since this animation started, some external code changed it and we - // shouldn't do anything here. Note also that this job will be cancelled in the case - // where the user intercepts this swipe. - if (layoutImpl.state.transitionState == transition) { - layoutImpl.state.transitionState = TransitionState.Idle(targetScene) + // Now that the animation is done, the state should be idle. Note that if the state + // was changed since this animation started, some external code changed it and we + // shouldn't do anything here. Note also that this job will be cancelled in the case + // where the user intercepts this swipe. + if (layoutImpl.state.transitionState == transition) { + layoutImpl.state.transitionState = TransitionState.Idle(targetScene) + } } - } + .also { it.invokeOnCompletion { transition.isAnimatingOffset = false } } } } 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..49669775fedd 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 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..f1c27178391c 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 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..2ef8d56c6bc6 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 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/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt new file mode 100644 index 000000000000..4204cd5f0da0 --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt @@ -0,0 +1,207 @@ +/* + * 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.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.hasParent +import androidx.compose.ui.test.hasText +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onAllNodesWithText +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.compose.ui.unit.dp +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.compose.test.assertSizeIsEqualTo +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class MovableElementTest { + @get:Rule val rule = createComposeRule() + + /** An element that displays a counter that is incremented whenever this element is clicked. */ + @Composable + private fun Counter(modifier: Modifier = Modifier) { + var count by remember { mutableIntStateOf(0) } + Box(modifier.fillMaxSize().clickable { count++ }) { Text("count: $count") } + } + + @Composable + private fun SceneScope.MovableCounter(key: ElementKey, modifier: Modifier) { + MovableElement(key, modifier) { Counter() } + } + + @Test + fun modifierElementIsDuplicatedDuringTransitions() { + rule.testTransition( + fromSceneContent = { + Box(Modifier.element(TestElements.Foo).size(50.dp)) { Counter() } + }, + toSceneContent = { Box(Modifier.element(TestElements.Foo).size(100.dp)) { Counter() } }, + transition = { spec = tween(durationMillis = 16 * 4, easing = LinearEasing) }, + fromScene = TestScenes.SceneA, + toScene = TestScenes.SceneB, + ) { + before { + // Click 3 times on the counter. + rule.onNodeWithText("count: 0").assertIsDisplayed().performClick() + rule.onNodeWithText("count: 1").assertIsDisplayed().performClick() + rule.onNodeWithText("count: 2").assertIsDisplayed().performClick() + rule + .onNodeWithText("count: 3") + .assertIsDisplayed() + .assertSizeIsEqualTo(50.dp, 50.dp) + + // There are no other counters. + assertThat( + rule + .onAllNodesWithText("count: ", substring = true) + .fetchSemanticsNodes() + .size + ) + .isEqualTo(1) + } + + at(32) { + // In the middle of the transition, there are 2 copies of the counter: the previous + // one from scene A (equal to 3) and the new one from scene B (equal to 0). + rule + .onNode( + hasText("count: 3") and + hasParent(isElement(TestElements.Foo, scene = TestScenes.SceneA)) + ) + .assertIsDisplayed() + .assertSizeIsEqualTo(75.dp, 75.dp) + + rule + .onNode( + hasText("count: 0") and + hasParent(isElement(TestElements.Foo, scene = TestScenes.SceneB)) + ) + .assertIsDisplayed() + .assertSizeIsEqualTo(75.dp, 75.dp) + + // There are exactly 2 counters. + assertThat( + rule + .onAllNodesWithText("count: ", substring = true) + .fetchSemanticsNodes() + .size + ) + .isEqualTo(2) + } + + after { + // At the end of the transition, only the counter from scene B is composed. + rule + .onNodeWithText("count: 0") + .assertIsDisplayed() + .assertSizeIsEqualTo(100.dp, 100.dp) + + // There are no other counters. + assertThat( + rule + .onAllNodesWithText("count: ", substring = true) + .fetchSemanticsNodes() + .size + ) + .isEqualTo(1) + } + } + } + + @Test + fun movableElementIsMovedAndComposedOnlyOnce() { + 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) }, + fromScene = TestScenes.SceneA, + toScene = TestScenes.SceneB, + ) { + before { + // Click 3 times on the counter. + rule.onNodeWithText("count: 0").assertIsDisplayed().performClick() + rule.onNodeWithText("count: 1").assertIsDisplayed().performClick() + rule.onNodeWithText("count: 2").assertIsDisplayed().performClick() + rule + .onNodeWithText("count: 3") + .assertIsDisplayed() + .assertSizeIsEqualTo(50.dp, 50.dp) + + // There are no other counters. + assertThat( + rule + .onAllNodesWithText("count: ", substring = true) + .fetchSemanticsNodes() + .size + ) + .isEqualTo(1) + } + + at(32) { + // During the transition, there is a single counter that is moved, with the current + // value. + rule + .onNode(hasText("count: 3")) + .assertIsDisplayed() + .assertSizeIsEqualTo(75.dp, 75.dp) + + // 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 + .onNodeWithText("count: 3") + .assertIsDisplayed() + .assertSizeIsEqualTo(100.dp, 100.dp) + + // There are no other counters. + assertThat( + rule + .onAllNodesWithText("count: ", substring = true) + .fetchSemanticsNodes() + .size + ) + .isEqualTo(1) + } + } + } +} 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 328866ea76ca..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 @@ -117,7 +117,7 @@ class SceneTransitionLayoutTest { .size(size) .background(Color.Red) .element(TestElements.Foo) - .testTag(TestElements.Foo.name) + .testTag(TestElements.Foo.debugName) ) { // Offset the single child of Foo by some animated shared offset. val offset by animateSharedDpAsState(childOffset, TestValues.Value1, TestElements.Foo) @@ -129,7 +129,7 @@ class SceneTransitionLayoutTest { } .size(30.dp) .background(Color.Blue) - .testTag(TestElements.Bar.name) + .testTag(TestElements.Bar.debugName) ) } } 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 53ed2b5d3317..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 @@ -63,7 +63,7 @@ class SwipeToSceneTest { { currentScene = it }, EmptyTestTransitions, state = layoutState, - modifier = Modifier.size(LayoutWidth, LayoutHeight).testTag(TestElements.Foo.name), + modifier = Modifier.size(LayoutWidth, LayoutHeight).testTag(TestElements.Foo.debugName), ) { scene( TestScenes.SceneA, @@ -122,8 +122,7 @@ class SwipeToSceneTest { assertThat(transition.toScene).isEqualTo(TestScenes.SceneB) assertThat(transition.currentScene).isEqualTo(TestScenes.SceneA) assertThat(transition.progress).isEqualTo(55.dp / LayoutWidth) - assertThat(transition.isInitiatedByUserInput).isTrue() - assertThat(transition.isUserInputOngoing).isTrue() + assertThat(transition.isUserInputDriven).isTrue() // Release the finger. We should now be animating back to A (currentScene = SceneA) given // that 55dp < positional threshold. @@ -135,8 +134,7 @@ class SwipeToSceneTest { assertThat(transition.toScene).isEqualTo(TestScenes.SceneB) assertThat(transition.currentScene).isEqualTo(TestScenes.SceneA) assertThat(transition.progress).isEqualTo(55.dp / LayoutWidth) - assertThat(transition.isInitiatedByUserInput).isTrue() - assertThat(transition.isUserInputOngoing).isFalse() + assertThat(transition.isUserInputDriven).isTrue() // Wait for the animation to finish. We should now be in scene A. rule.waitForIdle() @@ -158,8 +156,7 @@ class SwipeToSceneTest { assertThat(transition.toScene).isEqualTo(TestScenes.SceneC) assertThat(transition.currentScene).isEqualTo(TestScenes.SceneA) assertThat(transition.progress).isEqualTo(56.dp / LayoutHeight) - assertThat(transition.isInitiatedByUserInput).isTrue() - assertThat(transition.isUserInputOngoing).isTrue() + assertThat(transition.isUserInputDriven).isTrue() // Release the finger. We should now be animating to C (currentScene = SceneC) given // that 56dp >= positional threshold. @@ -171,8 +168,7 @@ class SwipeToSceneTest { assertThat(transition.toScene).isEqualTo(TestScenes.SceneC) assertThat(transition.currentScene).isEqualTo(TestScenes.SceneC) assertThat(transition.progress).isEqualTo(56.dp / LayoutHeight) - assertThat(transition.isInitiatedByUserInput).isTrue() - assertThat(transition.isUserInputOngoing).isFalse() + assertThat(transition.isUserInputDriven).isTrue() // Wait for the animation to finish. We should now be in scene C. rule.waitForIdle() 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 268057fd2f2c..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 @@ -22,13 +22,13 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.test.SemanticsMatcher import androidx.compose.ui.test.SemanticsNodeInteraction import androidx.compose.ui.test.SemanticsNodeInteractionCollection import androidx.compose.ui.test.hasParent import androidx.compose.ui.test.hasTestTag import androidx.compose.ui.test.junit4.ComposeContentTestRule import androidx.compose.ui.test.onAllNodesWithTag -import androidx.compose.ui.test.onNodeWithTag @DslMarker annotation class TransitionTestDsl @@ -63,6 +63,8 @@ interface TransitionTestBuilder { @TransitionTestDsl interface TransitionTestAssertionScope { + fun isElement(element: ElementKey, scene: SceneKey? = null): SemanticsMatcher + /** * Assert on [element]. * @@ -130,15 +132,19 @@ fun ComposeContentTestRule.testTransition( val test = transitionTest(builder) val assertionScope = object : TransitionTestAssertionScope { + override fun isElement(element: ElementKey, scene: SceneKey?): SemanticsMatcher { + return if (scene == null) { + hasTestTag(element.testTag) + } else { + hasTestTag(element.testTag) and hasParent(hasTestTag(scene.testTag)) + } + } + override fun onElement( element: ElementKey, scene: SceneKey? ): SemanticsNodeInteraction { - return if (scene == null) { - onNodeWithTag(element.testTag) - } else { - onNode(hasTestTag(element.testTag) and hasParent(hasTestTag(scene.testTag))) - } + return onNode(isElement(element, scene)) } override fun onSharedElement(element: ElementKey): SemanticsNodeInteractionCollection { 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/proguard_common.flags b/packages/SystemUI/proguard_common.flags index be1e6554baf1..445bdc2c1936 100644 --- a/packages/SystemUI/proguard_common.flags +++ b/packages/SystemUI/proguard_common.flags @@ -2,45 +2,17 @@ # Needed to ensure callback field references are kept in their respective # owning classes when the downstream callback registrars only store weak refs. -# TODO(b/264686688): Handle these cases with more targeted annotations. --keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** { - private com.android.keyguard.KeyguardUpdateMonitorCallback *; - private com.android.systemui.privacy.PrivacyConfig$Callback *; - private com.android.systemui.privacy.PrivacyItemController$Callback *; - private com.android.systemui.settings.UserTracker$Callback *; - private com.android.systemui.statusbar.phone.StatusBarWindowCallback *; - private com.android.systemui.util.service.Observer$Callback *; - private com.android.systemui.util.service.ObservableServiceConnection$Callback *; -} -# Note that these rules are temporary companions to the above rules, required -# for cases like Kotlin where fields with anonymous types use the anonymous type -# rather than the supertype. --if class * extends com.android.keyguard.KeyguardUpdateMonitorCallback --keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** { - <1> *; -} --if class * extends com.android.systemui.privacy.PrivacyConfig$Callback --keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** { - <1> *; -} --if class * extends com.android.systemui.privacy.PrivacyItemController$Callback --keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** { - <1> *; -} --if class * extends com.android.systemui.settings.UserTracker$Callback --keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** { - <1> *; -} --if class * extends com.android.systemui.statusbar.phone.StatusBarWindowCallback --keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** { - <1> *; -} --if class * extends com.android.systemui.util.service.Observer$Callback --keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** { +# Note that we restrict this to SysUISingleton classes, as other registering +# classes should either *always* unregister or *never* register from their +# constructor. We also keep callback class names for easier debugging. +-keepnames @com.android.systemui.util.annotations.WeaklyReferencedCallback class * +-keepnames class * extends @com.android.systemui.util.annotations.WeaklyReferencedCallback ** +-if @com.android.systemui.util.annotations.WeaklyReferencedCallback class * +-keepclassmembers,allowaccessmodification @com.android.systemui.dagger.SysUISingleton class * { <1> *; } --if class * extends com.android.systemui.util.service.ObservableServiceConnection$Callback --keepclassmembers,allowaccessmodification class com.android.systemui.**, com.android.keyguard.** { +-if class * extends @com.android.systemui.util.annotations.WeaklyReferencedCallback ** +-keepclassmembers,allowaccessmodification @com.android.systemui.dagger.SysUISingleton class * { <1> *; } diff --git a/packages/SystemUI/res/color/qs_tile_ripple_color.xml b/packages/SystemUI/res/color/qs_tile_ripple_color.xml new file mode 100644 index 000000000000..c1062548aa89 --- /dev/null +++ b/packages/SystemUI/res/color/qs_tile_ripple_color.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="?android:attr/colorControlHighlight" android:state_hovered="true" + android:state_pressed="true" /> + <!-- RippleDrawable has default way of handling hover state with highlighting but it's not + consistent with our approach so we make highlighting invisible and instead do custom handling + of hover state on a different level --> + <item android:color="@color/transparent" android:state_hovered="true" /> + <item android:color="?android:attr/colorControlHighlight" /> +</selector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/qs_tile_background.xml b/packages/SystemUI/res/drawable/qs_tile_background.xml index 265f575fc99c..ef3c61bda15a 100644 --- a/packages/SystemUI/res/drawable/qs_tile_background.xml +++ b/packages/SystemUI/res/drawable/qs_tile_background.xml @@ -15,9 +15,25 @@ ~ limitations under the License. --> <ripple xmlns:android="http://schemas.android.com/apk/res/android" - android:color="?android:attr/colorControlHighlight"> + android:color="@color/qs_tile_ripple_color"> <item android:id="@android:id/mask" - android:drawable="@drawable/qs_tile_background_shape" /> - <item android:id="@id/background" - android:drawable="@drawable/qs_tile_background_shape"/> + android:drawable="@drawable/qs_tile_background_shape" + /> + <item android:id="@id/background"> + <layer-list> + <item + android:id="@+id/qs_tile_background_base" + android:drawable="@drawable/qs_tile_background_shape" /> + <item android:id="@+id/qs_tile_background_overlay"> + <selector> + <item + android:state_hovered="true" + android:drawable="@drawable/qs_tile_background_shape" /> + <item + android:state_focused="true" + android:drawable="@drawable/qs_tile_background_shape" /> + </selector> + </item> + </layer-list> + </item> </ripple>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/bluetooth_device_item.xml b/packages/SystemUI/res/layout/bluetooth_device_item.xml index 1c7e9977afe5..6d779438ad6c 100644 --- a/packages/SystemUI/res/layout/bluetooth_device_item.xml +++ b/packages/SystemUI/res/layout/bluetooth_device_item.xml @@ -27,7 +27,6 @@ <ImageView android:id="@+id/bluetooth_device_icon" - android:contentDescription="@string/accessibility_bluetooth_device_icon" android:layout_width="24dp" android:layout_height="24dp" app:layout_constraintStart_toStartOf="parent" @@ -39,8 +38,12 @@ android:layout_width="0dp" android:id="@+id/bluetooth_device_name" style="@style/BluetoothTileDialog.DeviceName" + android:textDirection="locale" + android:textAlignment="gravity" android:paddingStart="20dp" android:paddingTop="10dp" + android:maxLines="1" + android:ellipsize="end" app:layout_constraintWidth_percent="0.7" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toEndOf="@+id/bluetooth_device_icon" @@ -55,6 +58,8 @@ style="@style/BluetoothTileDialog.DeviceSummary" android:paddingStart="20dp" android:paddingBottom="10dp" + android:maxLines="1" + android:ellipsize="end" app:layout_constraintWidth_percent="0.7" app:layout_constraintTop_toBottomOf="@+id/bluetooth_device_name" app:layout_constraintStart_toEndOf="@+id/bluetooth_device_icon" @@ -66,6 +71,7 @@ android:id="@+id/gear_icon" android:layout_width="0dp" android:layout_height="0dp" + android:contentDescription="@string/accessibility_bluetooth_device_settings_gear" app:layout_constraintStart_toEndOf="@+id/bluetooth_device_name" app:layout_constraintEnd_toEndOf="@+id/gear_icon_image" app:layout_constraintTop_toTopOf="parent" diff --git a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml index 16aeb951822c..5d986e00deed 100644 --- a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml +++ b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml @@ -27,6 +27,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingTop="24dp" + android:maxLines="1" android:ellipsize="end" android:gravity="center_vertical|center_horizontal" android:text="@string/quick_settings_bluetooth_label" @@ -58,9 +59,12 @@ style="@style/BluetoothTileDialog.Device" android:layout_width="0dp" android:layout_height="64dp" + android:maxLines="1" + android:ellipsize="end" android:gravity="center_vertical" android:layout_marginTop="4dp" android:text="@string/turn_on_bluetooth" + android:clickable="false" android:textAppearance="@style/TextAppearance.Dialog.Body.Message" android:textSize="16sp" app:layout_constraintEnd_toStartOf="@+id/bluetooth_toggle" @@ -84,53 +88,17 @@ app:layout_constraintStart_toEndOf="@+id/bluetooth_toggle_title" app:layout_constraintTop_toBottomOf="@id/bluetooth_tile_dialog_subtitle" /> - <androidx.constraintlayout.widget.Group - android:id="@+id/pair_new_device_layout_group" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:visibility="gone" - app:constraint_referenced_ids="ic_add,pair_new_device_text" /> - - <ImageView - android:id="@+id/ic_add" - android:layout_width="24dp" - android:layout_height="24dp" - android:layout_marginStart="36dp" - android:gravity="center_vertical" - android:importantForAccessibility="no" - android:src="@drawable/ic_add" - app:layout_constraintBottom_toTopOf="@id/device_list" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toStartOf="@id/pair_new_device_text" - app:layout_constraintTop_toBottomOf="@id/bluetooth_toggle_title" - android:tint="?android:attr/textColorPrimary" /> - - <TextView - android:id="@+id/pair_new_device_text" - style="@style/BluetoothTileDialog.Device" - android:layout_width="0dp" - android:layout_height="@dimen/bluetooth_dialog_device_height" - android:gravity="center_vertical" - android:layout_marginStart="0dp" - android:paddingStart="20dp" - android:text="@string/pair_new_bluetooth_devices" - android:textSize="14sp" - android:textAppearance="@style/TextAppearance.Dialog.Title" - app:layout_constraintBottom_toTopOf="@id/device_list" - app:layout_constraintStart_toEndOf="@+id/ic_add" - app:layout_constraintTop_toBottomOf="@id/bluetooth_toggle_title" - app:layout_constraintEnd_toEndOf="parent" /> - <androidx.recyclerview.widget.RecyclerView android:id="@+id/device_list" android:layout_width="match_parent" android:layout_height="wrap_content" + android:layout_marginTop="20dp" android:nestedScrollingEnabled="false" android:overScrollMode="never" android:scrollbars="vertical" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toBottomOf="@id/pair_new_device_text" + app:layout_constraintTop_toBottomOf="@id/bluetooth_toggle" app:layout_constraintBottom_toTopOf="@+id/see_all_text" /> <androidx.constraintlayout.widget.Group @@ -148,7 +116,7 @@ android:importantForAccessibility="no" android:gravity="center_vertical" android:src="@drawable/ic_arrow_forward" - app:layout_constraintBottom_toTopOf="@+id/done_button" + app:layout_constraintBottom_toTopOf="@+id/pair_new_device_text" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toStartOf="@id/see_all_text" app:layout_constraintTop_toBottomOf="@id/device_list" /> @@ -157,18 +125,59 @@ android:id="@+id/see_all_text" style="@style/BluetoothTileDialog.Device" android:layout_width="0dp" - android:layout_height="@dimen/bluetooth_dialog_device_height" + android:layout_height="64dp" + android:maxLines="1" + android:ellipsize="end" android:gravity="center_vertical" android:layout_marginStart="0dp" android:paddingStart="20dp" android:text="@string/see_all_bluetooth_devices" android:textSize="14sp" android:textAppearance="@style/TextAppearance.Dialog.Title" - app:layout_constraintBottom_toTopOf="@+id/done_button" + app:layout_constraintBottom_toTopOf="@+id/pair_new_device_text" app:layout_constraintStart_toEndOf="@+id/ic_arrow" app:layout_constraintTop_toBottomOf="@id/device_list" app:layout_constraintEnd_toEndOf="parent" /> + <androidx.constraintlayout.widget.Group + android:id="@+id/pair_new_device_layout_group" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone" + app:constraint_referenced_ids="ic_add,pair_new_device_text" /> + + <ImageView + android:id="@+id/ic_add" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_marginStart="36dp" + android:gravity="center_vertical" + android:importantForAccessibility="no" + android:src="@drawable/ic_add" + app:layout_constraintBottom_toTopOf="@id/done_button" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toStartOf="@id/pair_new_device_text" + app:layout_constraintTop_toBottomOf="@id/see_all_text" + android:tint="?android:attr/textColorPrimary" /> + + <TextView + android:id="@+id/pair_new_device_text" + style="@style/BluetoothTileDialog.Device" + android:layout_width="0dp" + android:layout_height="64dp" + android:maxLines="1" + android:ellipsize="end" + android:gravity="center_vertical" + android:layout_marginStart="0dp" + android:paddingStart="20dp" + android:text="@string/pair_new_bluetooth_devices" + android:textSize="14sp" + android:textAppearance="@style/TextAppearance.Dialog.Title" + app:layout_constraintBottom_toTopOf="@id/done_button" + app:layout_constraintStart_toEndOf="@+id/ic_add" + app:layout_constraintTop_toBottomOf="@id/see_all_text" + app:layout_constraintEnd_toEndOf="parent" /> + <Button android:id="@+id/done_button" style="@style/Widget.Dialog.Button" @@ -184,5 +193,5 @@ android:text="@string/inline_done_button" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toBottomOf="@id/see_all_text" /> + app:layout_constraintTop_toBottomOf="@id/pair_new_device_text" /> </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file 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..6856717653bd 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -947,6 +947,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. diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 6d54058f8e0c..6377df3410ab 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -852,6 +852,11 @@ 'keyguard_affordance_horizontal_offset' --> <dimen name="keyguard_indication_area_padding">82dp</dimen> + <!-- The width/padding of the communal tutorial indicator on keyguard. --> + <dimen name="communal_tutorial_indicator_fixed_width">168dp</dimen> + <dimen name="communal_tutorial_indicator_padding">24dp</dimen> + <dimen name="communal_tutorial_indicator_horizontal_offset">32dp</dimen> + <!-- The width/height of the unlock icon view on keyguard. --> <dimen name="keyguard_lock_height">42dp</dimen> <dimen name="keyguard_lock_padding">20dp</dimen> @@ -1350,6 +1355,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> diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index 81101d828c86..05f4334bbe89 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -222,12 +222,14 @@ <item type="id" name="lock_icon" /> <item type="id" name="lock_icon_bg" /> <item type="id" name="burn_in_layer" /> + <item type="id" name="communal_tutorial_indicator" /> <!-- Privacy dialog --> <item type="id" name="privacy_dialog_close_app_button" /> <item type="id" name="privacy_dialog_manage_app_button" /> <!-- Communal mode --> + <item type="id" name="communal_hub" /> <item type="id" name="communal_widget_wrapper" /> <!-- Values assigned to the views in Biometrics Prompt --> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index a2637d5e55c3..321594f41479 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1043,6 +1043,9 @@ <!-- Indication on the keyguard that is shown when the device is dock charging. [CHAR LIMIT=80]--> <string name="keyguard_indication_charging_time_dock"><xliff:g id="percentage" example="20%">%2$s</xliff:g> • Charging • Full in <xliff:g id="charging_time_left" example="4 hr, 2 min">%1$s</xliff:g></string> + <!-- Indicator shown to start the communal tutorial. [CHAR LIMIT=100] --> + <string name="communal_tutorial_indicator_text">Click on the arrow button to start the communal tutorial</string> + <!-- Related to user switcher --><skip/> <!-- Accessibility label for the button that opens the user switcher. --> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java index 80040a384b9d..631423e4b7fc 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java @@ -155,6 +155,26 @@ public class ActivityManagerWrapper { } } + + /** + * Requests for a new snapshot to be taken for the given task, stores it in the cache, and + * returns a {@link ThumbnailData} with the result. + */ + @NonNull + public ThumbnailData takeTaskThumbnail(int taskId) { + TaskSnapshot snapshot = null; + try { + snapshot = getService().takeTaskSnapshot(taskId, /* updateCache= */ true); + } catch (RemoteException e) { + Log.w(TAG, "Failed to take task snapshot", e); + } + if (snapshot != null) { + return new ThumbnailData(snapshot); + } else { + return new ThumbnailData(); + } + } + /** * Removes the outdated snapshot of home task. * diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java index 1e7222de91d9..88b9c020e9a5 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java @@ -233,6 +233,11 @@ public abstract class RemoteAnimationRunnerCompat extends IRemoteAnimationRunner runner.onAnimationCancelled(); finishRunnable.run(); } + + @Override + public void onTransitionConsumed(IBinder iBinder, boolean aborted) + throws RemoteException { + } }; } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java index f6a0563ebf94..9bddcd79ca49 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java @@ -22,10 +22,10 @@ import androidx.core.content.res.ResourcesCompat; import com.android.app.animation.Interpolators; import com.android.keyguard.dagger.KeyguardStatusViewScope; -import com.android.systemui.res.R; import com.android.systemui.log.LogBuffer; import com.android.systemui.log.core.LogLevel; import com.android.systemui.plugins.ClockController; +import com.android.systemui.res.R; import com.android.systemui.shared.clocks.DefaultClockController; import java.io.PrintWriter; @@ -452,6 +452,10 @@ public class KeyguardClockSwitch extends RelativeLayout { @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); + // TODO: b/305022530 + if (mClock.getConfig().getId().equals("DIGITAL_CLOCK_METRO")) { + mClock.getEvents().onColorPaletteChanged(mContext.getResources()); + } if (changed) { post(() -> updateClockTargetRegions()); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java index 50be97ec1af9..3585feb3442d 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java @@ -44,8 +44,6 @@ import android.util.AttributeSet; import android.view.WindowInsets; import android.view.WindowInsetsAnimationControlListener; import android.view.WindowInsetsAnimationController; -import android.view.animation.AnimationUtils; -import android.view.animation.Interpolator; import android.widget.TextView; import androidx.annotation.NonNull; @@ -66,18 +64,8 @@ import com.android.systemui.statusbar.policy.DevicePostureController; */ public class KeyguardPasswordView extends KeyguardAbsKeyInputView { - private final int mDisappearYTranslation; - - private static final long IME_DISAPPEAR_DURATION_MS = 125; - - // A delay constant to be used in a workaround for the situation where InputMethodManagerService - // is not switched to the new user yet. - // TODO: Remove this by ensuring such a race condition never happens. - private TextView mPasswordEntry; private TextViewInputDisabler mPasswordEntryDisabler; - private Interpolator mLinearOutSlowInInterpolator; - private Interpolator mFastOutLinearInInterpolator; private DisappearAnimationListener mDisappearAnimationListener; @Nullable private MotionLayout mContainerMotionLayout; private boolean mAlreadyUsingSplitBouncer = false; @@ -93,12 +81,6 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView { public KeyguardPasswordView(Context context, AttributeSet attrs) { super(context, attrs); - mDisappearYTranslation = getResources().getDimensionPixelSize( - R.dimen.disappear_y_translation); - mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator( - context, android.R.interpolator.linear_out_slow_in); - mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator( - context, android.R.interpolator.fast_out_linear_in); } /** diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java index 8717a532b43d..d2d051735643 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java @@ -31,6 +31,7 @@ import android.telephony.PinResult; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; +import android.text.TextUtils; import android.util.Log; import android.view.View; import android.view.WindowManager; @@ -39,9 +40,9 @@ import android.widget.ImageView; import com.android.internal.util.LatencyTracker; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; -import com.android.systemui.res.R; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.res.R; public class KeyguardSimPinViewController extends KeyguardPinBasedInputViewController<KeyguardSimPinView> { @@ -324,7 +325,11 @@ public class KeyguardSimPinViewController } else { SubscriptionInfo info = mKeyguardUpdateMonitor.getSubscriptionInfoForSubId(mSubId); CharSequence displayName = info != null ? info.getDisplayName() : ""; // don't crash - msg = rez.getString(R.string.kg_sim_pin_instructions_multi, displayName); + if (!TextUtils.isEmpty(displayName)) { + msg = rez.getString(R.string.kg_sim_pin_instructions_multi, displayName); + } else { + msg = rez.getString(R.string.kg_sim_pin_instructions); + } if (info != null) { color = info.getIconTint(); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java index 248b7afd7535..b52a36b8199e 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java @@ -29,6 +29,7 @@ import android.telephony.PinResult; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; +import android.text.TextUtils; import android.util.Log; import android.view.WindowManager; import android.widget.ImageView; @@ -36,9 +37,9 @@ import android.widget.ImageView; import com.android.internal.util.LatencyTracker; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; -import com.android.systemui.res.R; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.res.R; public class KeyguardSimPukViewController extends KeyguardPinBasedInputViewController<KeyguardSimPukView> { @@ -206,7 +207,11 @@ public class KeyguardSimPukViewController } else { SubscriptionInfo info = mKeyguardUpdateMonitor.getSubscriptionInfoForSubId(mSubId); CharSequence displayName = info != null ? info.getDisplayName() : ""; - msg = rez.getString(R.string.kg_puk_enter_puk_hint_multi, displayName); + if (!TextUtils.isEmpty(displayName)) { + msg = rez.getString(R.string.kg_puk_enter_puk_hint_multi, displayName); + } else { + msg = rez.getString(R.string.kg_puk_enter_puk_hint); + } if (info != null) { color = info.getIconTint(); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 3bf148276eab..b7bb35eb6783 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -1250,6 +1250,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab * @deprecated This is being migrated to use modern architecture, this method is visible purely * for bridging the gap while the migration is active. */ + @Deprecated private void handleFaceAuthFailed() { Assert.isMainThread(); String reason = @@ -1278,6 +1279,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab * @deprecated This is being migrated to use modern architecture, this method is visible purely * for bridging the gap while the migration is active. */ + @Deprecated private void handleFaceAcquired(int acquireInfo) { Assert.isMainThread(); for (int i = 0; i < mCallbacks.size(); i++) { @@ -1299,6 +1301,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab * @deprecated This is being migrated to use modern architecture, this method is visible purely * for bridging the gap while the migration is active. */ + @Deprecated private void handleFaceAuthenticated(int authUserId, boolean isStrongBiometric) { Trace.beginSection("KeyGuardUpdateMonitor#handlerFaceAuthenticated"); try { @@ -1327,6 +1330,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab * @deprecated This is being migrated to use modern architecture, this method is visible purely * for bridging the gap while the migration is active. */ + @Deprecated private void handleFaceHelp(int msgId, String helpString) { if (mFaceAcquiredInfoIgnoreList.contains(msgId)) { return; @@ -1344,6 +1348,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab * @deprecated This is being migrated to use modern architecture, this method is visible purely * for bridging the gap while the migration is active. */ + @Deprecated private void handleFaceError(int msgId, final String originalErrMsg) { Assert.isMainThread(); String errString = originalErrMsg; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java index 7b596328ca13..247606771155 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java @@ -25,12 +25,14 @@ import androidx.annotation.Nullable; import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.systemui.plugins.WeatherData; import com.android.systemui.statusbar.KeyguardIndicationController; +import com.android.systemui.util.annotations.WeaklyReferencedCallback; import java.util.TimeZone; /** * Callback for general information relevant to lock screen. */ +@WeaklyReferencedCallback public class KeyguardUpdateMonitorCallback { /** diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index 0d3f726b011b..83da80f4123a 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -692,10 +692,10 @@ public class LockIconViewController implements Dumpable { mVelocityTracker.addMovement(event); // Compute pointer velocity in pixels per second. mVelocityTracker.computeCurrentVelocity(1000); - float velocity = UdfpsController.computePointerSpeed(mVelocityTracker, + float velocity = computePointerSpeed(mVelocityTracker, mActivePointerId); if (event.getClassification() != MotionEvent.CLASSIFICATION_DEEP_PRESS - && UdfpsController.exceedsVelocityThreshold(velocity)) { + && exceedsVelocityThreshold(velocity)) { Log.v(TAG, "lock icon long-press rescheduled due to " + "high pointer velocity=" + velocity); mLongPressCancelRunnable.run(); @@ -713,6 +713,23 @@ public class LockIconViewController implements Dumpable { return true; } + /** + * Calculate the pointer speed given a velocity tracker and the pointer id. + * This assumes that the velocity tracker has already been passed all relevant motion events. + */ + private static float computePointerSpeed(@NonNull VelocityTracker tracker, int pointerId) { + final float vx = tracker.getXVelocity(pointerId); + final float vy = tracker.getYVelocity(pointerId); + return (float) Math.sqrt(Math.pow(vx, 2.0) + Math.pow(vy, 2.0)); + } + + /** + * Whether the velocity exceeds the acceptable UDFPS debouncing threshold. + */ + private static boolean exceedsVelocityThreshold(float velocity) { + return velocity > 750f; + } + private boolean actionableDownEventStartedOnView(MotionEvent event) { if (!isActionable()) { return false; diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt index 2dfb370bb382..fe19616cef1d 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt @@ -451,7 +451,7 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) { } fun logSubInfo(subInfo: SubscriptionInfo?) { - logBuffer.log(TAG, VERBOSE, { str1 = "$subInfo" }, { "SubInfo:$str1" }) + logBuffer.log(TAG, DEBUG, { str1 = "$subInfo" }, { "SubInfo:$str1" }) } fun logTimeFormatChanged(newTimeFormat: String?) { diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index 7739021bad33..1a34cc4fc3a9 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -15,123 +15,55 @@ package com.android.systemui; import android.annotation.Nullable; -import android.app.AlarmManager; -import android.app.INotificationManager; -import android.app.IWallpaperManager; -import android.hardware.SensorPrivacyManager; -import android.hardware.display.NightDisplayListener; import android.os.Handler; import android.os.Looper; import android.util.ArrayMap; -import android.util.DisplayMetrics; -import android.view.IWindowManager; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.Preconditions; -import com.android.keyguard.KeyguardSecurityModel; import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.systemui.accessibility.AccessibilityButtonModeObserver; import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver; -import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController; import com.android.systemui.animation.DialogLaunchAnimator; -import com.android.systemui.appops.AppOpsController; import com.android.systemui.assist.AssistManager; import com.android.systemui.broadcast.BroadcastDispatcher; -import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.dock.DockManager; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.fragments.FragmentService; -import com.android.systemui.keyguard.ScreenLifecycle; -import com.android.systemui.keyguard.WakefulnessLifecycle; -import com.android.systemui.media.dialog.MediaOutputDialogFactory; import com.android.systemui.model.SysUiState; import com.android.systemui.navigationbar.NavigationBarController; import com.android.systemui.navigationbar.NavigationModeController; -import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler; -import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.DarkIconDispatcher; -import com.android.systemui.plugins.PluginDependencyProvider; import com.android.systemui.plugins.PluginManager; import com.android.systemui.plugins.VolumeDialogController; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.power.EnhancedEstimates; -import com.android.systemui.power.PowerUI; -import com.android.systemui.privacy.PrivacyItemController; -import com.android.systemui.qs.ReduceBrightColorsController; -import com.android.systemui.qs.tiles.dialog.InternetDialogFactory; import com.android.systemui.recents.OverviewProxyService; -import com.android.systemui.screenrecord.RecordingController; import com.android.systemui.settings.UserTracker; -import com.android.systemui.shade.ShadeController; -import com.android.systemui.shared.system.ActivityManagerWrapper; -import com.android.systemui.shared.system.DevicePolicyManagerWrapper; -import com.android.systemui.shared.system.PackageManagerWrapper; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.NotificationListener; -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.VibratorHelper; -import com.android.systemui.statusbar.events.PrivacyDotViewController; -import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager; import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; -import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.stack.AmbientState; import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager; -import com.android.systemui.statusbar.phone.AutoHideController; -import com.android.systemui.statusbar.phone.DozeParameters; -import com.android.systemui.statusbar.phone.KeyguardDismissUtil; import com.android.systemui.statusbar.phone.LightBarController; -import com.android.systemui.statusbar.phone.LockscreenGestureLogger; -import com.android.systemui.statusbar.phone.ManagedProfileController; import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider; -import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.SystemUIDialogManager; -import com.android.systemui.statusbar.policy.AccessibilityController; -import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.BluetoothController; -import com.android.systemui.statusbar.policy.CastController; -import com.android.systemui.statusbar.policy.DataSaverController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; -import com.android.systemui.statusbar.policy.ExtensionController; import com.android.systemui.statusbar.policy.FlashlightController; -import com.android.systemui.statusbar.policy.HotspotController; -import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.statusbar.policy.LocationController; -import com.android.systemui.statusbar.policy.NextAlarmController; -import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler; -import com.android.systemui.statusbar.policy.RotationLockController; -import com.android.systemui.statusbar.policy.SecurityController; -import com.android.systemui.statusbar.policy.SensorPrivacyController; -import com.android.systemui.statusbar.policy.SmartReplyConstants; -import com.android.systemui.statusbar.policy.UserInfoController; -import com.android.systemui.statusbar.policy.UserSwitcherController; -import com.android.systemui.statusbar.policy.ZenModeController; -import com.android.systemui.statusbar.window.StatusBarWindowController; -import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.tuner.TunablePadding.TunablePaddingService; import com.android.systemui.tuner.TunerService; -import com.android.systemui.util.DeviceConfigProxy; -import com.android.systemui.util.leak.GarbageMonitor; -import com.android.systemui.util.leak.LeakDetector; -import com.android.systemui.util.leak.LeakReporter; -import com.android.systemui.util.sensors.AsyncSensorManager; import dagger.Lazy; -import java.util.concurrent.Executor; import java.util.function.Consumer; import javax.inject.Inject; @@ -154,10 +86,6 @@ import javax.inject.Named; */ @SysUISingleton public class Dependency { - /** - * Key for getting a the main looper. - */ - private static final String MAIN_LOOPER_NAME = "main_looper"; /** * Key for getting a background Looper for background work. @@ -171,15 +99,6 @@ public class Dependency { * Generic handler on the main thread. */ private static final String MAIN_HANDLER_NAME = "main_handler"; - /** - * Generic executor on the main thread. - */ - private static final String MAIN_EXECUTOR_NAME = "main_executor"; - - /** - * Generic executor on a background thread. - */ - private static final String BACKGROUND_EXECUTOR_NAME = "background_executor"; /** * An email address to send memory leak reports to by default. @@ -197,10 +116,6 @@ public class Dependency { */ public static final DependencyKey<Looper> BG_LOOPER = new DependencyKey<>(BG_LOOPER_NAME); /** - * Key for getting a mainer Looper. - */ - public static final DependencyKey<Looper> MAIN_LOOPER = new DependencyKey<>(MAIN_LOOPER_NAME); - /** * Key for getting a Handler for receiving time tick broadcasts on. */ public static final DependencyKey<Handler> TIME_TICK_HANDLER = @@ -211,133 +126,43 @@ public class Dependency { public static final DependencyKey<Handler> MAIN_HANDLER = new DependencyKey<>(MAIN_HANDLER_NAME); - /** - * Generic executor on the main thread. - */ - public static final DependencyKey<Executor> MAIN_EXECUTOR = - new DependencyKey<>(MAIN_EXECUTOR_NAME); - /** - * Generic executor on a background thread. - */ - public static final DependencyKey<Executor> BACKGROUND_EXECUTOR = - new DependencyKey<>(BACKGROUND_EXECUTOR_NAME); - - /** - * An email address to send memory leak reports to by default. - */ - public static final DependencyKey<String> LEAK_REPORT_EMAIL = - new DependencyKey<>(LEAK_REPORT_EMAIL_NAME); - private final ArrayMap<Object, Object> mDependencies = new ArrayMap<>(); private final ArrayMap<Object, LazyDependencyCreator> mProviders = new ArrayMap<>(); @Inject DumpManager mDumpManager; - @Inject Lazy<ActivityStarter> mActivityStarter; @Inject Lazy<BroadcastDispatcher> mBroadcastDispatcher; - @Inject Lazy<AsyncSensorManager> mAsyncSensorManager; @Inject Lazy<BluetoothController> mBluetoothController; - @Inject Lazy<LocationController> mLocationController; - @Inject Lazy<RotationLockController> mRotationLockController; - @Inject Lazy<ZenModeController> mZenModeController; - @Inject Lazy<HotspotController> mHotspotController; - @Inject Lazy<CastController> mCastController; @Inject Lazy<FlashlightController> mFlashlightController; - @Inject Lazy<UserSwitcherController> mUserSwitcherController; - @Inject Lazy<UserInfoController> mUserInfoController; - @Inject Lazy<KeyguardStateController> mKeyguardMonitor; @Inject Lazy<KeyguardUpdateMonitor> mKeyguardUpdateMonitor; - @Inject Lazy<NightDisplayListener> mNightDisplayListener; - @Inject Lazy<ReduceBrightColorsController> mReduceBrightColorsController; - @Inject Lazy<ManagedProfileController> mManagedProfileController; - @Inject Lazy<NextAlarmController> mNextAlarmController; - @Inject Lazy<DataSaverController> mDataSaverController; - @Inject Lazy<AccessibilityController> mAccessibilityController; @Inject Lazy<DeviceProvisionedController> mDeviceProvisionedController; @Inject Lazy<PluginManager> mPluginManager; @Inject Lazy<AssistManager> mAssistManager; - @Inject Lazy<SecurityController> mSecurityController; - @Inject Lazy<LeakDetector> mLeakDetector; - @Inject Lazy<LeakReporter> mLeakReporter; - @Inject Lazy<GarbageMonitor> mGarbageMonitor; @Inject Lazy<TunerService> mTunerService; - @Inject Lazy<NotificationShadeWindowController> mNotificationShadeWindowController; - @Inject Lazy<StatusBarWindowController> mTempStatusBarWindowController; @Inject Lazy<DarkIconDispatcher> mDarkIconDispatcher; - @Inject Lazy<StatusBarIconController> mStatusBarIconController; - @Inject Lazy<ScreenLifecycle> mScreenLifecycle; - @Inject Lazy<WakefulnessLifecycle> mWakefulnessLifecycle; @Inject Lazy<FragmentService> mFragmentService; - @Inject Lazy<ExtensionController> mExtensionController; - @Inject Lazy<PluginDependencyProvider> mPluginDependencyProvider; @Nullable - @Inject Lazy<LocalBluetoothManager> mLocalBluetoothManager; @Inject Lazy<VolumeDialogController> mVolumeDialogController; @Inject Lazy<MetricsLogger> mMetricsLogger; - @Inject Lazy<AccessibilityManagerWrapper> mAccessibilityManagerWrapper; - @Inject Lazy<SysuiColorExtractor> mSysuiColorExtractor; @Inject Lazy<TunablePaddingService> mTunablePaddingService; @Inject Lazy<UiOffloadThread> mUiOffloadThread; - @Inject Lazy<PowerUI.WarningsUI> mWarningsUI; @Inject Lazy<LightBarController> mLightBarController; - @Inject Lazy<IWindowManager> mIWindowManager; @Inject Lazy<OverviewProxyService> mOverviewProxyService; @Inject Lazy<NavigationModeController> mNavBarModeController; @Inject Lazy<AccessibilityButtonModeObserver> mAccessibilityButtonModeObserver; @Inject Lazy<AccessibilityButtonTargetsObserver> mAccessibilityButtonListController; - @Inject Lazy<EnhancedEstimates> mEnhancedEstimates; - @Inject Lazy<VibratorHelper> mVibratorHelper; @Inject Lazy<IStatusBarService> mIStatusBarService; - @Inject Lazy<DisplayMetrics> mDisplayMetrics; - @Inject Lazy<LockscreenGestureLogger> mLockscreenGestureLogger; - @Inject Lazy<ShadeController> mShadeController; @Inject Lazy<NotificationRemoteInputManager.Callback> mNotificationRemoteInputManagerCallback; - @Inject Lazy<AppOpsController> mAppOpsController; @Inject Lazy<NavigationBarController> mNavigationBarController; - @Inject Lazy<AccessibilityFloatingMenuController> mAccessibilityFloatingMenuController; @Inject Lazy<StatusBarStateController> mStatusBarStateController; - @Inject Lazy<NotificationLockscreenUserManager> mNotificationLockscreenUserManager; @Inject Lazy<NotificationMediaManager> mNotificationMediaManager; - @Inject Lazy<NotificationRemoteInputManager> mNotificationRemoteInputManager; - @Inject Lazy<SmartReplyConstants> mSmartReplyConstants; - @Inject Lazy<NotificationListener> mNotificationListener; - @Inject Lazy<NotificationLogger> mNotificationLogger; - @Inject Lazy<KeyguardDismissUtil> mKeyguardDismissUtil; - @Inject Lazy<SmartReplyController> mSmartReplyController; - @Inject Lazy<RemoteInputQuickSettingsDisabler> mRemoteInputQuickSettingsDisabler; - @Inject Lazy<SensorPrivacyManager> mSensorPrivacyManager; - @Inject Lazy<AutoHideController> mAutoHideController; - @Inject Lazy<PrivacyItemController> mPrivacyItemController; @Inject @Background Lazy<Looper> mBgLooper; - @Inject @Background Lazy<Handler> mBgHandler; - @Inject @Main Lazy<Looper> mMainLooper; @Inject @Main Lazy<Handler> mMainHandler; @Inject @Named(TIME_TICK_HANDLER_NAME) Lazy<Handler> mTimeTickHandler; - @Inject @Named(LEAK_REPORT_EMAIL_NAME) Lazy<String> mLeakReportEmail; - @Inject @Main Lazy<Executor> mMainExecutor; - @Inject @Background Lazy<Executor> mBackgroundExecutor; - @Inject Lazy<ActivityManagerWrapper> mActivityManagerWrapper; - @Inject Lazy<DevicePolicyManagerWrapper> mDevicePolicyManagerWrapper; - @Inject Lazy<PackageManagerWrapper> mPackageManagerWrapper; - @Inject Lazy<SensorPrivacyController> mSensorPrivacyController; - @Inject Lazy<DockManager> mDockManager; - @Inject Lazy<INotificationManager> mINotificationManager; @Inject Lazy<SysUiState> mSysUiStateFlagsContainer; - @Inject Lazy<AlarmManager> mAlarmManager; - @Inject Lazy<KeyguardSecurityModel> mKeyguardSecurityModel; - @Inject Lazy<DozeParameters> mDozeParameters; - @Inject Lazy<IWallpaperManager> mWallpaperManager; @Inject Lazy<CommandQueue> mCommandQueue; - @Inject Lazy<RecordingController> mRecordingController; - @Inject Lazy<MediaOutputDialogFactory> mMediaOutputDialogFactory; - @Inject Lazy<DeviceConfigProxy> mDeviceConfigProxy; - @Inject Lazy<TelephonyListenerManager> mTelephonyListenerManager; - @Inject Lazy<SystemStatusAnimationScheduler> mSystemStatusAnimationSchedulerLazy; - @Inject Lazy<PrivacyDotViewController> mPrivacyDotViewControllerLazy; - @Inject Lazy<EdgeBackGestureHandler.Factory> mEdgeBackGestureHandlerFactoryLazy; @Inject Lazy<UiEventLogger> mUiEventLogger; @Inject Lazy<StatusBarContentInsetsProvider> mContentInsetsProviderLazy; - @Inject Lazy<InternetDialogFactory> mInternetDialogFactory; @Inject Lazy<FeatureFlags> mFeatureFlagsLazy; @Inject Lazy<NotificationSectionsManager> mNotificationSectionsManagerLazy; @Inject Lazy<ScreenOffAnimationController> mScreenOffAnimationController; @@ -360,183 +185,36 @@ public class Dependency { // on imports. mProviders.put(TIME_TICK_HANDLER, mTimeTickHandler::get); mProviders.put(BG_LOOPER, mBgLooper::get); - mProviders.put(MAIN_LOOPER, mMainLooper::get); mProviders.put(MAIN_HANDLER, mMainHandler::get); - mProviders.put(MAIN_EXECUTOR, mMainExecutor::get); - mProviders.put(BACKGROUND_EXECUTOR, mBackgroundExecutor::get); - mProviders.put(ActivityStarter.class, mActivityStarter::get); mProviders.put(BroadcastDispatcher.class, mBroadcastDispatcher::get); - - mProviders.put(AsyncSensorManager.class, mAsyncSensorManager::get); - mProviders.put(BluetoothController.class, mBluetoothController::get); - mProviders.put(SensorPrivacyManager.class, mSensorPrivacyManager::get); - - mProviders.put(LocationController.class, mLocationController::get); - - mProviders.put(RotationLockController.class, mRotationLockController::get); - - mProviders.put(ZenModeController.class, mZenModeController::get); - - mProviders.put(HotspotController.class, mHotspotController::get); - - mProviders.put(CastController.class, mCastController::get); - mProviders.put(FlashlightController.class, mFlashlightController::get); - - mProviders.put(KeyguardStateController.class, mKeyguardMonitor::get); - mProviders.put(KeyguardUpdateMonitor.class, mKeyguardUpdateMonitor::get); - - mProviders.put(UserSwitcherController.class, mUserSwitcherController::get); - - mProviders.put(UserInfoController.class, mUserInfoController::get); - - mProviders.put(NightDisplayListener.class, mNightDisplayListener::get); - - mProviders.put(ReduceBrightColorsController.class, mReduceBrightColorsController::get); - - mProviders.put(ManagedProfileController.class, mManagedProfileController::get); - - mProviders.put(NextAlarmController.class, mNextAlarmController::get); - - mProviders.put(DataSaverController.class, mDataSaverController::get); - - mProviders.put(AccessibilityController.class, mAccessibilityController::get); - mProviders.put(DeviceProvisionedController.class, mDeviceProvisionedController::get); - mProviders.put(PluginManager.class, mPluginManager::get); - mProviders.put(AssistManager.class, mAssistManager::get); - - mProviders.put(SecurityController.class, mSecurityController::get); - - mProviders.put(LeakDetector.class, mLeakDetector::get); - - mProviders.put(LEAK_REPORT_EMAIL, mLeakReportEmail::get); - - mProviders.put(LeakReporter.class, mLeakReporter::get); - - mProviders.put(GarbageMonitor.class, mGarbageMonitor::get); - mProviders.put(TunerService.class, mTunerService::get); - - mProviders.put(NotificationShadeWindowController.class, - mNotificationShadeWindowController::get); - - mProviders.put(StatusBarWindowController.class, mTempStatusBarWindowController::get); - mProviders.put(DarkIconDispatcher.class, mDarkIconDispatcher::get); - - mProviders.put(StatusBarIconController.class, mStatusBarIconController::get); - - mProviders.put(ScreenLifecycle.class, mScreenLifecycle::get); - - mProviders.put(WakefulnessLifecycle.class, mWakefulnessLifecycle::get); - mProviders.put(FragmentService.class, mFragmentService::get); - - mProviders.put(ExtensionController.class, mExtensionController::get); - - mProviders.put(PluginDependencyProvider.class, mPluginDependencyProvider::get); - - mProviders.put(LocalBluetoothManager.class, mLocalBluetoothManager::get); - mProviders.put(VolumeDialogController.class, mVolumeDialogController::get); - mProviders.put(MetricsLogger.class, mMetricsLogger::get); - - mProviders.put(AccessibilityManagerWrapper.class, mAccessibilityManagerWrapper::get); - - mProviders.put(SysuiColorExtractor.class, mSysuiColorExtractor::get); - mProviders.put(TunablePaddingService.class, mTunablePaddingService::get); - mProviders.put(UiOffloadThread.class, mUiOffloadThread::get); - - mProviders.put(PowerUI.WarningsUI.class, mWarningsUI::get); - mProviders.put(LightBarController.class, mLightBarController::get); - - mProviders.put(IWindowManager.class, mIWindowManager::get); - mProviders.put(OverviewProxyService.class, mOverviewProxyService::get); - mProviders.put(NavigationModeController.class, mNavBarModeController::get); - mProviders.put(AccessibilityButtonModeObserver.class, mAccessibilityButtonModeObserver::get); mProviders.put(AccessibilityButtonTargetsObserver.class, mAccessibilityButtonListController::get); - - mProviders.put(EnhancedEstimates.class, mEnhancedEstimates::get); - - mProviders.put(VibratorHelper.class, mVibratorHelper::get); - mProviders.put(IStatusBarService.class, mIStatusBarService::get); - - mProviders.put(DisplayMetrics.class, mDisplayMetrics::get); - - mProviders.put(LockscreenGestureLogger.class, mLockscreenGestureLogger::get); - - mProviders.put(ShadeController.class, mShadeController::get); - mProviders.put(NotificationRemoteInputManager.Callback.class, mNotificationRemoteInputManagerCallback::get); - - mProviders.put(AppOpsController.class, mAppOpsController::get); - mProviders.put(NavigationBarController.class, mNavigationBarController::get); - - mProviders.put(AccessibilityFloatingMenuController.class, - mAccessibilityFloatingMenuController::get); - mProviders.put(StatusBarStateController.class, mStatusBarStateController::get); - mProviders.put(NotificationLockscreenUserManager.class, - mNotificationLockscreenUserManager::get); mProviders.put(NotificationMediaManager.class, mNotificationMediaManager::get); - mProviders.put(NotificationRemoteInputManager.class, - mNotificationRemoteInputManager::get); - mProviders.put(SmartReplyConstants.class, mSmartReplyConstants::get); - mProviders.put(NotificationListener.class, mNotificationListener::get); - mProviders.put(NotificationLogger.class, mNotificationLogger::get); - mProviders.put(KeyguardDismissUtil.class, mKeyguardDismissUtil::get); - mProviders.put(SmartReplyController.class, mSmartReplyController::get); - mProviders.put(RemoteInputQuickSettingsDisabler.class, - mRemoteInputQuickSettingsDisabler::get); - mProviders.put(PrivacyItemController.class, mPrivacyItemController::get); - mProviders.put(ActivityManagerWrapper.class, mActivityManagerWrapper::get); - mProviders.put(DevicePolicyManagerWrapper.class, mDevicePolicyManagerWrapper::get); - mProviders.put(PackageManagerWrapper.class, mPackageManagerWrapper::get); - mProviders.put(SensorPrivacyController.class, mSensorPrivacyController::get); - mProviders.put(DockManager.class, mDockManager::get); - mProviders.put(INotificationManager.class, mINotificationManager::get); mProviders.put(SysUiState.class, mSysUiStateFlagsContainer::get); - mProviders.put(AlarmManager.class, mAlarmManager::get); - mProviders.put(KeyguardSecurityModel.class, mKeyguardSecurityModel::get); - mProviders.put(DozeParameters.class, mDozeParameters::get); - mProviders.put(IWallpaperManager.class, mWallpaperManager::get); mProviders.put(CommandQueue.class, mCommandQueue::get); - mProviders.put(DeviceConfigProxy.class, mDeviceConfigProxy::get); - mProviders.put(TelephonyListenerManager.class, mTelephonyListenerManager::get); - - // TODO(b/118592525): to support multi-display , we start to add something which is - // per-display, while others may be global. I think it's time to add - // a new class maybe named DisplayDependency to solve per-display - // Dependency problem. - mProviders.put(AutoHideController.class, mAutoHideController::get); - - mProviders.put(RecordingController.class, mRecordingController::get); - - mProviders.put(MediaOutputDialogFactory.class, mMediaOutputDialogFactory::get); - - mProviders.put(SystemStatusAnimationScheduler.class, - mSystemStatusAnimationSchedulerLazy::get); - mProviders.put(PrivacyDotViewController.class, mPrivacyDotViewControllerLazy::get); - mProviders.put(InternetDialogFactory.class, mInternetDialogFactory::get); - mProviders.put(EdgeBackGestureHandler.Factory.class, - mEdgeBackGestureHandlerFactoryLazy::get); mProviders.put(UiEventLogger.class, mUiEventLogger::get); mProviders.put(FeatureFlags.class, mFeatureFlagsLazy::get); mProviders.put(StatusBarContentInsetsProvider.class, mContentInsetsProviderLazy::get); diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index 54dbf7275dce..6faee8cd1c4f 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -83,7 +83,7 @@ import com.android.systemui.decor.RoundedCornerDecorProviderFactory; import com.android.systemui.decor.RoundedCornerResDelegateImpl; import com.android.systemui.decor.ScreenDecorCommand; import com.android.systemui.log.ScreenDecorationsLogger; -import com.android.systemui.qs.SettingObserver; +import com.android.systemui.qs.UserSettingObserver; import com.android.systemui.res.R; import com.android.systemui.settings.DisplayTracker; import com.android.systemui.settings.UserTracker; @@ -165,7 +165,7 @@ public class ScreenDecorations implements CoreStartable, Dumpable { ScreenDecorHwcLayer mScreenDecorHwcLayer; private WindowManager mWindowManager; private int mRotation; - private SettingObserver mColorInversionSetting; + private UserSettingObserver mColorInversionSetting; @Nullable private DelayableExecutor mExecutor; private Handler mHandler; @@ -686,7 +686,7 @@ public class ScreenDecorations implements CoreStartable, Dumpable { // Watch color inversion and invert the overlay as needed. if (mColorInversionSetting == null) { - mColorInversionSetting = new SettingObserver(mSecureSettings, mHandler, + mColorInversionSetting = new UserSettingObserver(mSecureSettings, mHandler, Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, mUserTracker.getUserId()) { @Override diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java index 1ee06cc8d757..56273eb9a2cf 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java +++ b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java @@ -35,18 +35,19 @@ import android.widget.FrameLayout; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.systemui.res.R; import com.android.systemui.assist.AssistLogger; import com.android.systemui.assist.AssistManager; import com.android.systemui.assist.AssistantSessionEvent; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.navigationbar.NavigationBarController; +import com.android.systemui.res.R; + +import dagger.Lazy; import java.util.Locale; import javax.inject.Inject; -import dagger.Lazy; - /** * Default UiController implementation. Shows white edge lights along the bottom of the phone, * expanding from the corners to meet in the center. @@ -80,7 +81,8 @@ public class DefaultUiController implements AssistManager.UiController { @Inject public DefaultUiController(Context context, AssistLogger assistLogger, WindowManager windowManager, MetricsLogger metricsLogger, - Lazy<AssistManager> assistManagerLazy) { + Lazy<AssistManager> assistManagerLazy, + NavigationBarController navigationBarController) { mAssistLogger = assistLogger; mRoot = new FrameLayout(context); mWindowManager = windowManager; @@ -103,6 +105,7 @@ public class DefaultUiController implements AssistManager.UiController { mInvocationLightsView = (InvocationLightsView) LayoutInflater.from(context).inflate(R.layout.invocation_lights, mRoot, false); + mInvocationLightsView.setNavigationBarController(navigationBarController); mRoot.addView(mInvocationLightsView); } diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/InvocationLightsView.java b/packages/SystemUI/src/com/android/systemui/assist/ui/InvocationLightsView.java index 4d89231f0b15..0cdb376e7a27 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/ui/InvocationLightsView.java +++ b/packages/SystemUI/src/com/android/systemui/assist/ui/InvocationLightsView.java @@ -31,11 +31,10 @@ import android.view.ContextThemeWrapper; import android.view.View; import com.android.settingslib.Utils; -import com.android.systemui.Dependency; -import com.android.systemui.res.R; -import com.android.systemui.navigationbar.NavigationBarController; import com.android.systemui.navigationbar.NavigationBar; +import com.android.systemui.navigationbar.NavigationBarController; import com.android.systemui.navigationbar.NavigationBarTransitions; +import com.android.systemui.res.R; import java.util.ArrayList; @@ -64,6 +63,8 @@ public class InvocationLightsView extends View private final int mLightColor; @ColorInt private final int mDarkColor; + @Nullable + private NavigationBarController mNavigationBarController; // Allocate variable for screen location lookup to avoid memory alloc onDraw() private int[] mScreenLocation = new int[2]; @@ -279,12 +280,11 @@ public class InvocationLightsView extends View private void attemptRegisterNavBarListener() { if (!mRegistered) { - NavigationBarController controller = Dependency.get(NavigationBarController.class); - if (controller == null) { + if (mNavigationBarController == null) { return; } - NavigationBar navBar = controller.getDefaultNavigationBar(); + NavigationBar navBar = mNavigationBarController.getDefaultNavigationBar(); if (navBar == null) { return; } @@ -296,12 +296,11 @@ public class InvocationLightsView extends View private void attemptUnregisterNavBarListener() { if (mRegistered) { - NavigationBarController controller = Dependency.get(NavigationBarController.class); - if (controller == null) { + if (mNavigationBarController == null) { return; } - NavigationBar navBar = controller.getDefaultNavigationBar(); + NavigationBar navBar = mNavigationBarController.getDefaultNavigationBar(); if (navBar == null) { return; } @@ -310,4 +309,8 @@ public class InvocationLightsView extends View mRegistered = false; } } + + public void setNavigationBarController(NavigationBarController navigationBarController) { + mNavigationBarController = navigationBarController; + } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AlternateUdfpsTouchProvider.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AlternateUdfpsTouchProvider.kt deleted file mode 100644 index ca4b8efa98a5..000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AlternateUdfpsTouchProvider.kt +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.biometrics - -/** - * Interface for controlling the on finger down & on finger up events. - */ -interface AlternateUdfpsTouchProvider { - - /** - * onPointerDown: - * - * This operation is used to notify the Fingerprint HAL that - * a fingerprint has been detected on the device's screen. - * - * See fingerprint/ISession#onPointerDown for more details. - */ - fun onPointerDown(pointerId: Long, x: Int, y: Int, minor: Float, major: Float) - - /** - * onPointerUp: - * - * This operation can be invoked when the HAL is performing any one of: ISession#authenticate, - * ISession#enroll, ISession#detectInteraction. This operation is used to indicate - * that a fingerprint that was previously down, is now up. - * - * See fingerprint/ISession#onPointerUp for more details. - */ - fun onPointerUp(pointerId: Long) - - /** - * onUiReady: - * - * This operation is used by the callee to notify the Fingerprint HAL that SystemUI is - * correctly configured for the fingerprint capture. - * - * See fingerprint/ISession#onUiReady for more details. - */ - fun onUiReady() -} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java index bdad41348c95..395f68c7c0e6 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java @@ -37,9 +37,6 @@ public abstract class UdfpsAnimationView extends FrameLayout { private float mDialogSuggestedAlpha = 1f; private float mNotificationShadeExpansion = 0f; - // Used for Udfps ellipse detection when flag is true, set by AnimationViewController - boolean mUseExpandedOverlay = false; - // mAlpha takes into consideration the status bar expansion amount and dialog suggested alpha private int mAlpha; boolean mPauseAuth; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 79d9c1ba70bc..c9e4cbe30a2c 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -32,7 +32,6 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.graphics.Point; import android.graphics.Rect; import android.hardware.biometrics.BiometricFingerprintConstants; import android.hardware.biometrics.SensorProperties; @@ -54,7 +53,6 @@ import android.util.Log; import android.view.HapticFeedbackConstants; import android.view.LayoutInflater; import android.view.MotionEvent; -import android.view.VelocityTracker; import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; @@ -84,7 +82,6 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.doze.DozeReceiver; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor; import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter; @@ -102,7 +99,6 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.concurrency.Execution; -import com.android.systemui.util.settings.SecureSettings; import com.android.systemui.util.time.SystemClock; import kotlin.Unit; @@ -110,7 +106,6 @@ import kotlin.Unit; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashSet; -import java.util.Optional; import java.util.Set; import java.util.concurrent.Executor; @@ -136,13 +131,8 @@ public class UdfpsController implements DozeReceiver, Dumpable { private static final String TAG = "UdfpsController"; private static final long AOD_SEND_FINGER_UP_DELAY_MILLIS = 1000; - // Minimum required delay between consecutive touch logs in milliseconds. - private static final long MIN_TOUCH_LOG_INTERVAL = 50; private static final long MIN_UNCHANGED_INTERACTION_LOG_INTERVAL = 50; - // This algorithm checks whether the touch is within the sensor's bounding box. - private static final int BOUNDING_BOX_TOUCH_CONFIG_ID = 0; - private final Context mContext; private final Execution mExecution; private final FingerprintManager mFingerprintManager; @@ -175,8 +165,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { @Nullable private final TouchProcessor mTouchProcessor; @NonNull private final SessionTracker mSessionTracker; @NonNull private final AlternateBouncerInteractor mAlternateBouncerInteractor; - @NonNull private final SecureSettings mSecureSettings; - @NonNull private final UdfpsUtils mUdfpsUtils; @NonNull private final InputManager mInputManager; @NonNull private final UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate; private final boolean mIgnoreRefreshRate; @@ -187,11 +175,8 @@ public class UdfpsController implements DozeReceiver, Dumpable { @VisibleForTesting @NonNull UdfpsOverlayParams mOverlayParams = new UdfpsOverlayParams(); // TODO(b/229290039): UDFPS controller should manage its dimensions on its own. Remove this. @Nullable private Runnable mAuthControllerUpdateUdfpsLocation; - @Nullable private final AlternateUdfpsTouchProvider mAlternateTouchProvider; @Nullable private UdfpsDisplayModeProvider mUdfpsDisplayMode; - // Tracks the velocity of a touch to help filter out the touches that move too fast. - @Nullable private VelocityTracker mVelocityTracker; // The ID of the pointer for which ACTION_DOWN has occurred. -1 means no pointer is active. private int mActivePointerId = -1; // Whether a pointer has been pilfered for current gesture @@ -259,8 +244,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { final int touchConfigId = mContext.getResources().getInteger( com.android.internal.R.integer.config_selected_udfps_touch_detection); pw.println("mSensorProps=(" + mSensorProps + ")"); - pw.println("Using new touch detection framework: " + mFeatureFlags.isEnabled( - Flags.UDFPS_NEW_TOUCH_DETECTION)); pw.println("touchConfigId: " + touchConfigId); } @@ -272,7 +255,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { mFgExecutor.execute(() -> UdfpsController.this.showUdfpsOverlay( new UdfpsControllerOverlay( mContext, - mFingerprintManager, mInflater, mWindowManager, mAccessibilityManager, @@ -286,7 +268,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { mKeyguardStateController, mUnlockedScreenOffAnimationController, mUdfpsDisplayMode, - mSecureSettings, requestId, reason, callback, @@ -299,7 +280,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { mFeatureFlags, mPrimaryBouncerInteractor, mAlternateBouncerInteractor, - mUdfpsUtils, mUdfpsKeyguardAccessibilityDelegate, mUdfpsKeyguardViewModels ))); @@ -376,13 +356,9 @@ public class UdfpsController implements DozeReceiver, Dumpable { * Debug to run onUiReady */ public void debugOnUiReady(int sensorId) { - if (UdfpsController.this.mAlternateTouchProvider != null) { - UdfpsController.this.mAlternateTouchProvider.onUiReady(); - } else { - final long requestId = (mOverlay != null) ? mOverlay.getRequestId() : 0L; - UdfpsController.this.mFingerprintManager.onUdfpsUiEvent( - FingerprintManager.UDFPS_UI_READY, requestId, sensorId); - } + final long requestId = (mOverlay != null) ? mOverlay.getRequestId() : 0L; + UdfpsController.this.mFingerprintManager.onUdfpsUiEvent( + FingerprintManager.UDFPS_UI_READY, requestId, sensorId); } } @@ -423,23 +399,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { mUdfpsDisplayMode = udfpsDisplayMode; } - /** - * Calculate the pointer speed given a velocity tracker and the pointer id. - * This assumes that the velocity tracker has already been passed all relevant motion events. - */ - public static float computePointerSpeed(@NonNull VelocityTracker tracker, int pointerId) { - final float vx = tracker.getXVelocity(pointerId); - final float vy = tracker.getYVelocity(pointerId); - return (float) Math.sqrt(Math.pow(vx, 2.0) + Math.pow(vy, 2.0)); - } - - /** - * Whether the velocity exceeds the acceptable UDFPS debouncing threshold. - */ - public static boolean exceedsVelocityThreshold(float velocity) { - return velocity > 750f; - } - private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -457,38 +416,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { } }; - /** - * Forwards touches to the udfps controller / view - */ - public boolean onTouch(MotionEvent event) { - if (mOverlay == null || mOverlay.isHiding()) { - return false; - } - // TODO(b/225068271): may not be correct but no way to get the id yet - return onTouch(mOverlay.getRequestId(), event, false); - } - - /** - * @param x coordinate - * @param y coordinate - * @param relativeToUdfpsView true if the coordinates are relative to the udfps view; else, - * calculate from the display dimensions in portrait orientation - */ - private boolean isWithinSensorArea(UdfpsView udfpsView, float x, float y, - boolean relativeToUdfpsView) { - if (relativeToUdfpsView) { - // TODO: move isWithinSensorArea to UdfpsController. - return udfpsView.isWithinSensorArea(x, y); - } - - if (mOverlay == null || mOverlay.getAnimationViewController() == null) { - return false; - } - - return !mOverlay.getAnimationViewController().shouldPauseAuth() - && mOverlayParams.getSensorBounds().contains((int) x, (int) y); - } - private void tryDismissingKeyguard() { if (!mOnFingerDown) { playStartHaptic(); @@ -497,15 +424,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { mAttemptedToDismissKeyguard = true; } - @VisibleForTesting - boolean onTouch(long requestId, @NonNull MotionEvent event, boolean fromUdfpsView) { - if (mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) { - return newOnTouch(requestId, event, fromUdfpsView); - } else { - return oldOnTouch(requestId, event, fromUdfpsView); - } - } - private int getBiometricSessionType() { if (mOverlay == null) { return -1; @@ -566,7 +484,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { } } - private boolean newOnTouch(long requestId, @NonNull MotionEvent event, boolean fromUdfpsView) { + private boolean onTouch(long requestId, @NonNull MotionEvent event, boolean fromUdfpsView) { if (!fromUdfpsView) { Log.e(TAG, "ignoring the touch injected from outside of UdfpsView"); return false; @@ -580,7 +498,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { + mOverlay.getRequestId()); return false; } - if ((mLockscreenShadeTransitionController.getQSDragProgress() != 0f && !mAlternateBouncerInteractor.isVisibleState()) || mPrimaryBouncerInteractor.isInTransit()) { @@ -654,8 +571,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { break; case UNCHANGED: - if (!isWithinSensorArea(mOverlay.getOverlayView(), event.getRawX(), event.getRawY(), - true) && mActivePointerId == MotionEvent.INVALID_POINTER_ID + if (mActivePointerId == MotionEvent.INVALID_POINTER_ID && mAlternateBouncerInteractor.isVisibleState()) { // No pointer on sensor, forward to keyguard if alternateBouncer is visible mKeyguardViewManager.onTouch(event); @@ -667,7 +583,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { logBiometricTouch(processedTouch.getEvent(), data); // Always pilfer pointers that are within sensor area or when alternate bouncer is showing - if (isWithinSensorArea(mOverlay.getOverlayView(), event.getRawX(), event.getRawY(), true) + if (mActivePointerId != MotionEvent.INVALID_POINTER_ID || mAlternateBouncerInteractor.isVisibleState()) { shouldPilfer = true; } @@ -680,146 +596,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { mPointerPilfered = true; } - return processedTouch.getTouchData().isWithinBounds(mOverlayParams.getNativeSensorBounds()); - } - - private boolean oldOnTouch(long requestId, @NonNull MotionEvent event, boolean fromUdfpsView) { - if (mOverlay == null) { - Log.w(TAG, "ignoring onTouch with null overlay"); - return false; - } - if (!mOverlay.matchesRequestId(requestId)) { - Log.w(TAG, "ignoring stale touch event: " + requestId + " current: " - + mOverlay.getRequestId()); - return false; - } - - final UdfpsView udfpsView = mOverlay.getOverlayView(); - boolean handled = false; - switch (event.getActionMasked()) { - case MotionEvent.ACTION_DOWN: - case MotionEvent.ACTION_HOVER_ENTER: - Trace.beginSection("UdfpsController.onTouch.ACTION_DOWN"); - // To simplify the lifecycle of the velocity tracker, make sure it's never null - // after ACTION_DOWN, and always null after ACTION_CANCEL or ACTION_UP. - if (mVelocityTracker == null) { - mVelocityTracker = VelocityTracker.obtain(); - } else { - // ACTION_UP or ACTION_CANCEL is not guaranteed to be called before a new - // ACTION_DOWN, in that case we should just reuse the old instance. - mVelocityTracker.clear(); - } - - final boolean withinSensorArea = - isWithinSensorArea(udfpsView, event.getX(), event.getY(), fromUdfpsView); - if (withinSensorArea) { - Trace.beginAsyncSection("UdfpsController.e2e.onPointerDown", 0); - Log.v(TAG, "onTouch | action down"); - // The pointer that causes ACTION_DOWN is always at index 0. - // We need to persist its ID to track it during ACTION_MOVE that could include - // data for many other pointers because of multi-touch support. - mActivePointerId = event.getPointerId(0); - mVelocityTracker.addMovement(event); - handled = true; - mAcquiredReceived = false; - } - if ((withinSensorArea || fromUdfpsView) && shouldTryToDismissKeyguard()) { - Log.v(TAG, "onTouch | dismiss keyguard ACTION_DOWN"); - tryDismissingKeyguard(); - } - - Trace.endSection(); - break; - - case MotionEvent.ACTION_MOVE: - case MotionEvent.ACTION_HOVER_MOVE: - Trace.beginSection("UdfpsController.onTouch.ACTION_MOVE"); - final int idx = mActivePointerId == -1 - ? event.getPointerId(0) - : event.findPointerIndex(mActivePointerId); - if (idx == event.getActionIndex()) { - final boolean actionMoveWithinSensorArea = - isWithinSensorArea(udfpsView, event.getX(idx), event.getY(idx), - fromUdfpsView); - if ((fromUdfpsView || actionMoveWithinSensorArea) - && shouldTryToDismissKeyguard()) { - Log.v(TAG, "onTouch | dismiss keyguard ACTION_MOVE"); - tryDismissingKeyguard(); - break; - } - // Map the touch to portrait mode if the device is in landscape mode. - final Point scaledTouch = mUdfpsUtils.getTouchInNativeCoordinates( - idx, event, mOverlayParams); - if (actionMoveWithinSensorArea) { - if (mVelocityTracker == null) { - // touches could be injected, so the velocity tracker may not have - // been initialized (via ACTION_DOWN). - mVelocityTracker = VelocityTracker.obtain(); - } - mVelocityTracker.addMovement(event); - // Compute pointer velocity in pixels per second. - mVelocityTracker.computeCurrentVelocity(1000); - // Compute pointer speed from X and Y velocities. - final float v = computePointerSpeed(mVelocityTracker, mActivePointerId); - final float minor = event.getTouchMinor(idx); - final float major = event.getTouchMajor(idx); - final boolean exceedsVelocityThreshold = exceedsVelocityThreshold(v); - final String touchInfo = String.format( - "minor: %.1f, major: %.1f, v: %.1f, exceedsVelocityThreshold: %b", - minor, major, v, exceedsVelocityThreshold); - final long sinceLastLog = mSystemClock.elapsedRealtime() - mTouchLogTime; - - if (!mOnFingerDown && !mAcquiredReceived && !exceedsVelocityThreshold) { - final float scale = mOverlayParams.getScaleFactor(); - float scaledMinor = minor / scale; - float scaledMajor = major / scale; - onFingerDown(requestId, scaledTouch.x, scaledTouch.y, scaledMinor, - scaledMajor); - - Log.v(TAG, "onTouch | finger down: " + touchInfo); - mTouchLogTime = mSystemClock.elapsedRealtime(); - handled = true; - } else if (sinceLastLog >= MIN_TOUCH_LOG_INTERVAL) { - Log.v(TAG, "onTouch | finger move: " + touchInfo); - mTouchLogTime = mSystemClock.elapsedRealtime(); - } - } else { - Log.v(TAG, "onTouch | finger outside"); - onFingerUp(requestId, udfpsView); - // Maybe announce for accessibility. - mFgExecutor.execute(() -> { - if (mOverlay == null) { - Log.e(TAG, "touch outside sensor area received" - + "but serverRequest is null"); - return; - } - mOverlay.onTouchOutsideOfSensorArea(scaledTouch); - }); - } - } - Trace.endSection(); - break; - - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_HOVER_EXIT: - Trace.beginSection("UdfpsController.onTouch.ACTION_UP"); - mActivePointerId = -1; - if (mVelocityTracker != null) { - mVelocityTracker.recycle(); - mVelocityTracker = null; - } - Log.v(TAG, "onTouch | finger up"); - mAttemptedToDismissKeyguard = false; - onFingerUp(requestId, udfpsView); - mFalsingManager.isFalseTouch(UDFPS_AUTHENTICATION); - Trace.endSection(); - break; - - default: - // Do nothing. - } - return handled; + return mActivePointerId != MotionEvent.INVALID_POINTER_ID; } private boolean shouldTryToDismissKeyguard() { @@ -859,15 +636,12 @@ public class UdfpsController implements DozeReceiver, Dumpable { @NonNull SystemUIDialogManager dialogManager, @NonNull LatencyTracker latencyTracker, @NonNull ActivityLaunchAnimator activityLaunchAnimator, - @NonNull Optional<Provider<AlternateUdfpsTouchProvider>> alternateTouchProvider, @NonNull @BiometricsBackground Executor biometricsExecutor, @NonNull PrimaryBouncerInteractor primaryBouncerInteractor, @NonNull SinglePointerTouchProcessor singlePointerTouchProcessor, @NonNull SessionTracker sessionTracker, @NonNull AlternateBouncerInteractor alternateBouncerInteractor, - @NonNull SecureSettings secureSettings, @NonNull InputManager inputManager, - @NonNull UdfpsUtils udfpsUtils, @NonNull KeyguardFaceAuthInteractor keyguardFaceAuthInteractor, @NonNull UdfpsKeyguardAccessibilityDelegate udfpsKeyguardAccessibilityDelegate, @NonNull Provider<UdfpsKeyguardViewModels> udfpsKeyguardViewModelsProvider) { @@ -900,7 +674,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; mLatencyTracker = latencyTracker; mActivityLaunchAnimator = activityLaunchAnimator; - mAlternateTouchProvider = alternateTouchProvider.map(Provider::get).orElse(null); mSensorProps = new FingerprintSensorPropertiesInternal( -1 /* sensorId */, SensorProperties.STRENGTH_CONVENIENCE, @@ -912,13 +685,10 @@ public class UdfpsController implements DozeReceiver, Dumpable { mBiometricExecutor = biometricsExecutor; mPrimaryBouncerInteractor = primaryBouncerInteractor; mAlternateBouncerInteractor = alternateBouncerInteractor; - mSecureSettings = secureSettings; - mUdfpsUtils = udfpsUtils; mInputManager = inputManager; mUdfpsKeyguardAccessibilityDelegate = udfpsKeyguardAccessibilityDelegate; - mTouchProcessor = mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION) - ? singlePointerTouchProcessor : null; + mTouchProcessor = singlePointerTouchProcessor; mSessionTracker = sessionTracker; mDumpManager.registerDumpable(TAG, this); @@ -1172,16 +942,9 @@ public class UdfpsController implements DozeReceiver, Dumpable { } private void dispatchOnUiReady(long requestId) { - if (mAlternateTouchProvider != null) { - mBiometricExecutor.execute(() -> { - mAlternateTouchProvider.onUiReady(); - mLatencyTracker.onActionEnd(LatencyTracker.ACTION_UDFPS_ILLUMINATE); - }); - } else { - mFingerprintManager.onUdfpsUiEvent(FingerprintManager.UDFPS_UI_READY, requestId, - mSensorProps.sensorId); - mLatencyTracker.onActionEnd(LatencyTracker.ACTION_UDFPS_ILLUMINATE); - } + mFingerprintManager.onUdfpsUiEvent(FingerprintManager.UDFPS_UI_READY, requestId, + mSensorProps.sensorId); + mLatencyTracker.onActionEnd(LatencyTracker.ACTION_UDFPS_ILLUMINATE); } private void onFingerDown( @@ -1241,24 +1004,8 @@ public class UdfpsController implements DozeReceiver, Dumpable { } } mOnFingerDown = true; - if (mAlternateTouchProvider != null) { - mBiometricExecutor.execute(() -> { - mAlternateTouchProvider.onPointerDown(requestId, (int) x, (int) y, minor, major); - }); - mFgExecutor.execute(() -> { - if (mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) { - mKeyguardUpdateMonitor.onUdfpsPointerDown((int) requestId); - } - }); - } else { - if (mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) { - mFingerprintManager.onPointerDown(requestId, mSensorProps.sensorId, pointerId, x, y, - minor, major, orientation, time, gestureStart, isAod); - } else { - mFingerprintManager.onPointerDown(requestId, mSensorProps.sensorId, (int) x, - (int) y, minor, major); - } - } + mFingerprintManager.onPointerDown(requestId, mSensorProps.sensorId, pointerId, x, y, + minor, major, orientation, time, gestureStart, isAod); Trace.endAsyncSection("UdfpsController.e2e.onPointerDown", 0); final UdfpsView view = mOverlay.getOverlayView(); if (view != null && isOptical()) { @@ -1305,23 +1052,8 @@ public class UdfpsController implements DozeReceiver, Dumpable { mActivePointerId = -1; mAcquiredReceived = false; if (mOnFingerDown) { - if (mAlternateTouchProvider != null) { - mBiometricExecutor.execute(() -> { - mAlternateTouchProvider.onPointerUp(requestId); - }); - mFgExecutor.execute(() -> { - if (mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) { - mKeyguardUpdateMonitor.onUdfpsPointerUp((int) requestId); - } - }); - } else { - if (mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) { - mFingerprintManager.onPointerUp(requestId, mSensorProps.sensorId, pointerId, x, - y, minor, major, orientation, time, gestureStart, isAod); - } else { - mFingerprintManager.onPointerUp(requestId, mSensorProps.sensorId); - } - } + mFingerprintManager.onPointerUp(requestId, mSensorProps.sensorId, pointerId, x, + y, minor, major, orientation, time, gestureStart, isAod); for (Callback cb : mCallbacks) { cb.onFingerUp(); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt index 34a0d8a46e52..7130bfb462e3 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt @@ -20,7 +20,6 @@ import android.annotation.SuppressLint import android.annotation.UiThread import android.content.Context import android.graphics.PixelFormat -import android.graphics.Point import android.graphics.Rect import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_BP import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD @@ -29,7 +28,6 @@ import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTING import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_ENROLLING import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR import android.hardware.biometrics.BiometricOverlayConstants.ShowReason -import android.hardware.fingerprint.FingerprintManager import android.hardware.fingerprint.IUdfpsOverlayControllerCallback import android.os.Build import android.os.RemoteException @@ -54,7 +52,6 @@ import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.flags.Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels @@ -65,7 +62,6 @@ import com.android.systemui.statusbar.phone.SystemUIDialogManager import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.KeyguardStateController -import com.android.systemui.util.settings.SecureSettings import kotlinx.coroutines.ExperimentalCoroutinesApi import javax.inject.Provider @@ -83,7 +79,6 @@ const val SETTING_REMOVE_ENROLLMENT_UI = "udfps_overlay_remove_enrollment_ui" @UiThread class UdfpsControllerOverlay @JvmOverloads constructor( private val context: Context, - fingerprintManager: FingerprintManager, private val inflater: LayoutInflater, private val windowManager: WindowManager, private val accessibilityManager: AccessibilityManager, @@ -97,7 +92,6 @@ class UdfpsControllerOverlay @JvmOverloads constructor( private val keyguardStateController: KeyguardStateController, private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController, private var udfpsDisplayModeProvider: UdfpsDisplayModeProvider, - private val secureSettings: SecureSettings, val requestId: Long, @ShowReason val requestReason: Int, private val controllerCallback: IUdfpsOverlayControllerCallback, @@ -107,7 +101,6 @@ class UdfpsControllerOverlay @JvmOverloads constructor( private val primaryBouncerInteractor: PrimaryBouncerInteractor, private val alternateBouncerInteractor: AlternateBouncerInteractor, private val isDebuggable: Boolean = Build.IS_DEBUGGABLE, - private val udfpsUtils: UdfpsUtils, private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate, private val udfpsKeyguardViewModels: Provider<UdfpsKeyguardViewModels>, ) { @@ -134,10 +127,7 @@ class UdfpsControllerOverlay @JvmOverloads constructor( privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY // Avoid announcing window title. accessibilityTitle = " " - - if (featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) { - inputFeatures = WindowManager.LayoutParams.INPUT_FEATURE_SPY - } + inputFeatures = WindowManager.LayoutParams.INPUT_FEATURE_SPY } /** If the overlay is currently showing. */ @@ -206,7 +196,6 @@ class UdfpsControllerOverlay @JvmOverloads constructor( overlayTouchListener!! ) overlayTouchListener?.onTouchExplorationStateChanged(true) - useExpandedOverlay = featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION) } } catch (e: RuntimeException) { Log.e(TAG, "showUdfpsOverlay | failed to add window", e) @@ -331,25 +320,6 @@ class UdfpsControllerOverlay @JvmOverloads constructor( return wasShowing } - /** - * This function computes the angle of touch relative to the sensor and maps - * the angle to a list of help messages which are announced if accessibility is enabled. - * - */ - fun onTouchOutsideOfSensorArea(scaledTouch: Point) { - val theStr = - udfpsUtils.onTouchOutsideOfSensorArea( - touchExplorationEnabled, - context, - scaledTouch.x, - scaledTouch.y, - overlayParams - ) - if (theStr != null) { - animationViewController?.doAnnounceForAccessibility(theStr) - } - } - /** Cancel this request. */ fun cancel() { try { @@ -367,10 +337,6 @@ class UdfpsControllerOverlay @JvmOverloads constructor( ): WindowManager.LayoutParams { val paddingX = animation?.paddingX ?: 0 val paddingY = animation?.paddingY ?: 0 - if (!featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION) && animation != null && - animation.listenForTouchesOutsideView()) { - flags = flags or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH - } val isEnrollment = when (requestReason) { REASON_ENROLL_FIND_SENSOR, REASON_ENROLL_ENROLLING -> true @@ -379,19 +345,15 @@ class UdfpsControllerOverlay @JvmOverloads constructor( // Use expanded overlay unless touchExploration enabled var rotatedBounds = - if (featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) { - if (accessibilityManager.isTouchExplorationEnabled && isEnrollment) { - Rect(overlayParams.sensorBounds) - } else { - Rect( - 0, - 0, - overlayParams.naturalDisplayWidth, - overlayParams.naturalDisplayHeight - ) - } - } else { + if (accessibilityManager.isTouchExplorationEnabled && isEnrollment) { Rect(overlayParams.sensorBounds) + } else { + Rect( + 0, + 0, + overlayParams.naturalDisplayWidth, + overlayParams.naturalDisplayHeight + ) } val rot = overlayParams.rotation @@ -399,9 +361,9 @@ class UdfpsControllerOverlay @JvmOverloads constructor( if (!shouldRotate(animation)) { Log.v( TAG, "Skip rotating UDFPS bounds " + Surface.rotationToString(rot) + - " animation=$animation" + - " isGoingToSleep=${keyguardUpdateMonitor.isGoingToSleep}" + - " isOccluded=${keyguardStateController.isOccluded}" + " animation=$animation" + + " isGoingToSleep=${keyguardUpdateMonitor.isGoingToSleep}" + + " isOccluded=${keyguardStateController.isOccluded}" ) } else { Log.v(TAG, "Rotate UDFPS bounds " + Surface.rotationToString(rot)) @@ -412,14 +374,12 @@ class UdfpsControllerOverlay @JvmOverloads constructor( rot ) - if (featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) { - RotationUtils.rotateBounds( - sensorBounds, - overlayParams.naturalDisplayWidth, - overlayParams.naturalDisplayHeight, - rot - ) - } + RotationUtils.rotateBounds( + sensorBounds, + overlayParams.naturalDisplayWidth, + overlayParams.naturalDisplayHeight, + rot + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.kt index 8cc15dadffd2..afe37d496150 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.kt @@ -43,10 +43,6 @@ class UdfpsKeyguardView( return fingerprintDrawablePlaceHolder } - fun useExpandedOverlay(useExpandedOverlay: Boolean) { - mUseExpandedOverlay = useExpandedOverlay - } - fun isVisible(): Boolean { return visible } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt index 8ce98a92adbd..3d5be6fb9f8c 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt @@ -19,7 +19,6 @@ package com.android.systemui.biometrics import android.animation.ValueAnimator import android.content.res.Configuration import android.util.MathUtils -import android.view.MotionEvent import android.view.View import androidx.annotation.VisibleForTesting import androidx.lifecycle.Lifecycle @@ -27,16 +26,15 @@ import androidx.lifecycle.repeatOnLifecycle import com.android.app.animation.Interpolators import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress import com.android.keyguard.KeyguardUpdateMonitor -import com.android.systemui.res.R import com.android.systemui.animation.ActivityLaunchAnimator import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.res.R import com.android.systemui.statusbar.LockscreenShadeTransitionController import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.notification.stack.StackStateAnimator @@ -80,8 +78,6 @@ open class UdfpsKeyguardViewControllerLegacy( ), UdfpsKeyguardViewControllerAdapter { private val uniqueIdentifier = this.toString() - private val useExpandedOverlay: Boolean = - featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION) private var showingUdfpsBouncer = false private var udfpsRequested = false private var qsExpansion = 0f @@ -192,24 +188,6 @@ open class UdfpsKeyguardViewControllerLegacy( updateAlpha() updatePauseAuth() } - - /** - * Forward touches to the UdfpsController. This allows the touch to start from outside - * the sensor area and then slide their finger into the sensor area. - */ - override fun onTouch(event: MotionEvent) { - // Don't forward touches if the shade has already started expanding. - if (transitionToFullShadeProgress != 0f) { - return - } - - // Forwarding touches not needed with expanded overlay - if (useExpandedOverlay) { - return - } else { - udfpsController.onTouch(event) - } - } } private val occludingAppBiometricUI: OccludingAppBiometricUI = @@ -294,7 +272,6 @@ open class UdfpsKeyguardViewControllerLegacy( keyguardViewManager.setOccludingAppBiometricUI(occludingAppBiometricUI) lockScreenShadeTransitionController.mUdfpsKeyguardViewControllerLegacy = this activityLaunchAnimator.addListener(activityLaunchAnimatorListener) - view.mUseExpandedOverlay = useExpandedOverlay view.startIconAsyncInflate { val animationViewInternal: View = view.requireViewById(R.id.udfps_animation_view_internal) diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java index 36a42f945235..95e3a76c11d8 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java @@ -298,45 +298,34 @@ public class UdfpsKeyguardViewLegacy extends UdfpsAnimationView { pw.println(" mUdfpsRequested=" + mUdfpsRequested); pw.println(" mInterpolatedDarkAmount=" + mInterpolatedDarkAmount); pw.println(" mAnimationType=" + mAnimationType); - pw.println(" mUseExpandedOverlay=" + mUseExpandedOverlay); } private final AsyncLayoutInflater.OnInflateFinishedListener mLayoutInflaterFinishListener = new AsyncLayoutInflater.OnInflateFinishedListener() { - @Override - public void onInflateFinished(View view, int resid, ViewGroup parent) { - mFullyInflated = true; - mAodFp = view.findViewById(R.id.udfps_aod_fp); - mLockScreenFp = view.findViewById(R.id.udfps_lockscreen_fp); - mBgProtection = view.findViewById(R.id.udfps_keyguard_fp_bg); - - updatePadding(); - updateColor(); - updateAlpha(); - - if (mUseExpandedOverlay) { - final LayoutParams lp = (LayoutParams) view.getLayoutParams(); - lp.width = mSensorBounds.width(); - lp.height = mSensorBounds.height(); - RectF relativeToView = getBoundsRelativeToView(new RectF(mSensorBounds)); - lp.setMarginsRelative( - (int) relativeToView.left, - (int) relativeToView.top, - (int) relativeToView.right, - (int) relativeToView.bottom - ); - parent.addView(view, lp); - } else { - parent.addView(view); - } - - // requires call to invalidate to update the color - mLockScreenFp.addValueCallback( - new KeyPath("**"), LottieProperty.COLOR_FILTER, - frameInfo -> new PorterDuffColorFilter(mTextColorPrimary, - PorterDuff.Mode.SRC_ATOP) - ); - mOnFinishInflateRunnable.run(); - } - }; + @Override + public void onInflateFinished(View view, int resid, ViewGroup parent) { + mFullyInflated = true; + mAodFp = view.findViewById(R.id.udfps_aod_fp); + mLockScreenFp = view.findViewById(R.id.udfps_lockscreen_fp); + mBgProtection = view.findViewById(R.id.udfps_keyguard_fp_bg); + + updatePadding(); + updateColor(); + updateAlpha(); + + final LayoutParams lp = (LayoutParams) view.getLayoutParams(); + lp.width = mSensorBounds.width(); + lp.height = mSensorBounds.height(); + RectF relativeToView = getBoundsRelativeToView(new RectF(mSensorBounds)); + lp.setMarginsRelative((int) relativeToView.left, (int) relativeToView.top, + (int) relativeToView.right, (int) relativeToView.bottom); + parent.addView(view, lp); + + // requires call to invalidate to update the color + mLockScreenFp.addValueCallback(new KeyPath("**"), LottieProperty.COLOR_FILTER, + frameInfo -> new PorterDuffColorFilter(mTextColorPrimary, + PorterDuff.Mode.SRC_ATOP)); + mOnFinishInflateRunnable.run(); + } + }; } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt index 6ce6172c9faa..76bcd6e2863b 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt @@ -19,14 +19,12 @@ import android.content.Context import android.graphics.Canvas import android.graphics.Color import android.graphics.Paint -import android.graphics.PointF import android.graphics.Rect import android.graphics.RectF import android.util.AttributeSet import android.util.Log import android.view.MotionEvent import android.widget.FrameLayout -import com.android.systemui.res.R import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams import com.android.systemui.doze.DozeReceiver @@ -39,10 +37,6 @@ class UdfpsView( context: Context, attrs: AttributeSet? ) : FrameLayout(context, attrs), DozeReceiver { - - // Use expanded overlay when feature flag is true, set by UdfpsViewController - var useExpandedOverlay: Boolean = false - // sensorRect may be bigger than the sensor. True sensor dimensions are defined in // overlayParams.sensorBounds var sensorRect = Rect() @@ -53,14 +47,6 @@ class UdfpsView( textSize = 32f } - private val sensorTouchAreaCoefficient: Float = - context.theme.obtainStyledAttributes(attrs, R.styleable.UdfpsView, 0, 0).use { a -> - require(a.hasValue(R.styleable.UdfpsView_sensorTouchAreaCoefficient)) { - "UdfpsView must contain sensorTouchAreaCoefficient" - } - a.getFloat(R.styleable.UdfpsView_sensorTouchAreaCoefficient, 0f) - } - /** View controller (can be different for enrollment, BiometricPrompt, Keyguard, etc.). */ var animationViewController: UdfpsAnimationViewController<*>? = null @@ -94,22 +80,8 @@ class UdfpsView( override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { super.onLayout(changed, left, top, right, bottom) - val paddingX = animationViewController?.paddingX ?: 0 - val paddingY = animationViewController?.paddingY ?: 0 - // Updates sensor rect in relation to the overlay view - if (useExpandedOverlay) { - animationViewController?.onSensorRectUpdated(RectF(sensorRect)) - } else { - sensorRect.set( - paddingX, - paddingY, - (overlayParams.sensorBounds.width() + paddingX), - (overlayParams.sensorBounds.height() + paddingY) - ) - - animationViewController?.onSensorRectUpdated(RectF(sensorRect)) - } + animationViewController?.onSensorRectUpdated(RectF(sensorRect)) } override fun onAttachedToWindow() { @@ -131,22 +103,6 @@ class UdfpsView( } } - fun isWithinSensorArea(x: Float, y: Float): Boolean { - // The X and Y coordinates of the sensor's center. - val translation = animationViewController?.touchTranslation ?: PointF(0f, 0f) - val cx = sensorRect.centerX() + translation.x - val cy = sensorRect.centerY() + translation.y - // Radii along the X and Y axes. - val rx = (sensorRect.right - sensorRect.left) / 2.0f - val ry = (sensorRect.bottom - sensorRect.top) / 2.0f - - return x > cx - rx * sensorTouchAreaCoefficient && - x < cx + rx * sensorTouchAreaCoefficient && - y > cy - ry * sensorTouchAreaCoefficient && - y < cy + ry * sensorTouchAreaCoefficient && - !(animationViewController?.shouldPauseAuth() ?: false) - } - fun configureDisplay(onDisplayConfigured: Runnable) { isDisplayConfigured = true animationViewController?.onDisplayConfiguring() diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt new file mode 100644 index 000000000000..9a9b0e29cbc4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.communal.data.repository + +import android.provider.Settings +import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED +import android.provider.Settings.Secure.HubModeTutorialState +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.core.Logger +import com.android.systemui.log.dagger.CommunalLog +import com.android.systemui.settings.UserTracker +import com.android.systemui.user.data.repository.UserRepository +import com.android.systemui.util.settings.SecureSettings +import com.android.systemui.util.settings.SettingsProxyExt.observerFlow +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.withContext + +/** + * Repository for the current state of hub mode tutorial. Valid states are defined in + * [HubModeTutorialState]. + */ +interface CommunalTutorialRepository { + /** Emits the tutorial state stored in Settings */ + val tutorialSettingState: StateFlow<Int> + + /** Update the tutorial state */ + suspend fun setTutorialState(@HubModeTutorialState state: Int) +} + +@OptIn(ExperimentalCoroutinesApi::class) +@SysUISingleton +class CommunalTutorialRepositoryImpl +@Inject +constructor( + @Application private val applicationScope: CoroutineScope, + @Background private val backgroundDispatcher: CoroutineDispatcher, + userRepository: UserRepository, + private val secureSettings: SecureSettings, + private val userTracker: UserTracker, + @CommunalLog logBuffer: LogBuffer, +) : CommunalTutorialRepository { + + companion object { + private const val TAG = "CommunalTutorialRepository" + } + + private data class SettingsState( + @HubModeTutorialState val hubModeTutorialState: Int? = null, + ) + + private val logger = Logger(logBuffer, TAG) + + private val settingsState: Flow<SettingsState> = + userRepository.selectedUserInfo + .flatMapLatest { observeSettings() } + .shareIn(scope = applicationScope, started = SharingStarted.WhileSubscribed()) + + /** Emits the state of tutorial state in settings */ + override val tutorialSettingState: StateFlow<Int> = + settingsState + .map { it.hubModeTutorialState } + .filterNotNull() + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = HUB_MODE_TUTORIAL_NOT_STARTED + ) + + private fun observeSettings(): Flow<SettingsState> = + secureSettings + .observerFlow( + userId = userTracker.userId, + names = + arrayOf( + Settings.Secure.HUB_MODE_TUTORIAL_STATE, + ) + ) + // Force an update + .onStart { emit(Unit) } + .map { readFromSettings() } + + private suspend fun readFromSettings(): SettingsState = + withContext(backgroundDispatcher) { + val userId = userTracker.userId + val hubModeTutorialState = + secureSettings.getIntForUser( + Settings.Secure.HUB_MODE_TUTORIAL_STATE, + HUB_MODE_TUTORIAL_NOT_STARTED, + userId, + ) + val settingsState = SettingsState(hubModeTutorialState) + logger.d({ "Communal tutorial state for user $int1 in settings: $str1" }) { + int1 = userId + str1 = settingsState.hubModeTutorialState.toString() + } + + settingsState + } + + override suspend fun setTutorialState(state: Int): Unit = + withContext(backgroundDispatcher) { + val userId = userTracker.userId + if (tutorialSettingState.value == state) { + return@withContext + } + logger.d({ "Update communal tutorial state to $int1 for user $int2" }) { + int1 = state + int2 = userId + } + secureSettings.putIntForUser( + Settings.Secure.HUB_MODE_TUTORIAL_STATE, + state, + userId, + ) + } +} diff --git a/core/java/android/security/keymaster/KeyAttestationPackageInfo.aidl b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryModule.kt index f8b843bc032f..69b0a27c55a8 100644 --- a/core/java/android/security/keymaster/KeyAttestationPackageInfo.aidl +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryModule.kt @@ -1,22 +1,27 @@ /* - * Copyright (c) 2016, The Android Open Source Project + * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. + * */ -package android.security.keymaster; +package com.android.systemui.communal.data.repository -/* The cpp_header is relative to system/security/keystore/include - * Link against libkeystore_binder to make use of the native implementation of this Parcelable. - */ -parcelable KeyAttestationPackageInfo cpp_header "keystore/KeyAttestationPackageInfo.h"; +import dagger.Binds +import dagger.Module + +@Module +interface CommunalTutorialRepositoryModule { + @Binds + fun communalTutorialRepository(impl: CommunalTutorialRepositoryImpl): CommunalTutorialRepository +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt new file mode 100644 index 000000000000..276df4eb68ec --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt @@ -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.systemui.communal.domain.interactor + +import android.provider.Settings +import com.android.systemui.communal.data.repository.CommunalTutorialRepository +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged + +/** Encapsulates business-logic related to communal tutorial state. */ +@OptIn(ExperimentalCoroutinesApi::class) +@SysUISingleton +class CommunalTutorialInteractor +@Inject +constructor( + communalTutorialRepository: CommunalTutorialRepository, + keyguardInteractor: KeyguardInteractor, +) { + /** An observable for whether the tutorial is available. */ + val isTutorialAvailable: Flow<Boolean> = + combine( + keyguardInteractor.isKeyguardVisible, + communalTutorialRepository.tutorialSettingState, + ) { isKeyguardVisible, tutorialSettingState -> + isKeyguardVisible && + tutorialSettingState != Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED + } + .distinctUntilChanged() +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalTutorialIndicatorViewBinder.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalTutorialIndicatorViewBinder.kt new file mode 100644 index 000000000000..dab6819e1028 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalTutorialIndicatorViewBinder.kt @@ -0,0 +1,66 @@ +/* + * 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.binder + +import android.widget.TextView +import androidx.core.view.isGone +import androidx.core.view.isVisible +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle +import com.android.systemui.communal.ui.viewmodel.CommunalTutorialIndicatorViewModel +import com.android.systemui.lifecycle.repeatWhenAttached +import kotlinx.coroutines.DisposableHandle +import kotlinx.coroutines.launch + +/** View binder for communal tutorial indicator shown on keyguard. */ +object CommunalTutorialIndicatorViewBinder { + fun bind( + view: TextView, + viewModel: CommunalTutorialIndicatorViewModel, + ): DisposableHandle { + val disposableHandle = + view.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.STARTED) { + launch { + viewModel.showIndicator.collect { isVisible -> + updateView( + view = view, + isIndicatorVisible = isVisible, + ) + } + } + } + } + + return disposableHandle + } + + private fun updateView( + isIndicatorVisible: Boolean, + view: TextView, + ) { + if (!isIndicatorVisible) { + view.isGone = true + return + } + + if (!view.isVisible) { + view.isVisible = true + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt index 3ff1f09cc0f1..d8d1dc0c11ef 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt @@ -16,6 +16,7 @@ package com.android.systemui.communal.ui.view.layout.blueprints +import com.android.systemui.communal.ui.view.layout.sections.DefaultCommunalHubSection import com.android.systemui.communal.ui.view.layout.sections.DefaultCommunalWidgetSection import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.shared.model.KeyguardBlueprint @@ -28,10 +29,15 @@ import javax.inject.Inject class DefaultCommunalBlueprint @Inject constructor( + defaultCommunalHubSection: DefaultCommunalHubSection, defaultCommunalWidgetSection: DefaultCommunalWidgetSection, ) : KeyguardBlueprint { override val id: String = COMMUNAL - override val sections: Set<KeyguardSection> = setOf(defaultCommunalWidgetSection) + override val sections: Set<KeyguardSection> = + setOf( + defaultCommunalHubSection, + defaultCommunalWidgetSection, + ) companion object { const val COMMUNAL = "communal" diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalTutorialIndicatorSection.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalTutorialIndicatorSection.kt new file mode 100644 index 000000000000..027cc96350f5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalTutorialIndicatorSection.kt @@ -0,0 +1,130 @@ +/* + * 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.view.layout.sections + +import android.content.res.Resources +import android.graphics.Typeface +import android.graphics.Typeface.NORMAL +import android.view.Gravity +import android.view.View +import android.view.ViewGroup.LayoutParams.WRAP_CONTENT +import android.widget.TextView +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.constraintlayout.widget.ConstraintSet +import androidx.core.content.res.ResourcesCompat +import com.android.systemui.communal.domain.interactor.CommunalInteractor +import com.android.systemui.communal.ui.binder.CommunalTutorialIndicatorViewBinder +import com.android.systemui.communal.ui.viewmodel.CommunalTutorialIndicatorViewModel +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.keyguard.shared.model.KeyguardSection +import com.android.systemui.keyguard.ui.view.layout.sections.removeView +import com.android.systemui.res.R +import javax.inject.Inject +import kotlinx.coroutines.DisposableHandle + +class CommunalTutorialIndicatorSection +@Inject +constructor( + @Main private val resources: Resources, + private val communalTutorialIndicatorViewModel: CommunalTutorialIndicatorViewModel, + private val communalInteractor: CommunalInteractor, +) : KeyguardSection() { + private var communalTutorialIndicatorHandle: DisposableHandle? = null + + override fun addViews(constraintLayout: ConstraintLayout) { + if (!communalInteractor.isCommunalEnabled) { + return + } + val padding = + constraintLayout.resources.getDimensionPixelSize( + R.dimen.communal_tutorial_indicator_padding + ) + val view = + TextView(constraintLayout.context).apply { + id = R.id.communal_tutorial_indicator + visibility = View.GONE + background = + ResourcesCompat.getDrawable( + context.resources, + R.drawable.keyguard_bottom_affordance_bg, + context.theme + ) + foreground = + ResourcesCompat.getDrawable( + context.resources, + R.drawable.keyguard_bottom_affordance_selected_border, + context.theme + ) + gravity = Gravity.CENTER_VERTICAL + typeface = Typeface.create("google-sans", NORMAL) + text = constraintLayout.context.getString(R.string.communal_tutorial_indicator_text) + setPadding(padding, padding, padding, padding) + } + constraintLayout.addView(view) + } + + override fun bindData(constraintLayout: ConstraintLayout) { + if (!communalInteractor.isCommunalEnabled) { + return + } + communalTutorialIndicatorHandle = + CommunalTutorialIndicatorViewBinder.bind( + constraintLayout.requireViewById(R.id.communal_tutorial_indicator), + communalTutorialIndicatorViewModel, + ) + } + + override fun applyConstraints(constraintSet: ConstraintSet) { + if (!communalInteractor.isCommunalEnabled) { + return + } + val tutorialIndicatorId = R.id.communal_tutorial_indicator + val width = resources.getDimensionPixelSize(R.dimen.communal_tutorial_indicator_fixed_width) + val horizontalOffsetMargin = + resources.getDimensionPixelSize(R.dimen.communal_tutorial_indicator_horizontal_offset) + + constraintSet.apply { + constrainWidth(tutorialIndicatorId, width) + constrainHeight(tutorialIndicatorId, WRAP_CONTENT) + connect( + tutorialIndicatorId, + ConstraintSet.RIGHT, + ConstraintSet.PARENT_ID, + ConstraintSet.RIGHT, + horizontalOffsetMargin + ) + connect( + tutorialIndicatorId, + ConstraintSet.TOP, + ConstraintSet.PARENT_ID, + ConstraintSet.TOP + ) + connect( + tutorialIndicatorId, + ConstraintSet.BOTTOM, + ConstraintSet.PARENT_ID, + ConstraintSet.BOTTOM + ) + } + } + + override fun removeViews(constraintLayout: ConstraintLayout) { + communalTutorialIndicatorHandle?.dispose() + constraintLayout.removeView(R.id.communal_tutorial_indicator) + } +} 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 new file mode 100644 index 000000000000..932dbfb093ce --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalHubSection.kt @@ -0,0 +1,57 @@ +package com.android.systemui.communal.ui.view.layout.sections + +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.constraintlayout.widget.ConstraintSet +import com.android.systemui.compose.ComposeFacade +import com.android.systemui.keyguard.shared.model.KeyguardSection +import com.android.systemui.keyguard.ui.view.layout.sections.removeView +import com.android.systemui.res.R +import javax.inject.Inject + +/** A keyguard section that hosts the communal hub. */ +class DefaultCommunalHubSection @Inject constructor() : KeyguardSection() { + private val communalHubViewId = R.id.communal_hub + + override fun addViews(constraintLayout: ConstraintLayout) { + constraintLayout.addView( + ComposeFacade.createCommunalView(constraintLayout.context).apply { + id = communalHubViewId + }, + ) + } + + override fun bindData(constraintLayout: ConstraintLayout) {} + + override fun applyConstraints(constraintSet: ConstraintSet) { + constraintSet.apply { + connect( + communalHubViewId, + ConstraintSet.START, + ConstraintSet.PARENT_ID, + ConstraintSet.START, + ) + connect( + communalHubViewId, + ConstraintSet.TOP, + ConstraintSet.PARENT_ID, + ConstraintSet.TOP, + ) + connect( + communalHubViewId, + ConstraintSet.END, + ConstraintSet.PARENT_ID, + ConstraintSet.END, + ) + connect( + communalHubViewId, + ConstraintSet.BOTTOM, + ConstraintSet.PARENT_ID, + ConstraintSet.BOTTOM, + ) + } + } + + override fun removeViews(constraintLayout: ConstraintLayout) { + constraintLayout.removeView(communalHubViewId) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTutorialIndicatorViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTutorialIndicatorViewModel.kt new file mode 100644 index 000000000000..eaf95508cf12 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTutorialIndicatorViewModel.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.ui.viewmodel + +import com.android.systemui.communal.domain.interactor.CommunalTutorialInteractor +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow + +/** View model for communal tutorial indicator on keyguard */ +class CommunalTutorialIndicatorViewModel +@Inject +constructor( + communalTutorialInteractor: CommunalTutorialInteractor, +) { + /** An observable for whether the tutorial indicator view should be visible. */ + val showIndicator: Flow<Boolean> = communalTutorialInteractor.isTutorialAvailable +} diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt index 1a6f7e13cf68..5c1539a7fcf3 100644 --- a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt +++ b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt @@ -72,4 +72,9 @@ interface BaseComposeFacade { windowInsets: StateFlow<WindowInsets?>, sceneByKey: Map<SceneKey, Scene>, ): View + + /** Create a [View] that represents the communal hub. */ + fun createCommunalView( + context: Context, + ): View } diff --git a/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImpl.kt index 8e3b5109339c..436b8cb851ab 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImpl.kt @@ -22,7 +22,7 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.qs.SettingObserver +import com.android.systemui.qs.UserSettingObserver import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.util.settings.SecureSettings import javax.inject.Inject @@ -64,7 +64,8 @@ constructor( .flatMapLatest { userInfo -> conflatedCallbackFlow { val observer = - object : SettingObserver(secureSettings, null, setting, userInfo.id) { + object : + UserSettingObserver(secureSettings, null, setting, userInfo.id) { override fun handleValueChanged( value: Int, observedChange: Boolean 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/GlobalModule.java b/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java index ca725c0e39ff..5c38264fbcf6 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java @@ -64,6 +64,7 @@ public class GlobalModule { * @deprecated Deprecdated because {@link Display#getMetrics} is deprecated. */ @Provides + @Deprecated public DisplayMetrics provideDisplayMetrics(Context context) { DisplayMetrics displayMetrics = new DisplayMetrics(); context.getDisplay().getMetrics(displayMetrics); diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index d5f0e649ef40..7d4e1a1011db 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -33,7 +33,6 @@ import com.android.systemui.aconfig.AConfigModule; import com.android.systemui.appops.dagger.AppOpsModule; import com.android.systemui.assist.AssistModule; import com.android.systemui.authentication.AuthenticationModule; -import com.android.systemui.biometrics.AlternateUdfpsTouchProvider; import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider; import com.android.systemui.biometrics.FingerprintReEnrollNotification; import com.android.systemui.biometrics.UdfpsDisplayModeProvider; @@ -300,9 +299,6 @@ public abstract class SystemUIModule { abstract UdfpsDisplayModeProvider optionalUdfpsDisplayModeProvider(); @BindsOptionalOf - abstract AlternateUdfpsTouchProvider optionalUdfpsTouchProvider(); - - @BindsOptionalOf abstract FingerprintInteractiveToAuthProvider optionalFingerprintInteractiveToAuthProvider(); @BindsOptionalOf 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 6bbd40c4f892..694695017efd 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java +++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java @@ -31,7 +31,6 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.res.Resources; import android.os.Bundle; -import android.os.UserHandle; import android.util.Log; import androidx.annotation.NonNull; @@ -56,15 +55,15 @@ import javax.inject.Named; /** * Concrete implementation of the a Flag manager that returns default values for debug builds - * + * <p> * Flags can be set (or unset) via the following adb command: - * + * <p> * adb shell cmd statusbar flag <id> <on|off|toggle|erase> - * + * <p> * Alternatively, you can change flags via a broadcast intent: - * + * <p> * adb shell am broadcast -a com.android.systemui.action.SET_FLAG --ei id <id> [--ez value <0|1>] - * + * <p> * To restore a flag back to its default, leave the `--ez value <0|1>` off of the command. */ @SysUISingleton @@ -319,8 +318,7 @@ public class FeatureFlagsClassicDebug implements FeatureFlagsClassic { Log.w(TAG, "Failed to set flag " + name + " to " + value); return; } - mGlobalSettings.putStringForUser(mFlagManager.nameToSettingsKey(name), data, - UserHandle.USER_CURRENT); + mGlobalSettings.putString(mFlagManager.nameToSettingsKey(name), data); } <T> void eraseFlag(Flag<T> flag) { @@ -354,8 +352,7 @@ public class FeatureFlagsClassicDebug implements FeatureFlagsClassic { /** Works just like {@link #eraseFlag(String)} except that it doesn't restart SystemUI. */ private void eraseInternal(String name) { // We can't actually "erase" things from settings, but we can set them to empty! - mGlobalSettings.putStringForUser(mFlagManager.nameToSettingsKey(name), "", - UserHandle.USER_CURRENT); + mGlobalSettings.putString(mFlagManager.nameToSettingsKey(name), ""); Log.i(TAG, "Erase name " + name); } diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index f6f24e0aecc0..11ac39ff867b 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -473,6 +473,9 @@ object Flags { // TODO(b/270437894): Tracking Bug val MEDIA_REMOTE_RESUME = unreleasedFlag("media_remote_resume") + // TODO(b/304506662): Tracking Bug + val MEDIA_DEVICE_NAME_FIX = unreleasedFlag("media_device_name_fix", teamfood = true) + // 1000 - dock val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag("simulate_dock_through_charging") @@ -667,8 +670,6 @@ object Flags { @JvmField val NOTE_TASKS = releasedFlag("keycode_flag") // 2200 - biometrics (udfps, sfps, BiometricPrompt, etc.) - // TODO(b/259264861): Tracking Bug - @JvmField val UDFPS_NEW_TOUCH_DETECTION = releasedFlag("udfps_new_touch_detection") // 2300 - stylus @JvmField val TRACK_STYLUS_EVER_USED = releasedFlag("track_stylus_ever_used") @@ -786,8 +787,7 @@ object Flags { // TODO(b/290213663): Tracking Bug @JvmField - val ONE_WAY_HAPTICS_API_MIGRATION = - unreleasedFlag("oneway_haptics_api_migration", teamfood = true) + val ONE_WAY_HAPTICS_API_MIGRATION = releasedFlag("oneway_haptics_api_migration") /** TODO(b/296223317): Enables the new keyguard presentation containing a clock. */ @JvmField diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java index ac402303bf69..c6c1f79c7113 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java @@ -749,7 +749,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene @VisibleForTesting boolean shouldDisplayBugReport(@Nullable UserInfo user) { return user != null && user.isAdmin() - && mGlobalSettings.getIntForUser(Settings.Secure.BUGREPORT_IN_POWER_MENU, 0, + && mSecureSettings.getIntForUser(Settings.Secure.BUGREPORT_IN_POWER_MENU, 0, user.id) != 0; } @@ -1091,7 +1091,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene @Override public boolean showBeforeProvisioning() { - return Build.isDebuggable() && mGlobalSettings.getIntForUser( + return Build.isDebuggable() && mSecureSettings.getIntForUser( Settings.Secure.BUGREPORT_IN_POWER_MENU, 0, getCurrentUser().id) != 0 && getCurrentUser().isAdmin(); } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index a1f0e7723597..b45613e0e182 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -269,6 +269,11 @@ public class KeyguardService extends Service { } } + @Override + public void onTransitionConsumed(IBinder transition, boolean aborted) + throws RemoteException { + } + private static void initAlphaForAnimationTargets(@NonNull SurfaceControl.Transaction t, @NonNull RemoteAnimationTarget[] targets) { for (RemoteAnimationTarget target : targets) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt index fde92b85cac3..0bac40bcbcc1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt @@ -451,7 +451,8 @@ class KeyguardUnlockAnimationController @Inject constructor( if (!keyguardStateController.isKeyguardGoingAway && willUnlockWithInWindowLauncherAnimations) { try { - launcherUnlockController?.setUnlockAmount(1f, true /* forceIfAnimating */) + launcherUnlockController?.setUnlockAmount(1f, + biometricUnlockControllerLazy.get().isWakeAndUnlock /* forceIfAnimating */) } catch (e: DeadObjectException) { Log.e(TAG, "launcherUnlockAnimationController was dead, but non-null in " + "onKeyguardGoingAwayChanged(). Catching exception as this should mean " + 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 41bde91d9a01..081edd152538 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java @@ -39,6 +39,7 @@ 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; @@ -96,6 +97,7 @@ import kotlinx.coroutines.CoroutineDispatcher; KeyguardUserSwitcherComponent.class}, includes = { CommunalRepositoryModule.class, + CommunalTutorialRepositoryModule.class, CommunalWidgetRepositoryModule.class, FalsingModule.class, KeyguardDataQuickAffordanceModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt index 75aa4b60f7b6..ca882e539a1a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt @@ -29,9 +29,7 @@ import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch @SysUISingleton @@ -64,29 +62,14 @@ constructor( private fun listenForDreamingToOccluded() { scope.launch { - keyguardInteractor.isDreaming - // Add a slight delay, as dreaming and occluded events will arrive with a small gap - // in time. This prevents a transition to OCCLUSION happening prematurely. - .onEach { delay(50) } - .sample( - combine( - keyguardInteractor.isKeyguardOccluded, - transitionInteractor.startedKeyguardTransitionStep, - ::Pair, - ), - ::toTriple - ) - .collect { (isDreaming, isOccluded, lastStartedTransition) -> + combine(keyguardInteractor.isKeyguardOccluded, keyguardInteractor.isDreaming, ::Pair) + .sample(transitionInteractor.startedKeyguardTransitionStep, ::toTriple) + .collect { (isOccluded, isDreaming, lastStartedTransition) -> if ( isOccluded && !isDreaming && - (lastStartedTransition.to == KeyguardState.DREAMING || - lastStartedTransition.to == KeyguardState.LOCKSCREEN) + lastStartedTransition.to == KeyguardState.DREAMING ) { - // At the moment, checking for LOCKSCREEN state above provides a corrective - // action. There's no great signal to determine when the dream is ending - // and a transition to OCCLUDED is beginning directly. For now, the solution - // is DREAMING->LOCKSCREEN->OCCLUDED startTransitionTo(KeyguardState.OCCLUDED) } } 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 ffa1a4959878..660bd84006d7 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 @@ -318,16 +318,9 @@ constructor( private fun listenForLockscreenToOccluded() { scope.launch { keyguardInteractor.isKeyguardOccluded - .sample( - combine( - transitionInteractor.startedKeyguardState, - keyguardInteractor.isDreaming, - ::Pair - ), - ::toTriple - ) - .collect { (isOccluded, keyguardState, isDreaming) -> - if (isOccluded && !isDreaming && keyguardState == KeyguardState.LOCKSCREEN) { + .sample(transitionInteractor.startedKeyguardState, ::Pair) + .collect { (isOccluded, keyguardState) -> + if (isOccluded && keyguardState == KeyguardState.LOCKSCREEN) { startTransitionTo(KeyguardState.OCCLUDED) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt index 6d084563cbb3..122c4c44cf9f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt @@ -29,10 +29,8 @@ import com.android.systemui.shade.ShadeController import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import javax.inject.Inject -import kotlinx.coroutines.ExperimentalCoroutinesApi /** Handles key events arriving when the keyguard is showing or device is dozing. */ -@ExperimentalCoroutinesApi @SysUISingleton class KeyguardKeyEventInteractor @Inject @@ -57,11 +55,7 @@ constructor( if (event.handleAction()) { when (event.keyCode) { KeyEvent.KEYCODE_MENU -> return dispatchMenuKeyEvent() - KeyEvent.KEYCODE_SPACE, - KeyEvent.KEYCODE_ENTER -> - if (isDeviceAwake()) { - return collapseShadeLockedOrShowPrimaryBouncer() - } + KeyEvent.KEYCODE_SPACE -> return dispatchSpaceEvent() } } return false @@ -97,24 +91,16 @@ constructor( (statusBarStateController.state != StatusBarState.SHADE) && statusBarKeyguardViewManager.shouldDismissOnMenuPressed() if (shouldUnlockOnMenuPressed) { - return collapseShadeLockedOrShowPrimaryBouncer() + shadeController.animateCollapseShadeForced() + return true } return false } - private fun collapseShadeLockedOrShowPrimaryBouncer(): Boolean { - when (statusBarStateController.state) { - StatusBarState.SHADE -> return false - StatusBarState.SHADE_LOCKED -> { - shadeController.animateCollapseShadeForced() - return true - } - StatusBarState.KEYGUARD -> { - if (!statusBarKeyguardViewManager.primaryBouncerIsShowing()) { - statusBarKeyguardViewManager.showPrimaryBouncer(true) - return true - } - } + private fun dispatchSpaceEvent(): Boolean { + if (isDeviceAwake() && statusBarStateController.state != StatusBarState.SHADE) { + shadeController.animateCollapseShadeForced() + return true } return false } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt index a0e0da452036..475d26f1db54 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt @@ -23,7 +23,6 @@ import android.widget.FrameLayout import androidx.asynclayoutinflater.view.AsyncLayoutInflater import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle -import com.android.systemui.res.R import com.android.systemui.biometrics.UdfpsKeyguardView import com.android.systemui.biometrics.ui.binder.UdfpsKeyguardInternalViewBinder import com.android.systemui.keyguard.ui.viewmodel.BackgroundViewModel @@ -32,6 +31,7 @@ import com.android.systemui.keyguard.ui.viewmodel.UdfpsAodViewModel import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardInternalViewModel import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModel import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.res.R import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch @@ -52,8 +52,6 @@ object UdfpsKeyguardViewBinder { fingerprintViewModel: FingerprintViewModel, backgroundViewModel: BackgroundViewModel, ) { - view.useExpandedOverlay(viewModel.useExpandedOverlay()) - val layoutInflaterFinishListener = AsyncLayoutInflater.OnInflateFinishedListener { inflatedInternalView, _, parent -> UdfpsKeyguardInternalViewBinder.bind( @@ -63,25 +61,21 @@ object UdfpsKeyguardViewBinder { fingerprintViewModel, backgroundViewModel, ) - if (viewModel.useExpandedOverlay()) { - val lp = inflatedInternalView.layoutParams as FrameLayout.LayoutParams - lp.width = viewModel.sensorBounds.width() - lp.height = viewModel.sensorBounds.height() - val relativeToView = - getBoundsRelativeToView( - inflatedInternalView, - RectF(viewModel.sensorBounds), - ) - lp.setMarginsRelative( - relativeToView.left.toInt(), - relativeToView.top.toInt(), - relativeToView.right.toInt(), - relativeToView.bottom.toInt(), + val lp = inflatedInternalView.layoutParams as FrameLayout.LayoutParams + lp.width = viewModel.sensorBounds.width() + lp.height = viewModel.sensorBounds.height() + val relativeToView = + getBoundsRelativeToView( + inflatedInternalView, + RectF(viewModel.sensorBounds), ) - parent!!.addView(inflatedInternalView, lp) - } else { - parent!!.addView(inflatedInternalView) - } + lp.setMarginsRelative( + relativeToView.left.toInt(), + relativeToView.top.toInt(), + relativeToView.right.toInt(), + relativeToView.bottom.toInt(), + ) + parent!!.addView(inflatedInternalView, lp) } val inflater = AsyncLayoutInflater(view.context) inflater.inflate(R.layout.udfps_keyguard_view_internal, view, layoutInflaterFinishListener) 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 c6d8ec7789f2..4a2954dc6559 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 @@ -29,12 +29,12 @@ import android.os.Handler import android.os.IBinder import android.view.Display import android.view.Display.DEFAULT_DISPLAY +import android.view.DisplayInfo import android.view.LayoutInflater import android.view.SurfaceControlViewHost import android.view.View import android.view.ViewGroup import android.view.WindowManager -import android.view.WindowManager.LayoutParams.TYPE_KEYGUARD import android.widget.FrameLayout import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.view.isInvisible @@ -129,7 +129,7 @@ constructor( bundle.getBoolean(ClockPreviewConstants.KEY_HIDE_CLOCK, false) private val wallpaperColors: WallpaperColors? = bundle.getParcelable(KEY_COLORS) private val displayId = bundle.getInt(KEY_DISPLAY_ID, DEFAULT_DISPLAY) - private val display: Display = displayManager.getDisplay(displayId) + private val display: Display? = displayManager.getDisplay(displayId) private var host: SurfaceControlViewHost @@ -179,7 +179,7 @@ constructor( fun render() { mainHandler.post { - val previewContext = context.createDisplayContext(display) + val previewContext = display?.let { context.createDisplayContext(it) } ?: context val rootView = FrameLayout(previewContext) @@ -189,16 +189,18 @@ constructor( setUpBottomArea(rootView) } - val windowContext = context.createWindowContext(display, TYPE_KEYGUARD, null) - val windowManagerOfDisplay = windowContext.getSystemService(WindowManager::class.java) + var displayInfo: DisplayInfo? = null + display?.let { + displayInfo = DisplayInfo() + it.getDisplayInfo(displayInfo) + } rootView.measure( View.MeasureSpec.makeMeasureSpec( - windowManagerOfDisplay?.currentWindowMetrics?.bounds?.width() - ?: windowManager.currentWindowMetrics.bounds.width(), + displayInfo?.logicalWidth ?: windowManager.currentWindowMetrics.bounds.width(), View.MeasureSpec.EXACTLY ), View.MeasureSpec.makeMeasureSpec( - windowManagerOfDisplay?.currentWindowMetrics?.bounds?.height() + displayInfo?.logicalHeight ?: windowManager.currentWindowMetrics.bounds.height(), View.MeasureSpec.EXACTLY ), diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt index e8df1a6fdaab..d8e43966990e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.ui.view.layout.blueprints +import com.android.systemui.communal.ui.view.layout.sections.CommunalTutorialIndicatorSection import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.shared.model.KeyguardBlueprint import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection @@ -53,6 +54,7 @@ constructor( splitShadeGuidelines: SplitShadeGuidelines, aodNotificationIconsSection: AodNotificationIconsSection, aodBurnInSection: AodBurnInSection, + communalTutorialIndicatorSection: CommunalTutorialIndicatorSection, ) : KeyguardBlueprint { override val id: String = DEFAULT @@ -69,6 +71,7 @@ constructor( splitShadeGuidelines, aodNotificationIconsSection, aodBurnInSection, + communalTutorialIndicatorSection, ) companion object { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt index 929f27f4fea3..dca151db8b58 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt @@ -17,20 +17,10 @@ package com.android.systemui.keyguard.ui.viewmodel import android.graphics.Rect -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi @ExperimentalCoroutinesApi -class UdfpsKeyguardViewModel -@Inject -constructor( - private val featureFlags: FeatureFlags, -) { +class UdfpsKeyguardViewModel @Inject constructor() { var sensorBounds: Rect = Rect() - - fun useExpandedOverlay(): Boolean { - return featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION) - } } diff --git a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java index 3d4fca1b8945..1b3b47350197 100644 --- a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java +++ b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java @@ -51,11 +51,12 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener Uri uri; boolean looping; AudioAttributes attributes; + float volume; long requestTime; public String toString() { return "{ code=" + code + " looping=" + looping + " attributes=" + attributes - + " uri=" + uri + " }"; + + " volume=" + volume + " uri=" + uri + " }"; } } @@ -101,6 +102,7 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener player.setAudioAttributes(mCmd.attributes); player.setDataSource(mCmd.context, mCmd.uri); player.setLooping(mCmd.looping); + player.setVolume(mCmd.volume); player.setOnCompletionListener(NotificationPlayer.this); player.setOnErrorListener(NotificationPlayer.this); player.prepare(); @@ -401,10 +403,11 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener * (see {@link MediaPlayer#setLooping(boolean)}) * @param stream the AudioStream to use. * (see {@link MediaPlayer#setAudioStreamType(int)}) + * @param volume the volume for the audio with values in range [0.0, 1.0] * @deprecated use {@link #play(Context, Uri, boolean, AudioAttributes)} instead. */ @Deprecated - public void play(Context context, Uri uri, boolean looping, int stream) { + public void play(Context context, Uri uri, boolean looping, int stream, float volume) { if (DEBUG) { Log.d(mTag, "play uri=" + uri.toString()); } PlayerBase.deprecateStreamTypeForPlayback(stream, "NotificationPlayer", "play"); Command cmd = new Command(); @@ -414,6 +417,7 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener cmd.uri = uri; cmd.looping = looping; cmd.attributes = new AudioAttributes.Builder().setInternalLegacyStreamType(stream).build(); + cmd.volume = volume; synchronized (mCmdQueue) { enqueueLocked(cmd); mState = PLAY; @@ -432,8 +436,10 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener * (see {@link MediaPlayer#setLooping(boolean)}) * @param attributes the AudioAttributes to use. * (see {@link MediaPlayer#setAudioAttributes(AudioAttributes)}) + * @param volume the volume for the audio with values in range [0.0, 1.0] */ - public void play(Context context, Uri uri, boolean looping, AudioAttributes attributes) { + public void play(Context context, Uri uri, boolean looping, AudioAttributes attributes, + float volume) { if (DEBUG) { Log.d(mTag, "play uri=" + uri.toString()); } Command cmd = new Command(); cmd.requestTime = SystemClock.uptimeMillis(); @@ -442,6 +448,7 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener cmd.uri = uri; cmd.looping = looping; cmd.attributes = attributes; + cmd.volume = volume; synchronized (mCmdQueue) { enqueueLocked(cmd); mState = PLAY; diff --git a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java index 80be76661098..7a488365c740 100644 --- a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java +++ b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java @@ -285,7 +285,8 @@ public class RingtonePlayer implements CoreStartable { } @Override - public void playAsync(Uri uri, UserHandle user, boolean looping, AudioAttributes aa) { + public void playAsync(Uri uri, UserHandle user, boolean looping, AudioAttributes aa, + float volume) { if (LOGD) Log.d(TAG, "playAsync(uri=" + uri + ", user=" + user + ")"); if (Binder.getCallingUid() != Process.SYSTEM_UID) { throw new SecurityException("Async playback only available from system UID."); @@ -293,7 +294,7 @@ public class RingtonePlayer implements CoreStartable { if (UserHandle.ALL.equals(user)) { user = UserHandle.SYSTEM; } - mAsyncPlayer.play(getContextForUser(user), uri, looping, aa); + mAsyncPlayer.play(getContextForUser(user), uri, looping, aa, volume); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt index a1291a4d6b0f..724241d8d41f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt @@ -19,6 +19,7 @@ package com.android.systemui.media.controls.pipeline import android.content.Context import android.os.SystemProperties import android.util.Log +import com.android.internal.annotations.KeepForWeakReference import com.android.internal.annotations.VisibleForTesting import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.dagger.qualifiers.Main @@ -82,6 +83,8 @@ constructor( private var smartspaceMediaData: SmartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA private var reactivatedKey: String? = null + // Ensure the field (and associated reference) isn't removed during optimization. + @KeepForWeakReference private val userTrackerCallback = object : UserTracker.Callback { override fun onUserChanged(newUser: Int, userContext: Context) { diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt index 0f3e0acd007d..2a32ddf03137 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt @@ -60,7 +60,6 @@ import com.android.internal.annotations.Keep import com.android.internal.logging.InstanceId import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.Dumpable -import com.android.systemui.res.R import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background @@ -83,6 +82,7 @@ import com.android.systemui.media.controls.util.MediaFlags import com.android.systemui.media.controls.util.MediaUiEventLogger import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.BcSmartspaceDataPlugin +import com.android.systemui.res.R import com.android.systemui.statusbar.NotificationMediaManager.isConnectingState import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState import com.android.systemui.statusbar.notification.row.HybridGroupManager @@ -90,6 +90,7 @@ import com.android.systemui.tuner.TunerService import com.android.systemui.util.Assert import com.android.systemui.util.Utils import com.android.systemui.util.concurrency.DelayableExecutor +import com.android.systemui.util.concurrency.ThreadFactory import com.android.systemui.util.time.SystemClock import com.android.systemui.util.traceSection import java.io.IOException @@ -245,7 +246,7 @@ class MediaDataManager( @Inject constructor( context: Context, - @Background backgroundExecutor: Executor, + threadFactory: ThreadFactory, @Main uiExecutor: Executor, @Main foregroundExecutor: DelayableExecutor, mediaControllerFactory: MediaControllerFactory, @@ -267,7 +268,9 @@ class MediaDataManager( keyguardUpdateMonitor: KeyguardUpdateMonitor, ) : this( context, - backgroundExecutor, + // Loading bitmap for UMO background can take longer time, so it cannot run on the default + // background thread. Use a custom thread for media. + threadFactory.buildExecutorOnNewThread(TAG), uiExecutor, foregroundExecutor, mediaControllerFactory, @@ -1429,8 +1432,6 @@ class MediaDataManager( } private fun onSessionDestroyed(key: String) { - if (!mediaFlags.isRetainingPlayersEnabled()) return - if (DEBUG) Log.d(TAG, "session destroyed for $key") val entry = mediaEntries.remove(key) ?: return // Clear token since the session is no longer valid @@ -1474,7 +1475,7 @@ class MediaDataManager( if (DEBUG) Log.d(TAG, "Removing still-active player $key") notifyMediaDataRemoved(key) logger.logMediaRemoved(removed.appUid, removed.packageName, removed.instanceId) - } else { + } else if (mediaFlags.isRetainingPlayersEnabled() || isAbleToResume(removed)) { // Convert to resume if (DEBUG) { Log.d( @@ -1484,6 +1485,11 @@ class MediaDataManager( ) } convertToResumePlayer(key, removed) + } else { + // Retaining players flag is off and app doesn't support resume: remove player. + if (DEBUG) Log.d(TAG, "Removing player $key") + notifyMediaDataRemoved(key) + logger.logMediaRemoved(removed.appUid, removed.packageName, removed.instanceId) } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt index 1fe93ed5503d..1db31ae4e050 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt @@ -21,6 +21,7 @@ import android.bluetooth.BluetoothLeBroadcastMetadata import android.content.Context import android.graphics.drawable.Drawable import android.media.MediaRouter2Manager +import android.media.RoutingSessionInfo import android.media.session.MediaController import android.text.TextUtils import android.util.Log @@ -31,17 +32,20 @@ import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast import com.android.settingslib.bluetooth.LocalBluetoothManager import com.android.settingslib.media.LocalMediaManager import com.android.settingslib.media.MediaDevice +import com.android.settingslib.media.PhoneMediaDevice import com.android.systemui.Dumpable -import com.android.systemui.res.R 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.media.controls.models.player.MediaData import com.android.systemui.media.controls.models.player.MediaDeviceData import com.android.systemui.media.controls.util.MediaControllerFactory import com.android.systemui.media.controls.util.MediaDataUtils import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManager import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManagerFactory +import com.android.systemui.res.R import com.android.systemui.statusbar.policy.ConfigurationController import java.io.PrintWriter import java.util.concurrent.Executor @@ -64,7 +68,8 @@ constructor( private val localBluetoothManager: LocalBluetoothManager?, @Main private val fgExecutor: Executor, @Background private val bgExecutor: Executor, - dumpManager: DumpManager + dumpManager: DumpManager, + private val featureFlags: FeatureFlagsClassic, ) : MediaDataManager.Listener, Dumpable { private val listeners: MutableSet<Listener> = mutableSetOf() @@ -215,6 +220,7 @@ constructor( println(" volumeControlId=$volumeControlId cached= $playbackVolumeControlId") println(" routingSession=$routingSession") println(" selectedRoutes=$selectedRoutes") + println(" currentConnectedDevice=${localMediaManager.currentConnectedDevice}") } } @@ -348,16 +354,16 @@ constructor( } val device = aboutToConnect?.fullMediaDevice ?: localMediaManager.currentConnectedDevice - val route = controller?.let { mr2manager.getRoutingSessionForMediaController(it) } + val routingSession = + controller?.let { mr2manager.getRoutingSessionForMediaController(it) } // If we have a controller but get a null route, then don't trust the device - val enabled = device != null && (controller == null || route != null) - val name = - if (controller == null || route != null) { - route?.name?.toString() ?: device?.name - } else { - null - } + val enabled = device != null && (controller == null || routingSession != null) + + val name = getDeviceName(device, routingSession) + if (DEBUG) { + Log.d(TAG, "new device name $name") + } current = MediaDeviceData( enabled, @@ -369,6 +375,57 @@ constructor( } } + /** Return a display name for the current device / route, or null if not possible */ + private fun getDeviceName( + device: MediaDevice?, + routingSession: RoutingSessionInfo?, + ): String? { + val selectedRoutes = routingSession?.let { mr2manager.getSelectedRoutes(it) } + + if (DEBUG) { + Log.d( + TAG, + "device is $device, controller $controller," + + " routingSession ${routingSession?.name}" + + " or ${selectedRoutes?.firstOrNull()?.name}" + ) + } + + if (!featureFlags.isEnabled(Flags.MEDIA_DEVICE_NAME_FIX)) { + if (controller == null || routingSession != null) { + return routingSession?.name?.toString() ?: device?.name + } + return null + } + + if (controller == null) { + // In resume state, we don't have a controller - just use the device name + return device?.name + } + + if (routingSession == null) { + // This happens when casting from apps that do not support MediaRouter2 + // The output switcher can't show anything useful here, so set to null + return null + } + + // If this is a user route (app / cast provided), use the provided name + if (!routingSession.isSystemSession) { + return routingSession.name?.toString() ?: device?.name + } + + selectedRoutes?.firstOrNull()?.let { + if (device is PhoneMediaDevice) { + // Get the (localized) name for this phone device + return PhoneMediaDevice.getSystemRouteNameFromType(context, it) + } else { + // If it's another type of device (in practice, Bluetooth), use the route name + return it.name.toString() + } + } + return null + } + private fun isLeAudioBroadcastEnabled(): Boolean { if (localBluetoothManager != null) { val profileManager = localBluetoothManager.profileManager 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..8634b0911391 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt @@ -0,0 +1,83 @@ +/* + * 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 +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 notifyPermissionProgress(state: Int, sessionCreationSource: Int) { + // TODO check that state & SessionCreationSource matches expected values + notifyToServer(state, sessionCreationSource) + } + + /** + * Request to log that the permission request moved to the given state. + * + * Should not be used for the initialization state, since that + */ + fun notifyPermissionProgress(state: Int) { + // TODO validate state is valid + notifyToServer( + state, + FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_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: Int) { + Log.v(TAG, "FOO notifyToServer of state $state and source $sessionCreationSource") + try { + service.notifyPermissionRequestStateChange( + Process.myUid(), state, sessionCreationSource) + } catch (e: RemoteException) { + Log.e( + TAG, + "Error notifying server of permission flow state $state from source $sessionCreationSource", + e) + } + } + + companion object { + const val TAG = "MediaProjectionMetricsLogger" + } +} 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 e61650fbb163..fced117a8132 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt @@ -20,10 +20,15 @@ import android.content.ComponentName import android.os.UserHandle import com.android.systemui.mediaprojection.appselector.data.RecentTask import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider +import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver +import com.android.systemui.shared.recents.model.ThumbnailData import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.async import kotlinx.coroutines.cancel +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch @MediaProjectionAppSelectorScope @@ -36,7 +41,8 @@ constructor( @HostUserHandle private val hostUserHandle: UserHandle, @MediaProjectionAppSelector private val scope: CoroutineScope, @MediaProjectionAppSelector private val appSelectorComponentName: ComponentName, - @MediaProjectionAppSelector private val callerPackageName: String? + @MediaProjectionAppSelector private val callerPackageName: String?, + private val thumbnailLoader: RecentTaskThumbnailLoader, ) { fun init() { @@ -46,6 +52,11 @@ constructor( val tasks = recentTasks.filterDevicePolicyRestrictedTasks().filterAppSelector().sortedTasks() + // Thumbnails are not fresh for the foreground task(s). They are only refreshed at + // launch, going to home, or going to overview. + // For this reason, we need to refresh them here. + refreshForegroundTaskThumbnails(tasks) + view.bind(tasks) } } @@ -54,6 +65,16 @@ constructor( scope.cancel() } + private suspend fun refreshForegroundTaskThumbnails(tasks: List<RecentTask>) { + coroutineScope { + val thumbnails: List<Deferred<ThumbnailData?>> = + tasks + .filter { it.isForegroundTask } + .map { async { thumbnailLoader.captureThumbnail(it.taskId) } } + thumbnails.forEach { thumbnail -> thumbnail.await() } + } + } + /** Removes all recent tasks that should be blocked according to the policy */ private fun List<RecentTask>.filterDevicePolicyRestrictedTasks(): List<RecentTask> = filter { devicePolicyResolver.isScreenCaptureAllowed( 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 41e22860d0ad..a9e6c53b3bcd 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 @@ -25,5 +25,6 @@ data class RecentTask( @UserIdInt val userId: Int, val topActivityComponent: ComponentName?, val baseIntentComponent: ComponentName?, - @ColorInt val colorBackground: Int? + @ColorInt val colorBackground: Int?, + val isForegroundTask: Boolean, ) 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 01398cf81314..aa4c4e55c718 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 @@ -48,9 +48,14 @@ constructor( override suspend fun loadRecentTasks(): List<RecentTask> = withContext(coroutineDispatcher) { - val rawRecentTasks: List<GroupedRecentTaskInfo> = recents?.getTasks() ?: emptyList() - - rawRecentTasks + val groupedTasks: List<GroupedRecentTaskInfo> = recents?.getTasks() ?: emptyList() + // Note: the returned task list is from the most-recent to least-recent order. + // The last foreground task is at index 1, because at index 0 will be our app selector. + val foregroundGroup = groupedTasks.elementAtOrNull(1) + val foregroundTaskId1 = foregroundGroup?.taskInfo1?.taskId + val foregroundTaskId2 = foregroundGroup?.taskInfo2?.taskId + val foregroundTaskIds = listOfNotNull(foregroundTaskId1, foregroundTaskId2) + groupedTasks .flatMap { listOfNotNull(it.taskInfo1, it.taskInfo2) } .map { RecentTask( @@ -58,7 +63,8 @@ constructor( it.userId, it.topActivity, it.baseIntent?.component, - it.taskDescription?.backgroundColor + it.taskDescription?.backgroundColor, + isForegroundTask = it.taskId in foregroundTaskIds ) } } diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskThumbnailLoader.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskThumbnailLoader.kt index 47faaed10302..ccf272cbd3c2 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskThumbnailLoader.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskThumbnailLoader.kt @@ -25,6 +25,8 @@ import kotlinx.coroutines.withContext interface RecentTaskThumbnailLoader { suspend fun loadThumbnail(taskId: Int): ThumbnailData? + + suspend fun captureThumbnail(taskId: Int): ThumbnailData? } class ActivityTaskManagerThumbnailLoader @@ -36,8 +38,13 @@ constructor( override suspend fun loadThumbnail(taskId: Int): ThumbnailData? = withContext(coroutineDispatcher) { - val thumbnailData = - activityManager.getTaskThumbnail(taskId, /* isLowResolution= */ false) - if (thumbnailData.thumbnail == null) null else thumbnailData + activityManager.getTaskThumbnail(taskId, /* isLowResolution= */ false).takeIf { + it.thumbnail != null + } + } + + override suspend fun captureThumbnail(taskId: Int): ThumbnailData? = + withContext(coroutineDispatcher) { + activityManager.takeTaskThumbnail(taskId).takeIf { it.thumbnail != null } } } 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/people/ui/view/PeopleViewBinder.kt b/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt index c567d56fd424..10a2b3ce7b85 100644 --- a/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt @@ -31,10 +31,10 @@ import androidx.lifecycle.Lifecycle.State.CREATED import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle -import com.android.systemui.res.R import com.android.systemui.people.PeopleSpaceTileView import com.android.systemui.people.ui.viewmodel.PeopleTileViewModel import com.android.systemui.people.ui.viewmodel.PeopleViewModel +import com.android.systemui.res.R import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch @@ -101,10 +101,10 @@ object PeopleViewBinder { view, priorityTiles, recentTiles, - viewModel::onTileClicked, + viewModel.onTileClicked, ) } else { - setNoConversationsContent(view, viewModel::onUserJourneyCancelled) + setNoConversationsContent(view, viewModel.onUserJourneyCancelled) } } } diff --git a/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt b/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt index ed7c21b787ca..b847e9523b71 100644 --- a/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt @@ -23,35 +23,32 @@ import android.content.Intent import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider -import com.android.systemui.res.R import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.people.PeopleSpaceUtils import com.android.systemui.people.PeopleTileViewHelper import com.android.systemui.people.data.model.PeopleTileModel import com.android.systemui.people.data.repository.PeopleTileRepository import com.android.systemui.people.data.repository.PeopleWidgetRepository +import com.android.systemui.res.R import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +private const val TAG = "PeopleViewModel" + /** * Models UI state for the people space, allowing the user to select which conversation should be * associated to a new or existing Conversation widget. */ class PeopleViewModel( - @Application private val context: Context, - private val tileRepository: PeopleTileRepository, - private val widgetRepository: PeopleWidgetRepository, -) : ViewModel() { /** * The list of the priority tiles/conversations. * * Important: Even though this is a Flow, the underlying API used to populate this Flow is not * reactive and you have to manually call [onTileRefreshRequested] to refresh the tiles. */ - private val _priorityTiles = MutableStateFlow(priorityTiles()) - val priorityTiles: StateFlow<List<PeopleTileViewModel>> = _priorityTiles.asStateFlow() + val priorityTiles: StateFlow<List<PeopleTileViewModel>>, /** * The list of the priority tiles/conversations. @@ -59,104 +56,133 @@ class PeopleViewModel( * Important: Even though this is a Flow, the underlying API used to populate this Flow is not * reactive and you have to manually call [onTileRefreshRequested] to refresh the tiles. */ - private val _recentTiles = MutableStateFlow(recentTiles()) - val recentTiles: StateFlow<List<PeopleTileViewModel>> = _recentTiles.asStateFlow() + val recentTiles: StateFlow<List<PeopleTileViewModel>>, /** The ID of the widget currently being edited/added. */ - private val _appWidgetId = MutableStateFlow(INVALID_APPWIDGET_ID) - val appWidgetId: StateFlow<Int> = _appWidgetId.asStateFlow() + val appWidgetId: StateFlow<Int>, /** The result of this user journey. */ - private val _result = MutableStateFlow<Result?>(null) - val result: StateFlow<Result?> = _result.asStateFlow() + val result: StateFlow<Result?>, /** Refresh the [priorityTiles] and [recentTiles]. */ - fun onTileRefreshRequested() { - _priorityTiles.value = priorityTiles() - _recentTiles.value = recentTiles() - } + val onTileRefreshRequested: () -> Unit, /** Called when the [appWidgetId] should be changed to [widgetId]. */ - fun onWidgetIdChanged(widgetId: Int) { - _appWidgetId.value = widgetId - } + val onWidgetIdChanged: (widgetId: Int) -> Unit, /** Clear [result], setting it to null. */ - fun clearResult() { - _result.value = null - } + val clearResult: () -> Unit, /** Called when a tile is clicked. */ - fun onTileClicked(tile: PeopleTileViewModel) { - val widgetId = _appWidgetId.value - if (PeopleSpaceUtils.DEBUG) { - Log.d( - TAG, - "Put ${tile.username}'s shortcut ID: ${tile.key.shortcutId} for widget ID $widgetId" - ) + val onTileClicked: (tile: PeopleTileViewModel) -> Unit, + + /** Called when this user journey is cancelled. */ + val onUserJourneyCancelled: () -> Unit, +) : ViewModel() { + /** The Factory that should be used to create a [PeopleViewModel]. */ + class Factory + @Inject + constructor( + @Application private val context: Context, + private val tileRepository: PeopleTileRepository, + private val widgetRepository: PeopleWidgetRepository, + ) : ViewModelProvider.Factory { + override fun <T : ViewModel> create(modelClass: Class<T>): T { + check(modelClass == PeopleViewModel::class.java) + return PeopleViewModel(context, tileRepository, widgetRepository) as T } - widgetRepository.setWidgetTile(widgetId, tile.key) - _result.value = - Result.Success(Intent().apply { putExtra(EXTRA_APPWIDGET_ID, appWidgetId.value) }) } - /** Called when this user journey is cancelled. */ - fun onUserJourneyCancelled() { - _result.value = Result.Cancelled + sealed class Result { + class Success(val data: Intent) : Result() + + object Cancelled : Result() } +} - private fun priorityTiles(): List<PeopleTileViewModel> { +private fun PeopleViewModel( + @Application context: Context, + tileRepository: PeopleTileRepository, + widgetRepository: PeopleWidgetRepository, +): PeopleViewModel { + fun priorityTiles(): List<PeopleTileViewModel> { return try { - tileRepository.priorityTiles().map { it.toViewModel() } + tileRepository.priorityTiles().map { it.toViewModel(context) } } catch (e: Exception) { Log.e(TAG, "Couldn't retrieve priority conversations", e) emptyList() } } - private fun recentTiles(): List<PeopleTileViewModel> { + fun recentTiles(): List<PeopleTileViewModel> { return try { - tileRepository.recentTiles().map { it.toViewModel() } + tileRepository.recentTiles().map { it.toViewModel(context) } } catch (e: Exception) { Log.e(TAG, "Couldn't retrieve recent conversations", e) emptyList() } } - private fun PeopleTileModel.toViewModel(): PeopleTileViewModel { - val icon = - PeopleTileViewHelper.getPersonIconBitmap( - context, - this, - PeopleTileViewHelper.getSizeInDp( - context, - R.dimen.avatar_size_for_medium, - context.resources.displayMetrics.density, - ) - ) - return PeopleTileViewModel(key, icon, username) + val priorityTiles = MutableStateFlow(priorityTiles()) + val recentTiles = MutableStateFlow(recentTiles()) + val appWidgetId = MutableStateFlow(INVALID_APPWIDGET_ID) + val result = MutableStateFlow<PeopleViewModel.Result?>(null) + + fun onTileRefreshRequested() { + priorityTiles.value = priorityTiles() + recentTiles.value = recentTiles() } - /** The Factory that should be used to create a [PeopleViewModel]. */ - class Factory - @Inject - constructor( - @Application private val context: Context, - private val tileRepository: PeopleTileRepository, - private val widgetRepository: PeopleWidgetRepository, - ) : ViewModelProvider.Factory { - override fun <T : ViewModel> create(modelClass: Class<T>): T { - check(modelClass == PeopleViewModel::class.java) - return PeopleViewModel(context, tileRepository, widgetRepository) as T - } + fun onWidgetIdChanged(widgetId: Int) { + appWidgetId.value = widgetId } - sealed class Result { - class Success(val data: Intent) : Result() - object Cancelled : Result() + fun clearResult() { + result.value = null } - companion object { - private const val TAG = "PeopleViewModel" + fun onTileClicked(tile: PeopleTileViewModel) { + val widgetId = appWidgetId.value + if (PeopleSpaceUtils.DEBUG) { + Log.d( + TAG, + "Put ${tile.username}'s shortcut ID: ${tile.key.shortcutId} for widget ID $widgetId" + ) + } + widgetRepository.setWidgetTile(widgetId, tile.key) + result.value = + PeopleViewModel.Result.Success( + Intent().apply { putExtra(EXTRA_APPWIDGET_ID, appWidgetId.value) } + ) } + + fun onUserJourneyCancelled() { + result.value = PeopleViewModel.Result.Cancelled + } + + return PeopleViewModel( + priorityTiles = priorityTiles.asStateFlow(), + recentTiles = recentTiles.asStateFlow(), + appWidgetId = appWidgetId.asStateFlow(), + result = result.asStateFlow(), + onTileRefreshRequested = ::onTileRefreshRequested, + onWidgetIdChanged = ::onWidgetIdChanged, + clearResult = ::clearResult, + onTileClicked = ::onTileClicked, + onUserJourneyCancelled = ::onUserJourneyCancelled, + ) +} + +fun PeopleTileModel.toViewModel(@Application context: Context): PeopleTileViewModel { + val icon = + PeopleTileViewHelper.getPersonIconBitmap( + context, + this, + PeopleTileViewHelper.getSizeInDp( + context, + R.dimen.avatar_size_for_medium, + context.resources.displayMetrics.density, + ) + ) + return PeopleTileViewModel(key, icon, username) } diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt index d949a2a0afe5..67d390d4f10d 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt @@ -25,6 +25,7 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import com.android.systemui.util.DeviceConfigProxy import com.android.systemui.util.asIndenting +import com.android.systemui.util.annotations.WeaklyReferencedCallback import com.android.systemui.util.concurrency.DelayableExecutor import com.android.systemui.util.withIncreasedIndent import java.io.PrintWriter @@ -144,6 +145,7 @@ class PrivacyConfig @Inject constructor( ipw.flush() } + @WeaklyReferencedCallback interface Callback { fun onFlagMicCameraChanged(flag: Boolean) {} diff --git a/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java b/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java index 7794fa071f45..eb11568614b7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java +++ b/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java @@ -20,15 +20,14 @@ import android.database.ContentObserver; import android.os.Handler; import com.android.systemui.statusbar.policy.Listenable; -import com.android.systemui.util.settings.GlobalSettings; import com.android.systemui.util.settings.SecureSettings; import com.android.systemui.util.settings.SettingsProxy; import com.android.systemui.util.settings.SystemSettings; /** - * Helper for managing secure, global, and system settings through use of {@link SettingsProxy}, - * which is the common superclass of {@link SecureSettings}, {@link GlobalSettings}, and - * {@link SystemSettings}. + * Helper for managing global settings through use of {@link SettingsProxy}. This should + * <em>not</em> be used for {@link SecureSettings} or {@link SystemSettings} since those must be + * user-aware (instead, use {@link UserSettingObserver}). */ public abstract class SettingObserver extends ContentObserver implements Listenable { private final SettingsProxy mSettingsProxy; @@ -36,23 +35,20 @@ public abstract class SettingObserver extends ContentObserver implements Listena private final int mDefaultValue; private boolean mListening; - private int mUserId; private int mObservedValue; protected abstract void handleValueChanged(int value, boolean observedChange); - public SettingObserver(SettingsProxy settingsProxy, Handler handler, String settingName, - int userId) { - this(settingsProxy, handler, settingName, userId, 0); + public SettingObserver(SettingsProxy settingsProxy, Handler handler, String settingName) { + this(settingsProxy, handler, settingName, 0); } public SettingObserver(SettingsProxy settingsProxy, Handler handler, String settingName, - int userId, int defaultValue) { + int defaultValue) { super(handler); mSettingsProxy = settingsProxy; mSettingName = settingName; mObservedValue = mDefaultValue = defaultValue; - mUserId = userId; } public int getValue() { @@ -65,11 +61,11 @@ public abstract class SettingObserver extends ContentObserver implements Listena * @param value The new value for the setting. */ public void setValue(int value) { - mSettingsProxy.putIntForUser(mSettingName, value, mUserId); + mSettingsProxy.putInt(mSettingName, value); } private int getValueFromProvider() { - return mSettingsProxy.getIntForUser(mSettingName, mDefaultValue, mUserId); + return mSettingsProxy.getInt(mSettingName, mDefaultValue); } @Override @@ -78,8 +74,8 @@ public abstract class SettingObserver extends ContentObserver implements Listena mListening = listening; if (listening) { mObservedValue = getValueFromProvider(); - mSettingsProxy.registerContentObserverForUser( - mSettingsProxy.getUriFor(mSettingName), false, this, mUserId); + mSettingsProxy.registerContentObserver( + mSettingsProxy.getUriFor(mSettingName), false, this); } else { mSettingsProxy.unregisterContentObserver(this); mObservedValue = mDefaultValue; @@ -94,21 +90,6 @@ public abstract class SettingObserver extends ContentObserver implements Listena handleValueChanged(value, changed); } - /** - * Set user handle for which to observe the setting. - */ - public void setUserId(int userId) { - mUserId = userId; - if (mListening) { - setListening(false); - setListening(true); - } - } - - public int getCurrentUser() { - return mUserId; - } - public String getKey() { return mSettingName; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/UserSettingObserver.java b/packages/SystemUI/src/com/android/systemui/qs/UserSettingObserver.java new file mode 100644 index 000000000000..539c2d6e64da --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/UserSettingObserver.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs; + +import android.database.ContentObserver; +import android.os.Handler; + +import com.android.systemui.statusbar.policy.Listenable; +import com.android.systemui.util.settings.SecureSettings; +import com.android.systemui.util.settings.SystemSettings; +import com.android.systemui.util.settings.UserSettingsProxy; + +/** + * Helper for managing secure and system settings through use of {@link UserSettingsProxy}, + * which is the common superclass of {@link SecureSettings} and {@link SystemSettings}. + */ +public abstract class UserSettingObserver extends ContentObserver implements Listenable { + private final UserSettingsProxy mSettingsProxy; + private final String mSettingName; + private final int mDefaultValue; + + private boolean mListening; + private int mUserId; + private int mObservedValue; + + protected abstract void handleValueChanged(int value, boolean observedChange); + + public UserSettingObserver(UserSettingsProxy settingsProxy, Handler handler, String settingName, + int userId) { + this(settingsProxy, handler, settingName, userId, 0); + } + + public UserSettingObserver(UserSettingsProxy settingsProxy, Handler handler, String settingName, + int userId, int defaultValue) { + super(handler); + mSettingsProxy = settingsProxy; + mSettingName = settingName; + mObservedValue = mDefaultValue = defaultValue; + mUserId = userId; + } + + public int getValue() { + return mListening ? mObservedValue : getValueFromProvider(); + } + + /** + * Set the value of the observed setting. + * + * @param value The new value for the setting. + */ + public void setValue(int value) { + mSettingsProxy.putIntForUser(mSettingName, value, mUserId); + } + + private int getValueFromProvider() { + return mSettingsProxy.getIntForUser(mSettingName, mDefaultValue, mUserId); + } + + @Override + public void setListening(boolean listening) { + if (listening == mListening) return; + mListening = listening; + if (listening) { + mObservedValue = getValueFromProvider(); + mSettingsProxy.registerContentObserverForUser( + mSettingsProxy.getUriFor(mSettingName), false, this, mUserId); + } else { + mSettingsProxy.unregisterContentObserver(this); + mObservedValue = mDefaultValue; + } + } + + @Override + public void onChange(boolean selfChange) { + final int value = getValueFromProvider(); + final boolean changed = value != mObservedValue; + mObservedValue = value; + handleValueChanged(value, changed); + } + + /** + * Set user handle for which to observe the setting. + */ + public void setUserId(int userId) { + mUserId = userId; + if (mListening) { + setListening(false); + setListening(true); + } + } + + public int getCurrentUser() { + return mUserId; + } + + public String getKey() { + return mSettingName; + } + + public boolean isListening() { + return mListening; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java index 2469a98140e3..78f2da53cd43 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java @@ -17,6 +17,7 @@ package com.android.systemui.qs.external; import static android.service.quicksettings.TileService.START_ACTIVITY_NEEDS_PENDING_INTENT; +import android.app.ActivityManager; import android.app.compat.CompatChanges; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -35,6 +36,7 @@ import android.os.UserHandle; import android.service.quicksettings.IQSService; import android.service.quicksettings.IQSTileService; import android.service.quicksettings.TileService; +import android.text.format.DateUtils; import android.util.ArraySet; import android.util.Log; @@ -81,7 +83,8 @@ public class TileLifecycleManager extends BroadcastReceiver implements // Bind retry control. private static final int MAX_BIND_RETRIES = 5; - private static final int DEFAULT_BIND_RETRY_DELAY = 1000; + private static final long DEFAULT_BIND_RETRY_DELAY = 5 * DateUtils.SECOND_IN_MILLIS; + private static final long LOW_MEMORY_BIND_RETRY_DELAY = 20 * DateUtils.SECOND_IN_MILLIS; // Shared prefs that hold tile lifecycle info. private static final String TILES = "tiles_prefs"; @@ -94,6 +97,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements private final IBinder mToken = new Binder(); private final PackageManagerAdapter mPackageManagerAdapter; private final BroadcastDispatcher mBroadcastDispatcher; + private final ActivityManager mActivityManager; private Set<Integer> mQueuedMessages = new ArraySet<>(); @Nullable @@ -102,7 +106,8 @@ public class TileLifecycleManager extends BroadcastReceiver implements private IBinder mClickBinder; private int mBindTryCount; - private int mBindRetryDelay = DEFAULT_BIND_RETRY_DELAY; + private long mBindRetryDelay = DEFAULT_BIND_RETRY_DELAY; + private AtomicBoolean isDeathRebindScheduled = new AtomicBoolean(false); private AtomicBoolean mBound = new AtomicBoolean(false); private AtomicBoolean mPackageReceiverRegistered = new AtomicBoolean(false); private AtomicBoolean mUserReceiverRegistered = new AtomicBoolean(false); @@ -115,7 +120,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements @AssistedInject TileLifecycleManager(@Main Handler handler, Context context, IQSService service, PackageManagerAdapter packageManagerAdapter, BroadcastDispatcher broadcastDispatcher, - @Assisted Intent intent, @Assisted UserHandle user, + @Assisted Intent intent, @Assisted UserHandle user, ActivityManager activityManager, @Background DelayableExecutor executor) { mContext = context; mHandler = handler; @@ -126,6 +131,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements mExecutor = executor; mPackageManagerAdapter = packageManagerAdapter; mBroadcastDispatcher = broadcastDispatcher; + mActivityManager = activityManager; if (DEBUG) Log.d(TAG, "Creating " + mIntent + " " + mUser); } @@ -152,10 +158,6 @@ public class TileLifecycleManager extends BroadcastReceiver implements } } - public void setBindRetryDelay(int delayMs) { - mBindRetryDelay = delayMs; - } - public boolean isActiveTile() { try { ServiceInfo info = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(), @@ -250,19 +252,15 @@ public class TileLifecycleManager extends BroadcastReceiver implements private boolean bindServices() { String packageName = mIntent.getComponent().getPackageName(); + int flags = Context.BIND_AUTO_CREATE + | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE + | Context.BIND_WAIVE_PRIORITY; if (CompatChanges.isChangeEnabled(START_ACTIVITY_NEEDS_PENDING_INTENT, packageName, mUser)) { - return mContext.bindServiceAsUser(mIntent, this, - Context.BIND_AUTO_CREATE - | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE - | Context.BIND_WAIVE_PRIORITY, - mUser); + return mContext.bindServiceAsUser(mIntent, this, flags, mUser); } return mContext.bindServiceAsUser(mIntent, this, - Context.BIND_AUTO_CREATE - | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE - | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS - | Context.BIND_WAIVE_PRIORITY, + flags | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS, mUser); } @@ -352,8 +350,32 @@ public class TileLifecycleManager extends BroadcastReceiver implements if (!mBound.get()) return; if (DEBUG) Log.d(TAG, "handleDeath"); if (checkComponentState()) { - mExecutor.executeDelayed(() -> setBindService(true), mBindRetryDelay); + if (isDeathRebindScheduled.compareAndSet(false, true)) { + mExecutor.executeDelayed(() -> { + setBindService(true); + isDeathRebindScheduled.set(false); + }, getRebindDelay()); + } + } + } + + /** + * @return the delay to automatically rebind after a service died. It provides a longer delay if + * the device is a low memory state because the service is likely to get killed again by the + * system. In this case we want to rebind later and not to cause a loop of a frequent rebinds. + */ + private long getRebindDelay() { + final ActivityManager.MemoryInfo info = new ActivityManager.MemoryInfo(); + mActivityManager.getMemoryInfo(info); + + final long delay; + if (info.lowMemory) { + delay = LOW_MEMORY_BIND_RETRY_DELAY; + } else { + delay = mBindRetryDelay; } + Log.i(TAG, "Rebinding with a delay=" + delay); + return delay; } private boolean checkComponentState() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java index 941a9d6dc952..3ee4a1be40b2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java @@ -75,13 +75,12 @@ public class TileServiceManager { private boolean mStarted = false; TileServiceManager(TileServices tileServices, Handler handler, ComponentName component, - BroadcastDispatcher broadcastDispatcher, UserTracker userTracker, - CustomTileAddedRepository customTileAddedRepository, DelayableExecutor executor) { + UserTracker userTracker, TileLifecycleManager.Factory tileLifecycleManagerFactory, + CustomTileAddedRepository customTileAddedRepository) { this(tileServices, handler, userTracker, customTileAddedRepository, - new TileLifecycleManager(handler, tileServices.getContext(), tileServices, - new PackageManagerAdapter(tileServices.getContext()), broadcastDispatcher, + tileLifecycleManagerFactory.create( new Intent(TileService.ACTION_QS_TILE).setComponent(component), - userTracker.getUserHandle(), executor)); + userTracker.getUserHandle())); } @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java index acee8e9ad2eb..c3744df5faeb 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java @@ -81,6 +81,7 @@ public class TileServices extends IQSService.Stub { private final UserTracker mUserTracker; private final StatusBarIconController mStatusBarIconController; private final PanelInteractor mPanelInteractor; + private final TileLifecycleManager.Factory mTileLifecycleManagerFactory; private final CustomTileAddedRepository mCustomTileAddedRepository; private final DelayableExecutor mBackgroundExecutor; @@ -96,6 +97,7 @@ public class TileServices extends IQSService.Stub { CommandQueue commandQueue, StatusBarIconController statusBarIconController, PanelInteractor panelInteractor, + TileLifecycleManager.Factory tileLifecycleManagerFactory, CustomTileAddedRepository customTileAddedRepository, @Background DelayableExecutor backgroundExecutor) { mHost = host; @@ -109,6 +111,7 @@ public class TileServices extends IQSService.Stub { mStatusBarIconController = statusBarIconController; mCommandQueue.addCallback(mRequestListeningCallback); mPanelInteractor = panelInteractor; + mTileLifecycleManagerFactory = tileLifecycleManagerFactory; mCustomTileAddedRepository = customTileAddedRepository; mBackgroundExecutor = backgroundExecutor; } @@ -137,8 +140,8 @@ public class TileServices extends IQSService.Stub { protected TileServiceManager onCreateTileService(ComponentName component, BroadcastDispatcher broadcastDispatcher) { - return new TileServiceManager(this, mHandlerProvider.get(), component, - broadcastDispatcher, mUserTracker, mCustomTileAddedRepository, mBackgroundExecutor); + return new TileServiceManager(this, mHandlerProvider.get(), component, mUserTracker, + mTileLifecycleManagerFactory, mCustomTileAddedRepository); } public void freeService(CustomTileInterface tile, TileServiceManager service) { @@ -323,7 +326,7 @@ public class TileServices extends IQSService.Stub { if (info.applicationInfo.isSystemApp()) { final StatusBarIcon statusIcon = icon != null ? new StatusBarIcon(userHandle, packageName, icon, 0, 0, - contentDescription) + contentDescription) : null; final String slot = getStatusBarIconSlotName(componentName); mMainHandler.post(new Runnable() { @@ -356,11 +359,11 @@ public class TileServices extends IQSService.Stub { synchronized (mServices) { mTokenMap.forEach((iBinder, customTile) -> sb.append(iBinder.toString()) - .append(":") - .append(customTile.getComponent().flattenToShortString()) - .append(":") - .append(customTile.getUser()) - .append(",")); + .append(":") + .append(customTile.getComponent().flattenToShortString()) + .append(":") + .append(customTile.getUser()) + .append(",")); } sb.append("]"); return sb.toString(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt index 64fa33ce8118..eff3e76f43be 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt @@ -33,6 +33,7 @@ import com.android.systemui.plugins.FalsingManager import com.android.systemui.qs.dagger.QSFlagsModule.PM_LITE_ENABLED import com.android.systemui.qs.footer.data.model.UserSwitcherStatusModel import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractor +import com.android.systemui.qs.footer.domain.model.SecurityButtonConfig import com.android.systemui.res.R import com.android.systemui.util.icuMessageFormat import javax.inject.Inject @@ -47,17 +48,35 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map +private const val TAG = "FooterActionsViewModel" + /** A ViewModel for the footer actions. */ class FooterActionsViewModel( - @Application appContext: Context, - private val footerActionsInteractor: FooterActionsInteractor, - private val falsingManager: FalsingManager, - private val globalActionsDialogLite: GlobalActionsDialogLite, - showPowerButton: Boolean, -) { - /** The context themed with the Quick Settings colors. */ - private val context = ContextThemeWrapper(appContext, R.style.Theme_SystemUI_QuickSettings) + /** The model for the security button. */ + val security: Flow<FooterActionsSecurityButtonViewModel?>, + + /** The model for the foreground services button. */ + val foregroundServices: Flow<FooterActionsForegroundServicesButtonViewModel?>, + /** The model for the user switcher button. */ + val userSwitcher: Flow<FooterActionsButtonViewModel?>, + + /** The model for the settings button. */ + val settings: FooterActionsButtonViewModel, + + /** The model for the power button. */ + val power: FooterActionsButtonViewModel?, + + /** + * Observe the device monitoring dialog requests and show the dialog accordingly. This function + * will suspend indefinitely and will need to be cancelled to stop observing. + * + * Important: [quickSettingsContext] must be the [Context] associated to the + * [Quick Settings fragment][com.android.systemui.qs.QSFragmentLegacy], and the call to this + * function must be cancelled when that fragment is destroyed. + */ + val observeDeviceMonitoringDialogRequests: suspend (quickSettingsContext: Context) -> Unit, +) { /** * Whether the UI rendering this ViewModel should be visible. Note that even when this is false, * the UI should still participate to the layout it is included in (i.e. in the View world it @@ -74,107 +93,6 @@ class FooterActionsViewModel( private val _backgroundAlpha = MutableStateFlow(1f) val backgroundAlpha: StateFlow<Float> = _backgroundAlpha.asStateFlow() - /** The model for the security button. */ - val security: Flow<FooterActionsSecurityButtonViewModel?> = - footerActionsInteractor.securityButtonConfig - .map { config -> - val (icon, text, isClickable) = config ?: return@map null - FooterActionsSecurityButtonViewModel( - icon, - text, - if (isClickable) this::onSecurityButtonClicked else null, - ) - } - .distinctUntilChanged() - - /** The model for the foreground services button. */ - val foregroundServices: Flow<FooterActionsForegroundServicesButtonViewModel?> = - combine( - footerActionsInteractor.foregroundServicesCount, - footerActionsInteractor.hasNewForegroundServices, - security, - ) { foregroundServicesCount, hasNewChanges, securityModel -> - if (foregroundServicesCount <= 0) { - return@combine null - } - - val text = - icuMessageFormat( - context.resources, - R.string.fgs_manager_footer_label, - foregroundServicesCount, - ) - FooterActionsForegroundServicesButtonViewModel( - foregroundServicesCount, - text = text, - displayText = securityModel == null, - hasNewChanges = hasNewChanges, - this::onForegroundServiceButtonClicked, - ) - } - .distinctUntilChanged() - - /** The model for the user switcher button. */ - val userSwitcher: Flow<FooterActionsButtonViewModel?> = - footerActionsInteractor.userSwitcherStatus - .map { userSwitcherStatus -> - when (userSwitcherStatus) { - UserSwitcherStatusModel.Disabled -> null - is UserSwitcherStatusModel.Enabled -> { - if (userSwitcherStatus.currentUserImage == null) { - Log.e( - TAG, - "Skipped the addition of user switcher button because " + - "currentUserImage is missing", - ) - return@map null - } - - userSwitcherButton(userSwitcherStatus) - } - } - } - .distinctUntilChanged() - - /** The model for the settings button. */ - val settings: FooterActionsButtonViewModel = - FooterActionsButtonViewModel( - id = R.id.settings_button_container, - Icon.Resource( - R.drawable.ic_settings, - ContentDescription.Resource(R.string.accessibility_quick_settings_settings) - ), - iconTint = - Utils.getColorAttrDefaultColor( - context, - R.attr.onShadeInactiveVariant, - ), - backgroundColor = R.attr.shadeInactive, - this::onSettingsButtonClicked, - ) - - /** The model for the power button. */ - val power: FooterActionsButtonViewModel? = - if (showPowerButton) { - FooterActionsButtonViewModel( - id = R.id.pm_lite, - Icon.Resource( - android.R.drawable.ic_lock_power_off, - ContentDescription.Resource(R.string.accessibility_quick_settings_power_menu) - ), - iconTint = - Utils.getColorAttrDefaultColor( - context, - R.attr.onShadeActive, - ), - backgroundColor = R.attr.shadeActive, - this::onPowerButtonClicked, - ) - } else { - null - } - - /** Called when the visibility of the UI rendering this model should be changed. */ fun onVisibilityChangeRequested(visible: Boolean) { _isVisible.value = visible } @@ -195,14 +113,52 @@ class FooterActionsViewModel( } } - /** - * Observe the device monitoring dialog requests and show the dialog accordingly. This function - * will suspend indefinitely and will need to be cancelled to stop observing. - * - * Important: [quickSettingsContext] must be the [Context] associated to the - * [Quick Settings fragment][com.android.systemui.qs.QSFragmentLegacy], and the call to this - * function must be cancelled when that fragment is destroyed. - */ + @SysUISingleton + class Factory + @Inject + constructor( + @Application private val context: Context, + private val falsingManager: FalsingManager, + private val footerActionsInteractor: FooterActionsInteractor, + private val globalActionsDialogLiteProvider: Provider<GlobalActionsDialogLite>, + @Named(PM_LITE_ENABLED) private val showPowerButton: Boolean, + ) { + /** Create a [FooterActionsViewModel] bound to the lifecycle of [lifecycleOwner]. */ + fun create(lifecycleOwner: LifecycleOwner): FooterActionsViewModel { + val globalActionsDialogLite = globalActionsDialogLiteProvider.get() + if (lifecycleOwner.lifecycle.currentState == Lifecycle.State.DESTROYED) { + // This should usually not happen, but let's make sure we already destroy + // globalActionsDialogLite. + globalActionsDialogLite.destroy() + } else { + // Destroy globalActionsDialogLite when the lifecycle is destroyed. + lifecycleOwner.lifecycle.addObserver( + object : DefaultLifecycleObserver { + override fun onDestroy(owner: LifecycleOwner) { + globalActionsDialogLite.destroy() + } + } + ) + } + + return FooterActionsViewModel( + context, + footerActionsInteractor, + falsingManager, + globalActionsDialogLite, + showPowerButton, + ) + } + } +} + +fun FooterActionsViewModel( + @Application appContext: Context, + footerActionsInteractor: FooterActionsInteractor, + falsingManager: FalsingManager, + globalActionsDialogLite: GlobalActionsDialogLite, + showPowerButton: Boolean, +): FooterActionsViewModel { suspend fun observeDeviceMonitoringDialogRequests(quickSettingsContext: Context) { footerActionsInteractor.deviceMonitoringDialogRequests.collect { footerActionsInteractor.showDeviceMonitoringDialog( @@ -212,7 +168,7 @@ class FooterActionsViewModel( } } - private fun onSecurityButtonClicked(quickSettingsContext: Context, expandable: Expandable) { + fun onSecurityButtonClicked(quickSettingsContext: Context, expandable: Expandable) { if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { return } @@ -220,7 +176,7 @@ class FooterActionsViewModel( footerActionsInteractor.showDeviceMonitoringDialog(quickSettingsContext, expandable) } - private fun onForegroundServiceButtonClicked(expandable: Expandable) { + fun onForegroundServiceButtonClicked(expandable: Expandable) { if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { return } @@ -228,7 +184,7 @@ class FooterActionsViewModel( footerActionsInteractor.showForegroundServicesDialog(expandable) } - private fun onUserSwitcherClicked(expandable: Expandable) { + fun onUserSwitcherClicked(expandable: Expandable) { if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { return } @@ -236,7 +192,7 @@ class FooterActionsViewModel( footerActionsInteractor.showUserSwitcher(expandable) } - private fun onSettingsButtonClicked(expandable: Expandable) { + fun onSettingsButtonClicked(expandable: Expandable) { if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { return } @@ -244,7 +200,7 @@ class FooterActionsViewModel( footerActionsInteractor.showSettings(expandable) } - private fun onPowerButtonClicked(expandable: Expandable) { + fun onPowerButtonClicked(expandable: Expandable) { if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { return } @@ -252,71 +208,179 @@ class FooterActionsViewModel( footerActionsInteractor.showPowerMenuDialog(globalActionsDialogLite, expandable) } - private fun userSwitcherButton( - status: UserSwitcherStatusModel.Enabled - ): FooterActionsButtonViewModel { - val icon = status.currentUserImage!! - - return FooterActionsButtonViewModel( - id = R.id.multi_user_switch, - icon = - Icon.Loaded( - icon, - ContentDescription.Loaded( - userSwitcherContentDescription(status.currentUserName) - ), - ), - iconTint = null, - backgroundColor = R.attr.shadeInactive, - onClick = this::onUserSwitcherClicked, - ) - } + val qsThemedContext = ContextThemeWrapper(appContext, R.style.Theme_SystemUI_QuickSettings) - private fun userSwitcherContentDescription(currentUser: String?): String? { - return currentUser?.let { user -> - context.getString(R.string.accessibility_quick_settings_user, user) - } - } + val security = + footerActionsInteractor.securityButtonConfig + .map { config -> + config?.let { securityButtonViewModel(it, ::onSecurityButtonClicked) } + } + .distinctUntilChanged() - @SysUISingleton - class Factory - @Inject - constructor( - @Application private val context: Context, - private val falsingManager: FalsingManager, - private val footerActionsInteractor: FooterActionsInteractor, - private val globalActionsDialogLiteProvider: Provider<GlobalActionsDialogLite>, - @Named(PM_LITE_ENABLED) private val showPowerButton: Boolean, - ) { - /** Create a [FooterActionsViewModel] bound to the lifecycle of [lifecycleOwner]. */ - fun create(lifecycleOwner: LifecycleOwner): FooterActionsViewModel { - val globalActionsDialogLite = globalActionsDialogLiteProvider.get() - if (lifecycleOwner.lifecycle.currentState == Lifecycle.State.DESTROYED) { - // This should usually not happen, but let's make sure we already destroy - // globalActionsDialogLite. - globalActionsDialogLite.destroy() - } else { - // Destroy globalActionsDialogLite when the lifecycle is destroyed. - lifecycleOwner.lifecycle.addObserver( - object : DefaultLifecycleObserver { - override fun onDestroy(owner: LifecycleOwner) { - globalActionsDialogLite.destroy() + val foregroundServices = + combine( + footerActionsInteractor.foregroundServicesCount, + footerActionsInteractor.hasNewForegroundServices, + security, + ) { foregroundServicesCount, hasNewChanges, securityModel -> + if (foregroundServicesCount <= 0) { + return@combine null + } + + foregroundServicesButtonViewModel( + qsThemedContext, + foregroundServicesCount, + securityModel, + hasNewChanges, + ::onForegroundServiceButtonClicked, + ) + } + .distinctUntilChanged() + + val userSwitcher = + footerActionsInteractor.userSwitcherStatus + .map { userSwitcherStatus -> + when (userSwitcherStatus) { + UserSwitcherStatusModel.Disabled -> null + is UserSwitcherStatusModel.Enabled -> { + if (userSwitcherStatus.currentUserImage == null) { + Log.e( + TAG, + "Skipped the addition of user switcher button because " + + "currentUserImage is missing", + ) + return@map null } + + userSwitcherButtonViewModel( + qsThemedContext, + userSwitcherStatus, + ::onUserSwitcherClicked + ) } - ) + } } + .distinctUntilChanged() - return FooterActionsViewModel( - context, - footerActionsInteractor, - falsingManager, - globalActionsDialogLite, - showPowerButton, - ) + val settings = settingsButtonViewModel(qsThemedContext, ::onSettingsButtonClicked) + val power = + if (showPowerButton) { + powerButtonViewModel(qsThemedContext, ::onPowerButtonClicked) + } else { + null } - } - companion object { - private const val TAG = "FooterActionsViewModel" + return FooterActionsViewModel( + security = security, + foregroundServices = foregroundServices, + userSwitcher = userSwitcher, + settings = settings, + power = power, + observeDeviceMonitoringDialogRequests = ::observeDeviceMonitoringDialogRequests, + ) +} + +fun securityButtonViewModel( + config: SecurityButtonConfig, + onSecurityButtonClicked: (Context, Expandable) -> Unit, +): FooterActionsSecurityButtonViewModel { + val (icon, text, isClickable) = config + return FooterActionsSecurityButtonViewModel( + icon, + text, + if (isClickable) onSecurityButtonClicked else null, + ) +} + +fun foregroundServicesButtonViewModel( + qsThemedContext: Context, + foregroundServicesCount: Int, + securityModel: FooterActionsSecurityButtonViewModel?, + hasNewChanges: Boolean, + onForegroundServiceButtonClicked: (Expandable) -> Unit, +): FooterActionsForegroundServicesButtonViewModel { + val text = + icuMessageFormat( + qsThemedContext.resources, + R.string.fgs_manager_footer_label, + foregroundServicesCount, + ) + + return FooterActionsForegroundServicesButtonViewModel( + foregroundServicesCount, + text = text, + displayText = securityModel == null, + hasNewChanges = hasNewChanges, + onForegroundServiceButtonClicked, + ) +} + +fun userSwitcherButtonViewModel( + qsThemedContext: Context, + status: UserSwitcherStatusModel.Enabled, + onUserSwitcherClicked: (Expandable) -> Unit, +): FooterActionsButtonViewModel { + val icon = status.currentUserImage!! + return FooterActionsButtonViewModel( + id = R.id.multi_user_switch, + icon = + Icon.Loaded( + icon, + ContentDescription.Loaded( + userSwitcherContentDescription(qsThemedContext, status.currentUserName) + ), + ), + iconTint = null, + backgroundColor = R.attr.shadeInactive, + onClick = onUserSwitcherClicked, + ) +} + +private fun userSwitcherContentDescription( + qsThemedContext: Context, + currentUser: String? +): String? { + return currentUser?.let { user -> + qsThemedContext.getString(R.string.accessibility_quick_settings_user, user) } } + +fun settingsButtonViewModel( + qsThemedContext: Context, + onSettingsButtonClicked: (Expandable) -> Unit, +): FooterActionsButtonViewModel { + return FooterActionsButtonViewModel( + id = R.id.settings_button_container, + Icon.Resource( + R.drawable.ic_settings, + ContentDescription.Resource(R.string.accessibility_quick_settings_settings) + ), + iconTint = + Utils.getColorAttrDefaultColor( + qsThemedContext, + R.attr.onShadeInactiveVariant, + ), + backgroundColor = R.attr.shadeInactive, + onSettingsButtonClicked, + ) +} + +fun powerButtonViewModel( + qsThemedContext: Context, + onPowerButtonClicked: (Expandable) -> Unit, +): FooterActionsButtonViewModel { + return FooterActionsButtonViewModel( + id = R.id.pm_lite, + Icon.Resource( + android.R.drawable.ic_lock_power_off, + ContentDescription.Resource(R.string.accessibility_quick_settings_power_menu) + ), + iconTint = + Utils.getColorAttrDefaultColor( + qsThemedContext, + R.attr.onShadeActive, + ), + backgroundColor = R.attr.shadeActive, + onPowerButtonClicked, + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt index 5a9c0a1893f8..f8e01590d368 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt @@ -23,7 +23,10 @@ import android.content.Context import android.content.res.ColorStateList import android.content.res.Configuration import android.content.res.Resources.ID_NULL +import android.graphics.Color +import android.graphics.PorterDuff import android.graphics.drawable.Drawable +import android.graphics.drawable.LayerDrawable import android.graphics.drawable.RippleDrawable import android.os.Trace import android.service.quicksettings.Tile @@ -44,7 +47,6 @@ import android.widget.TextView import androidx.annotation.VisibleForTesting import com.android.settingslib.Utils import com.android.systemui.FontSizeUtils -import com.android.systemui.res.R import com.android.systemui.animation.LaunchableView import com.android.systemui.animation.LaunchableViewDelegate import com.android.systemui.plugins.qs.QSIconView @@ -53,6 +55,7 @@ import com.android.systemui.plugins.qs.QSTile.BooleanState import com.android.systemui.plugins.qs.QSTileView import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSIconViewImpl.QS_ANIM_LENGTH +import com.android.systemui.res.R import java.util.Objects private const val TAG = "QSTileViewImpl" @@ -67,6 +70,7 @@ open class QSTileViewImpl @JvmOverloads constructor( private const val LABEL_NAME = "label" private const val SECONDARY_LABEL_NAME = "secondaryLabel" private const val CHEVRON_NAME = "chevron" + private const val OVERLAY_NAME = "overlay" const val UNAVAILABLE_ALPHA = 0.3f @VisibleForTesting internal const val TILE_STATE_RES_PREFIX = "tile_states_" @@ -97,6 +101,13 @@ open class QSTileViewImpl @JvmOverloads constructor( private val colorInactive = Utils.getColorAttrDefaultColor(context, R.attr.shadeInactive) private val colorUnavailable = Utils.getColorAttrDefaultColor(context, R.attr.shadeDisabled) + private val overlayColorActive = Utils.applyAlpha( + /* alpha= */ 0.11f, + Utils.getColorAttrDefaultColor(context, R.attr.onShadeActive)) + private val overlayColorInactive = Utils.applyAlpha( + /* alpha= */ 0.08f, + Utils.getColorAttrDefaultColor(context, R.attr.onShadeInactive)) + private val colorLabelActive = Utils.getColorAttrDefaultColor(context, R.attr.onShadeActive) private val colorLabelInactive = Utils.getColorAttrDefaultColor(context, R.attr.onShadeInactive) private val colorLabelUnavailable = @@ -123,8 +134,13 @@ open class QSTileViewImpl @JvmOverloads constructor( protected var showRippleEffect = true private lateinit var ripple: RippleDrawable - private lateinit var colorBackgroundDrawable: Drawable - private var paintColor: Int = 0 + private lateinit var backgroundDrawable: LayerDrawable + private lateinit var backgroundBaseDrawable: Drawable + private lateinit var backgroundOverlayDrawable: Drawable + + private var backgroundColor: Int = 0 + private var backgroundOverlayColor: Int = 0 + private val singleAnimator: ValueAnimator = ValueAnimator().apply { setDuration(QS_ANIM_LENGTH) addUpdateListener { animation -> @@ -134,7 +150,8 @@ open class QSTileViewImpl @JvmOverloads constructor( animation.getAnimatedValue(BACKGROUND_NAME) as Int, animation.getAnimatedValue(LABEL_NAME) as Int, animation.getAnimatedValue(SECONDARY_LABEL_NAME) as Int, - animation.getAnimatedValue(CHEVRON_NAME) as Int + animation.getAnimatedValue(CHEVRON_NAME) as Int, + animation.getAnimatedValue(OVERLAY_NAME) as Int, ) } } @@ -263,7 +280,12 @@ open class QSTileViewImpl @JvmOverloads constructor( fun createTileBackground(): Drawable { ripple = mContext.getDrawable(R.drawable.qs_tile_background) as RippleDrawable - colorBackgroundDrawable = ripple.findDrawableByLayerId(R.id.background) + backgroundDrawable = ripple.findDrawableByLayerId(R.id.background) as LayerDrawable + backgroundBaseDrawable = + backgroundDrawable.findDrawableByLayerId(R.id.qs_tile_background_base) + backgroundOverlayDrawable = + backgroundDrawable.findDrawableByLayerId(R.id.qs_tile_background_overlay) + backgroundOverlayDrawable.mutate().setTintMode(PorterDuff.Mode.SRC) return ripple } @@ -343,10 +365,10 @@ open class QSTileViewImpl @JvmOverloads constructor( ripple.also { // In case that the colorBackgroundDrawable was used as the background, make sure // it has the correct callback instead of null - colorBackgroundDrawable.callback = it + backgroundDrawable.callback = it } } else { - colorBackgroundDrawable + backgroundDrawable } } @@ -512,7 +534,7 @@ open class QSTileViewImpl @JvmOverloads constructor( singleAnimator.setValues( colorValuesHolder( BACKGROUND_NAME, - paintColor, + backgroundColor, getBackgroundColorForState(state.state, state.disabledByPolicy) ), colorValuesHolder( @@ -529,6 +551,11 @@ open class QSTileViewImpl @JvmOverloads constructor( CHEVRON_NAME, chevronView.imageTintList?.defaultColor ?: 0, getChevronColorForState(state.state, state.disabledByPolicy) + ), + colorValuesHolder( + OVERLAY_NAME, + backgroundOverlayColor, + getOverlayColorForState(state.state) ) ) singleAnimator.start() @@ -537,7 +564,8 @@ open class QSTileViewImpl @JvmOverloads constructor( getBackgroundColorForState(state.state, state.disabledByPolicy), getLabelColorForState(state.state, state.disabledByPolicy), getSecondaryLabelColorForState(state.state, state.disabledByPolicy), - getChevronColorForState(state.state, state.disabledByPolicy) + getChevronColorForState(state.state, state.disabledByPolicy), + getOverlayColorForState(state.state) ) } } @@ -555,17 +583,19 @@ open class QSTileViewImpl @JvmOverloads constructor( backgroundColor: Int, labelColor: Int, secondaryLabelColor: Int, - chevronColor: Int + chevronColor: Int, + overlayColor: Int, ) { setColor(backgroundColor) setLabelColor(labelColor) setSecondaryLabelColor(secondaryLabelColor) setChevronColor(chevronColor) + setOverlayColor(overlayColor) } private fun setColor(color: Int) { - colorBackgroundDrawable.mutate().setTint(color) - paintColor = color + backgroundBaseDrawable.mutate().setTint(color) + backgroundColor = color } private fun setLabelColor(color: Int) { @@ -580,6 +610,11 @@ open class QSTileViewImpl @JvmOverloads constructor( chevronView.imageTintList = ColorStateList.valueOf(color) } + private fun setOverlayColor(overlayColor: Int) { + backgroundOverlayDrawable.setTint(overlayColor) + backgroundOverlayColor = overlayColor + } + private fun loadSideViewDrawableIfNecessary(state: QSTile.State) { if (state.sideViewCustomDrawable != null) { customDrawableView.setImageDrawable(state.sideViewCustomDrawable) @@ -654,9 +689,17 @@ open class QSTileViewImpl @JvmOverloads constructor( private fun getChevronColorForState(state: Int, disabledByPolicy: Boolean = false): Int = getSecondaryLabelColorForState(state, disabledByPolicy) + private fun getOverlayColorForState(state: Int): Int { + return when (state) { + Tile.STATE_ACTIVE -> overlayColorActive + Tile.STATE_INACTIVE -> overlayColorInactive + else -> Color.TRANSPARENT + } + } + @VisibleForTesting internal fun getCurrentColors(): List<Int> = listOf( - paintColor, + backgroundColor, label.currentTextColor, secondaryLabel.currentTextColor, chevronView.imageTintList?.defaultColor ?: 0 diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java index fb71cefe8e0a..17251c37473f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java @@ -36,7 +36,6 @@ import androidx.annotation.Nullable; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.systemui.res.R; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; @@ -49,6 +48,7 @@ import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.SettingObserver; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; +import com.android.systemui.res.R; import com.android.systemui.settings.UserTracker; import com.android.systemui.util.settings.GlobalSettings; @@ -56,8 +56,6 @@ import dagger.Lazy; import javax.inject.Inject; - - /** Quick settings tile: Airplane mode **/ public class AirplaneModeTile extends QSTileImpl<BooleanState> { @@ -90,8 +88,7 @@ public class AirplaneModeTile extends QSTileImpl<BooleanState> { mBroadcastDispatcher = broadcastDispatcher; mLazyConnectivityManager = lazyConnectivityManager; - mSetting = new SettingObserver(globalSettings, mHandler, Global.AIRPLANE_MODE_ON, - userTracker.getUserId()) { + mSetting = new SettingObserver(globalSettings, mHandler, Global.AIRPLANE_MODE_ON) { @Override protected void handleValueChanged(int value, boolean observedChange) { // mHandler is the background handler so calling this is OK diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java index 18d472b9c755..426aa553f082 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java @@ -29,7 +29,6 @@ import androidx.annotation.Nullable; 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.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; @@ -38,9 +37,10 @@ import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.QsEventLogger; -import com.android.systemui.qs.SettingObserver; +import com.android.systemui.qs.UserSettingObserver; 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.policy.BatteryController; import com.android.systemui.util.settings.SecureSettings; @@ -53,7 +53,7 @@ public class BatterySaverTile extends QSTileImpl<BooleanState> implements private final BatteryController mBatteryController; @VisibleForTesting - protected final SettingObserver mSetting; + protected final UserSettingObserver mSetting; private int mLevel; private boolean mPowerSave; @@ -79,7 +79,7 @@ public class BatterySaverTile extends QSTileImpl<BooleanState> implements mBatteryController = batteryController; mBatteryController.observe(getLifecycle(), this); int currentUser = host.getUserContext().getUserId(); - mSetting = new SettingObserver( + mSetting = new UserSettingObserver( secureSettings, mHandler, Secure.LOW_POWER_WARNING_ACKNOWLEDGED, diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java index d862f563c8f9..18d2f306c247 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java @@ -39,7 +39,6 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settingslib.Utils; import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; -import com.android.systemui.res.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; @@ -53,6 +52,7 @@ import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialogViewModel; +import com.android.systemui.res.R; import com.android.systemui.statusbar.policy.BluetoothController; import java.util.List; @@ -198,6 +198,7 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { } state.expandedAccessibilityClassName = Switch.class.getName(); + state.forceExpandIcon = mFeatureFlags.isEnabled(Flags.BLUETOOTH_QS_TILE_DIALOG); } /** diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java index ee57b2b62d49..c8adbfcf5487 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java @@ -28,8 +28,6 @@ import android.widget.Switch; import androidx.annotation.Nullable; import com.android.internal.logging.MetricsLogger; -import com.android.systemui.res.R; -import com.android.systemui.res.R.drawable; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; @@ -38,9 +36,10 @@ import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.QsEventLogger; -import com.android.systemui.qs.SettingObserver; +import com.android.systemui.qs.UserSettingObserver; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; +import com.android.systemui.res.R; import com.android.systemui.settings.UserTracker; import com.android.systemui.util.settings.SecureSettings; @@ -51,8 +50,8 @@ public class ColorCorrectionTile extends QSTileImpl<BooleanState> { public static final String TILE_SPEC = "color_correction"; - private final Icon mIcon = ResourceIcon.get(drawable.ic_qs_color_correction); - private final SettingObserver mSetting; + private final Icon mIcon = ResourceIcon.get(R.drawable.ic_qs_color_correction); + private final UserSettingObserver mSetting; @Inject public ColorCorrectionTile( @@ -71,7 +70,7 @@ public class ColorCorrectionTile extends QSTileImpl<BooleanState> { super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); - mSetting = new SettingObserver(secureSettings, mHandler, + mSetting = new UserSettingObserver(secureSettings, mHandler, Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, userTracker.getUserId()) { @Override protected void handleValueChanged(int value, boolean observedChange) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java index 993ada6b86aa..c34a5842c1e3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java @@ -29,8 +29,6 @@ import androidx.annotation.Nullable; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.systemui.res.R; -import com.android.systemui.res.R.drawable; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; @@ -39,9 +37,10 @@ import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.QsEventLogger; -import com.android.systemui.qs.SettingObserver; +import com.android.systemui.qs.UserSettingObserver; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; +import com.android.systemui.res.R; import com.android.systemui.settings.UserTracker; import com.android.systemui.util.settings.SecureSettings; @@ -51,7 +50,7 @@ import javax.inject.Inject; public class ColorInversionTile extends QSTileImpl<BooleanState> { public static final String TILE_SPEC = "inversion"; - private final SettingObserver mSetting; + private final UserSettingObserver mSetting; @Inject public ColorInversionTile( @@ -70,7 +69,7 @@ public class ColorInversionTile extends QSTileImpl<BooleanState> { super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); - mSetting = new SettingObserver(secureSettings, mHandler, + mSetting = new UserSettingObserver(secureSettings, mHandler, Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, userTracker.getUserId()) { @Override protected void handleValueChanged(int value, boolean observedChange) { @@ -126,8 +125,8 @@ public class ColorInversionTile extends QSTileImpl<BooleanState> { state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; state.label = mContext.getString(R.string.quick_settings_inversion_label); state.icon = ResourceIcon.get(state.value - ? drawable.qs_invert_colors_icon_on - : drawable.qs_invert_colors_icon_off); + ? R.drawable.qs_invert_colors_icon_on + : R.drawable.qs_invert_colors_icon_off); state.expandedAccessibilityClassName = Switch.class.getName(); state.contentDescription = state.label; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java index 0617b30eb8fa..f6518d1e8023 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java @@ -45,7 +45,6 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settingslib.notification.EnableZenModeDialog; import com.android.systemui.Prefs; -import com.android.systemui.res.R; import com.android.systemui.animation.DialogCuj; import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.dagger.qualifiers.Background; @@ -56,10 +55,11 @@ import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.QsEventLogger; -import com.android.systemui.qs.SettingObserver; +import com.android.systemui.qs.UserSettingObserver; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.qs.tiles.dialog.QSZenModeDialogMetricsLogger; +import com.android.systemui.res.R; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.util.settings.SecureSettings; @@ -81,7 +81,7 @@ public class DndTile extends QSTileImpl<BooleanState> { private final ZenModeController mController; private final SharedPreferences mSharedPreferences; - private final SettingObserver mSettingZenDuration; + private final UserSettingObserver mSettingZenDuration; private final DialogLaunchAnimator mDialogLaunchAnimator; private final QSZenModeDialogMetricsLogger mQSZenDialogMetricsLogger; @@ -109,7 +109,7 @@ public class DndTile extends QSTileImpl<BooleanState> { mSharedPreferences = sharedPreferences; mController.observe(getLifecycle(), mZenCallback); mDialogLaunchAnimator = dialogLaunchAnimator; - mSettingZenDuration = new SettingObserver(secureSettings, mUiHandler, + mSettingZenDuration = new UserSettingObserver(secureSettings, mUiHandler, Settings.Secure.ZEN_DURATION, getHost().getUserId()) { @Override protected void handleValueChanged(int value, boolean observedChange) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java index a08b3fca15aa..4f0a63b667f3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java @@ -38,7 +38,6 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.internal.logging.MetricsLogger; -import com.android.systemui.res.R; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; @@ -49,9 +48,10 @@ import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.QsEventLogger; -import com.android.systemui.qs.SettingObserver; +import com.android.systemui.qs.UserSettingObserver; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; +import com.android.systemui.res.R; import com.android.systemui.settings.UserTracker; import com.android.systemui.util.settings.SecureSettings; @@ -69,8 +69,8 @@ public class DreamTile extends QSTileImpl<QSTile.BooleanState> { private final Icon mIconUndocked = ResourceIcon.get(R.drawable.ic_qs_screen_saver_undocked); private final IDreamManager mDreamManager; private final BroadcastDispatcher mBroadcastDispatcher; - private final SettingObserver mEnabledSettingObserver; - private final SettingObserver mDreamSettingObserver; + private final UserSettingObserver mEnabledSettingObserver; + private final UserSettingObserver mDreamSettingObserver; private final UserTracker mUserTracker; private final boolean mDreamSupported; private final boolean mDreamOnlyEnabledForDockUser; @@ -111,14 +111,14 @@ public class DreamTile extends QSTileImpl<QSTile.BooleanState> { statusBarStateController, activityStarter, qsLogger); mDreamManager = dreamManager; mBroadcastDispatcher = broadcastDispatcher; - mEnabledSettingObserver = new SettingObserver(secureSettings, mHandler, + mEnabledSettingObserver = new UserSettingObserver(secureSettings, mHandler, Settings.Secure.SCREENSAVER_ENABLED, userTracker.getUserId()) { @Override protected void handleValueChanged(int value, boolean observedChange) { refreshState(); } }; - mDreamSettingObserver = new SettingObserver(secureSettings, mHandler, + mDreamSettingObserver = new UserSettingObserver(secureSettings, mHandler, Settings.Secure.SCREENSAVER_COMPONENTS, userTracker.getUserId()) { @Override protected void handleValueChanged(int value, boolean observedChange) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java index 78af97668fc0..b08e6a5580a2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java @@ -28,7 +28,6 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; -import com.android.systemui.res.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; @@ -37,9 +36,10 @@ import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.QsEventLogger; -import com.android.systemui.qs.SettingObserver; +import com.android.systemui.qs.UserSettingObserver; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; +import com.android.systemui.res.R; import com.android.systemui.settings.UserTracker; import com.android.systemui.util.settings.SecureSettings; import com.android.wm.shell.onehanded.OneHanded; @@ -53,7 +53,7 @@ public class OneHandedModeTile extends QSTileImpl<BooleanState> { private final Icon mIcon = ResourceIcon.get( com.android.internal.R.drawable.ic_qs_one_handed_mode); - private final SettingObserver mSetting; + private final UserSettingObserver mSetting; @Inject public OneHandedModeTile( @@ -70,7 +70,7 @@ public class OneHandedModeTile extends QSTileImpl<BooleanState> { SecureSettings secureSettings) { super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); - mSetting = new SettingObserver(secureSettings, mHandler, + mSetting = new UserSettingObserver(secureSettings, mHandler, Settings.Secure.ONE_HANDED_MODE_ENABLED, userTracker.getUserId()) { @Override protected void handleValueChanged(int value, boolean observedChange) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java index 5a9500494de0..f1d8f9f25286 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java @@ -36,7 +36,6 @@ import androidx.annotation.Nullable; 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.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; @@ -45,9 +44,10 @@ import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.QsEventLogger; -import com.android.systemui.qs.SettingObserver; +import com.android.systemui.qs.UserSettingObserver; 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.policy.BatteryController; import com.android.systemui.statusbar.policy.RotationLockController; import com.android.systemui.statusbar.policy.RotationLockController.RotationLockControllerCallback; @@ -67,7 +67,7 @@ public class RotationLockTile extends QSTileImpl<BooleanState> implements private final RotationLockController mController; private final SensorPrivacyManager mPrivacyManager; private final BatteryController mBatteryController; - private final SettingObserver mSetting; + private final UserSettingObserver mSetting; private final boolean mAllowRotationResolver; @Inject @@ -93,7 +93,7 @@ public class RotationLockTile extends QSTileImpl<BooleanState> implements mPrivacyManager = privacyManager; mBatteryController = batteryController; int currentUser = host.getUserContext().getUserId(); - mSetting = new SettingObserver( + mSetting = new UserSettingObserver( secureSettings, mHandler, Secure.CAMERA_AUTOROTATE, diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt index 8ae2dc227998..80af76de23df 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt @@ -102,9 +102,10 @@ constructor( showSeeAll: Boolean, showPairNewDevice: Boolean ) { - seeAllViewGroup.visibility = if (showSeeAll) VISIBLE else GONE - pairNewDeviceViewGroup.visibility = if (showPairNewDevice) VISIBLE else GONE - deviceItemAdapter.refreshDeviceItemList(deviceItem) + deviceItemAdapter.refreshDeviceItemList(deviceItem) { + seeAllViewGroup.visibility = if (showSeeAll) VISIBLE else GONE + pairNewDeviceViewGroup.visibility = if (showPairNewDevice) VISIBLE else GONE + } } internal fun onBluetoothStateUpdated(isEnabled: Boolean, subtitleResId: Int) { @@ -173,8 +174,8 @@ constructor( internal fun getItem(position: Int) = asyncListDiffer.currentList[position] - internal fun refreshDeviceItemList(updated: List<DeviceItem>) { - asyncListDiffer.submitList(updated) + internal fun refreshDeviceItemList(updated: List<DeviceItem>, callback: () -> Unit) { + asyncListDiffer.submitList(updated, callback) } internal inner class DeviceItemViewHolder(view: View) : RecyclerView.ViewHolder(view) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt index 97e178371f2d..8e274932d4d2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt @@ -113,7 +113,6 @@ constructor( .launchIn(this) deviceItemInteractor.deviceItemUpdate - .filterNotNull() .onEach { dialog!!.onDeviceItemUpdated( it.take(MAX_DEVICE_ITEM_ENTRY), diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt index 14d24f952c8d..e196c6c27c8b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt @@ -34,10 +34,10 @@ import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.withContext @@ -55,10 +55,10 @@ constructor( @Background private val backgroundDispatcher: CoroutineDispatcher, ) { - private val mutableDeviceItemUpdate: MutableStateFlow<List<DeviceItem>?> = - MutableStateFlow(null) + private val mutableDeviceItemUpdate: MutableSharedFlow<List<DeviceItem>> = + MutableSharedFlow(extraBufferCapacity = 1) internal val deviceItemUpdate - get() = mutableDeviceItemUpdate.asStateFlow() + get() = mutableDeviceItemUpdate.asSharedFlow() internal val deviceItemUpdateRequest: SharedFlow<Unit> = conflatedCallbackFlow { @@ -119,16 +119,15 @@ constructor( internal suspend fun updateDeviceItems(context: Context) { withContext(backgroundDispatcher) { - val mostRecentlyConnectedDevices = bluetoothAdapter?.mostRecentlyConnectedDevices - - mutableDeviceItemUpdate.value = + mutableDeviceItemUpdate.tryEmit( bluetoothTileDialogRepository.cachedDevices .mapNotNull { cachedDevice -> deviceItemFactoryList .firstOrNull { it.isFilterMatched(cachedDevice, audioManager) } ?.create(context, cachedDevice) } - .sort(displayPriority, mostRecentlyConnectedDevices) + .sort(displayPriority, bluetoothAdapter?.mostRecentlyConnectedDevices) + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/ObservableTransitionState.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/ObservableTransitionState.kt index f704894e56e2..3927873f8ba8 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/ObservableTransitionState.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/ObservableTransitionState.kt @@ -42,13 +42,6 @@ sealed class ObservableTransitionState { * scene, this value will remain true after the pointer is no longer touching the screen and * will be true in any transition created to animate back to the original position. */ - val isInitiatedByUserInput: Boolean, - - /** - * Whether user input is currently driving the transition. For example, if a user is - * dragging a pointer, this emits true. Once they lift their finger, this emits false while - * the transition completes/settles. - */ - val isUserInputOngoing: Flow<Boolean>, + val isUserInputDriven: Boolean, ) : ObservableTransitionState() } 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/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java index 9325e18abce7..00d480a76355 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java @@ -19,6 +19,7 @@ package com.android.systemui.screenshot; import android.app.Activity; import android.app.ActivityOptions; import android.content.ComponentName; +import android.content.ContentProvider; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.HardwareRenderer; @@ -44,10 +45,10 @@ import androidx.constraintlayout.widget.ConstraintLayout; import com.android.internal.app.ChooserActivity; import com.android.internal.logging.UiEventLogger; import com.android.internal.view.OneShotPreDrawListener; -import com.android.systemui.res.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.res.R; import com.android.systemui.screenshot.CropView.CropBoundary; import com.android.systemui.screenshot.ScrollCaptureController.LongScreenshot; import com.android.systemui.settings.UserTracker; @@ -421,13 +422,15 @@ public class LongScreenshotActivity extends Activity { Log.e(TAG, "failed to export", e); return; } + Uri exported = ContentProvider.getUriWithoutUserId(result.uri); + Log.e(TAG, action + " uri=" + exported); switch (action) { case EDIT: - doEdit(result.uri); + doEdit(exported); break; case SHARE: - doShare(result.uri); + doShare(exported); break; case SAVE: // Nothing more to do diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 4b3bd0b44bc9..127a57e26a30 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -303,6 +303,13 @@ public class ScreenshotController { private String mPackageName = ""; private BroadcastReceiver mCopyBroadcastReceiver; + // When false, the screenshot is taken without showing the ui. Note that this only applies to + // external displays, as on the default one the UI should **always** be shown. + // This is needed in case of screenshot during display mirroring, as adding another window to + // the external display makes mirroring stop. + // When there is a way to distinguish between displays that are mirroring or extending, this + // can be removed and we can directly show the ui only in the extended case. + private final Boolean mShowUIOnExternalDisplay; /** Tracks config changes that require re-creating UI */ private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges( ActivityInfo.CONFIG_ORIENTATION @@ -335,7 +342,8 @@ public class ScreenshotController { AssistContentRequester assistContentRequester, MessageContainerController messageContainerController, Provider<ScreenshotSoundController> screenshotSoundController, - @Assisted int displayId + @Assisted int displayId, + @Assisted boolean showUIOnExternalDisplay ) { mScreenshotSmartActions = screenshotSmartActions; mNotificationsController = screenshotNotificationsController; @@ -401,6 +409,7 @@ public class ScreenshotController { mContext.registerReceiver(mCopyBroadcastReceiver, new IntentFilter( ClipboardOverlayController.COPY_OVERLAY_ACTION), ClipboardOverlayController.SELF_PERMISSION, null, Context.RECEIVER_NOT_EXPORTED); + mShowUIOnExternalDisplay = showUIOnExternalDisplay; } void handleScreenshot(ScreenshotData screenshot, Consumer<Uri> finisher, @@ -448,6 +457,23 @@ public class ScreenshotController { prepareViewForNewScreenshot(screenshot, oldPackageName); + if (mFlags.isEnabled(Flags.SCREENSHOT_METADATA) && screenshot.getTaskId() >= 0) { + mAssistContentRequester.requestAssistContent(screenshot.getTaskId(), + new AssistContentRequester.Callback() { + @Override + public void onAssistContentAvailable(AssistContent assistContent) { + screenshot.setContextUrl(assistContent.getWebUri()); + } + }); + } + + if (!shouldShowUi()) { + saveScreenshotInWorkerThread( + screenshot.getUserHandle(), finisher, this::logSuccessOnActionsReady, + (ignored) -> {}); + return; + } + saveScreenshotInWorkerThread(screenshot.getUserHandle(), finisher, this::showUiOnActionsReady, this::showUiOnQuickShareActionReady); @@ -482,22 +508,16 @@ public class ScreenshotController { screenshot.getUserHandle())); mScreenshotView.setScreenshot(screenshot); - if (mFlags.isEnabled(Flags.SCREENSHOT_METADATA) && screenshot.getTaskId() >= 0) { - mAssistContentRequester.requestAssistContent(screenshot.getTaskId(), - new AssistContentRequester.Callback() { - @Override - public void onAssistContentAvailable(AssistContent assistContent) { - screenshot.setContextUrl(assistContent.getWebUri()); - } - }); - } - // ignore system bar insets for the purpose of window layout mWindow.getDecorView().setOnApplyWindowInsetsListener( (v, insets) -> WindowInsets.CONSUMED); mScreenshotHandler.cancelTimeout(); // restarted after animation } + private boolean shouldShowUi() { + return mDisplayId == Display.DEFAULT_DISPLAY || mShowUIOnExternalDisplay; + } + void prepareViewForNewScreenshot(ScreenshotData screenshot, String oldPackageName) { withWindowAttached(() -> { if (mUserManager.isManagedProfile(screenshot.getUserHandle().getIdentifier())) { @@ -1199,7 +1219,13 @@ public class ScreenshotController { /** Injectable factory to create screenshot controller instances for a specific display. */ @AssistedFactory public interface Factory { - /** Creates an instance of the controller for that specific displayId. */ - ScreenshotController create(int displayId); + /** + * Creates an instance of the controller for that specific displayId. + * + * @param displayId: display to capture + * @param showUIOnExternalDisplay: Whether the UI should be shown if this is an external + * display. + */ + ScreenshotController create(int displayId, boolean showUIOnExternalDisplay); } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt index 03c3f7a8ddf3..abe40ff11ead 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt @@ -135,7 +135,9 @@ constructor( } private fun getScreenshotController(id: Int): ScreenshotController { - return screenshotControllers.computeIfAbsent(id) { screenshotControllerFactory.create(id) } + return screenshotControllers.computeIfAbsent(id) { + screenshotControllerFactory.create(id, /* showUIOnExternalDisplay= */ false) + } } /** For java compatibility only. see [executeScreenshots] */ diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java index 0be2265bf8fa..75d52cbe2e36 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java @@ -132,7 +132,8 @@ public class TakeScreenshotService extends Service { if (mFeatureFlags.isEnabled(MULTI_DISPLAY_SCREENSHOT)) { mScreenshot = null; } else { - mScreenshot = screenshotControllerFactory.create(Display.DEFAULT_DISPLAY); + mScreenshot = screenshotControllerFactory.create( + Display.DEFAULT_DISPLAY, /* showUIOnExternalDisplay= */ false); } } diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt index bd592c9daff6..cf1fbe3685e3 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt @@ -16,6 +16,8 @@ package com.android.systemui.settings +import com.android.systemui.util.annotations.WeaklyReferencedCallback + import android.content.Context import android.content.pm.UserInfo import android.os.UserHandle @@ -64,6 +66,7 @@ interface UserTracker : UserContentResolverProvider, UserContextProvider { /** * Callback for notifying of changes. */ + @WeaklyReferencedCallback interface Callback { /** diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index ba0cf08150f6..51972c2d025c 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) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt index 0554c5855d61..1a28ddc9719b 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt @@ -72,10 +72,6 @@ class ShadeExpansionStateManager @Inject constructor() : ShadeStateEvents { listener.onShadeExpansionFullyChanged(qsExpanded) } - fun removeFullExpansionListener(listener: ShadeFullExpansionListener) { - fullExpansionListeners.remove(listener) - } - fun addQsExpansionListener(listener: ShadeQsExpansionListener) { qsExpansionListeners.add(listener) listener.onQsExpansionChanged(qsExpanded) @@ -99,11 +95,6 @@ class ShadeExpansionStateManager @Inject constructor() : ShadeStateEvents { stateListeners.add(listener) } - /** Removes a state listener. */ - fun removeStateListener(listener: ShadeStateListener) { - stateListeners.remove(listener) - } - override fun addShadeStateEventsListener(listener: ShadeStateEventsListener) { shadeStateEventsListeners.addIfAbsent(listener) } diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt index 6117f9f80e6c..99370e963c9f 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt @@ -34,7 +34,7 @@ import com.android.systemui.statusbar.notification.stack.domain.interactor.Share import com.android.systemui.statusbar.phone.DozeParameters import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository import com.android.systemui.statusbar.policy.data.repository.DeviceProvisioningRepository -import com.android.systemui.user.domain.interactor.UserInteractor +import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.util.kotlin.pairwise import javax.inject.Inject import javax.inject.Provider @@ -71,7 +71,7 @@ constructor( keyguardTransitionInteractor: KeyguardTransitionInteractor, powerInteractor: PowerInteractor, userSetupRepository: UserSetupRepository, - userInteractor: UserInteractor, + userRepository: UserRepository, sharedNotificationContainerInteractor: SharedNotificationContainerInteractor, repository: ShadeRepository, ) { @@ -156,12 +156,13 @@ constructor( * * TODO(b/300258424) remove all but the first sentence of this comment */ - val isAnyExpanded: Flow<Boolean> = + val isAnyExpanded: StateFlow<Boolean> = if (sceneContainerFlags.isEnabled()) { - anyExpansion.map { it > 0f }.distinctUntilChanged() - } else { - repository.legacyExpandedOrAwaitingInputTransfer - } + anyExpansion.map { it > 0f }.distinctUntilChanged() + } else { + repository.legacyExpandedOrAwaitingInputTransfer + } + .stateIn(scope, SharingStarted.Eagerly, false) /** * Whether the user is expanding or collapsing the shade with user input. This will be true even @@ -227,7 +228,7 @@ constructor( isDeviceProvisioned && // Disallow QS during setup if it's a simple user switcher. (The user intends to // use the lock screen user switcher, QS is not needed.) - (isUserSetup || !userInteractor.isSimpleUserSwitcher) && + (isUserSetup || !userRepository.isSimpleUserSwitcher()) && isShadeEnabled && disableFlags.isQuickSettingsEnabled() && !isDozing @@ -261,7 +262,7 @@ constructor( when (state) { is ObservableTransitionState.Idle -> false is ObservableTransitionState.Transition -> - state.isInitiatedByUserInput && + state.isUserInputDriven && (state.toScene == sceneKey || state.fromScene == sceneKey) } } 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/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java index 37a4ef168423..ef45d2b75ba0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java @@ -18,6 +18,7 @@ package com.android.systemui.statusbar; import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_FROM_AOD; import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_TO_AOD; +import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -43,15 +44,17 @@ import com.android.internal.logging.UiEventLogger; import com.android.keyguard.KeyguardClockSwitch; import com.android.systemui.DejankUtils; import com.android.systemui.Dumpable; -import com.android.systemui.res.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; -import com.android.systemui.shade.ShadeExpansionStateManager; +import com.android.systemui.res.R; +import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; import com.android.systemui.statusbar.policy.CallbackController; import com.android.systemui.util.Compile; +import dagger.Lazy; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.Comparator; @@ -95,6 +98,7 @@ public class StatusBarStateControllerImpl implements private final ArrayList<RankedListener> mListeners = new ArrayList<>(); private final UiEventLogger mUiEventLogger; private final InteractionJankMonitor mInteractionJankMonitor; + private final Lazy<ShadeInteractor> mShadeInteractorLazy; private int mState; private int mLastState; private int mUpcomingState; @@ -158,15 +162,13 @@ public class StatusBarStateControllerImpl implements UiEventLogger uiEventLogger, DumpManager dumpManager, InteractionJankMonitor interactionJankMonitor, - ShadeExpansionStateManager shadeExpansionStateManager - ) { + Lazy<ShadeInteractor> shadeInteractorLazy) { mUiEventLogger = uiEventLogger; mInteractionJankMonitor = interactionJankMonitor; + mShadeInteractorLazy = shadeInteractorLazy; for (int i = 0; i < HISTORY_SIZE; i++) { mHistoricalRecords[i] = new HistoricalState(); } - shadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged); - dumpManager.registerDumpable(this); } @@ -336,6 +338,8 @@ public class StatusBarStateControllerImpl implements && (view != null && view.isAttachedToWindow())) { mView = view; mClockSwitchView = view.findViewById(R.id.keyguard_clock_container); + collectFlow(mView, mShadeInteractorLazy.get().isAnyExpanded(), + this::onShadeOrQsExpanded); } mDozeAmountTarget = dozeAmount; if (animated) { @@ -345,7 +349,7 @@ public class StatusBarStateControllerImpl implements } } - private void onShadeExpansionFullyChanged(Boolean isExpanded) { + private void onShadeOrQsExpanded(Boolean isExpanded) { if (mIsExpanded != isExpanded) { mIsExpanded = isExpanded; String tag = getClass().getSimpleName() + "#setIsExpanded"; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java index 9d56a8ede1cc..362786ec4b58 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java @@ -115,6 +115,7 @@ public interface NotifCollectionListener { * * @deprecated Use {@link #onRankingApplied()} instead. */ + @Deprecated default void onRankingUpdate(NotificationListenerService.RankingMap rankingMap) { } 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/interruption/NotificationInterruptStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java index 6ec9dbe003a2..b0155f13dbec 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java @@ -180,4 +180,9 @@ public interface NotificationInterruptStateProvider { * Add a component that can suppress visual interruptions. */ void addSuppressor(NotificationInterruptSuppressor suppressor); + + /** + * Remove a component that can suppress visual interruptions. + */ + void removeSuppressor(NotificationInterruptSuppressor suppressor); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java index 3819843aa7b2..778a0a90cd85 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java @@ -175,6 +175,11 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter } @Override + public void removeSuppressor(NotificationInterruptSuppressor suppressor) { + mSuppressors.remove(suppressor); + } + + @Override public boolean shouldBubbleUp(NotificationEntry entry) { final StatusBarNotification sbn = entry.getSbn(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt index ebdeded6e329..d7f0baf4f002 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt @@ -58,6 +58,10 @@ class NotificationInterruptStateProviderWrapper( wrapped.addSuppressor(suppressor) } + override fun removeLegacySuppressor(suppressor: NotificationInterruptSuppressor) { + wrapped.removeSuppressor(suppressor) + } + override fun makeUnloggedHeadsUpDecision(entry: NotificationEntry): Decision = wrapped.checkHeadsUp(entry, /* log= */ false).let { DecisionImpl.of(it) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt index 454ba02b2d73..920bbe96b33b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt @@ -60,6 +60,13 @@ interface VisualInterruptionDecisionProvider { fun addLegacySuppressor(suppressor: NotificationInterruptSuppressor) /** + * Removes a [component][suppressor] that can suppress visual interruptions. + * + * @param[suppressor] the suppressor to remove + */ + fun removeLegacySuppressor(suppressor: NotificationInterruptSuppressor) + + /** * Decides whether a [notification][entry] should display as heads-up or not, but does not log * that decision. * diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java index 847d94861401..661768da8479 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java @@ -351,12 +351,21 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView @Override public long performRemoveAnimation(long duration, long delay, float translationDirection, - boolean isHeadsUpAnimation, Runnable onFinishedRunnable, + boolean isHeadsUpAnimation, Runnable onStartedRunnable, Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener) { enableAppearDrawing(true); mIsHeadsUpAnimation = isHeadsUpAnimation; - startAppearAnimation(false /* isAppearing */, translationDirection, - delay, duration, onFinishedRunnable, animationListener); + if (mDrawingAppearAnimation) { + startAppearAnimation(false /* isAppearing */, translationDirection, + delay, duration, onStartedRunnable, onFinishedRunnable, animationListener); + } else { + if (onStartedRunnable != null) { + onStartedRunnable.run(); + } + if (onFinishedRunnable != null) { + onFinishedRunnable.run(); + } + } return 0; } @@ -365,12 +374,14 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView Runnable onFinishRunnable) { enableAppearDrawing(true); mIsHeadsUpAnimation = isHeadsUpAppear; - startAppearAnimation(true /* isAppearing */, isHeadsUpAppear ? 0.0f : -1.0f, delay, - duration, null, null); + if (mDrawingAppearAnimation) { + startAppearAnimation(true /* isAppearing */, isHeadsUpAppear ? 0.0f : -1.0f, delay, + duration, null, null, null); + } } private void startAppearAnimation(boolean isAppearing, float translationDirection, long delay, - long duration, final Runnable onFinishedRunnable, + long duration, final Runnable onStartedRunnable, final Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener) { mAnimationTranslationY = translationDirection * getActualHeight(); cancelAppearAnimation(); @@ -434,6 +445,9 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView @Override public void onAnimationStart(Animator animation) { + if (onStartedRunnable != null) { + onStartedRunnable.run(); + } mRunWithoutInterruptions = true; Configuration.Builder builder = Configuration.Builder .withView(getCujType(isAppearing), ActivatableNotificationView.this); 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 bc570f2cae35..9340b85a743d 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 @@ -2927,6 +2927,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView long delay, float translationDirection, boolean isHeadsUpAnimation, + Runnable onStartedRunnable, Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener) { if (mMenuRow != null && mMenuRow.isMenuVisible()) { @@ -2934,10 +2935,16 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (anim != null) { anim.addListener(new AnimatorListenerAdapter() { @Override + public void onAnimationStart(Animator animation) { + if (onStartedRunnable != null) { + onStartedRunnable.run(); + } + } + @Override public void onAnimationEnd(Animator animation) { ExpandableNotificationRow.super.performRemoveAnimation( duration, delay, translationDirection, isHeadsUpAnimation, - onFinishedRunnable, animationListener); + null, onFinishedRunnable, animationListener); } }); anim.start(); @@ -2945,7 +2952,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } return super.performRemoveAnimation(duration, delay, translationDirection, - isHeadsUpAnimation, onFinishedRunnable, animationListener); + isHeadsUpAnimation, onStartedRunnable, onFinishedRunnable, animationListener); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java index f2f55a87ba3f..6edab4d26d59 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java @@ -69,6 +69,9 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro private boolean mClipToActualHeight = true; private boolean mChangingPosition = false; private ViewGroup mTransientContainer; + + // Needs to be added as transient view when removed from parent, because it's in animation + private boolean mInRemovalAnimation; private boolean mInShelf; private boolean mTransformingInShelf; protected float mContentTransformationAmount; @@ -381,6 +384,7 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro */ public abstract long performRemoveAnimation(long duration, long delay, float translationDirection, boolean isHeadsUpAnimation, + Runnable onStartedRunnable, Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener); @@ -604,6 +608,25 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro } /** + * Add the view to a transient container. + */ + public void addToTransientContainer(ViewGroup container, int index) { + container.addTransientView(this, index); + setTransientContainer(container); + } + + /** + * @return If the view is in a process of removal animation. + */ + public boolean inRemovalAnimation() { + return mInRemovalAnimation; + } + + public void setInRemovalAnimation(boolean inRemovalAnimation) { + mInRemovalAnimation = inRemovalAnimation; + } + + /** * @return true if the group's expansion state is changing, false otherwise. */ public boolean isGroupExpansionChanging() { @@ -837,6 +860,7 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro pw.println(); } if (DUMP_VERBOSE) { + pw.println("mInRemovalAnimation: " + mInRemovalAnimation); pw.println("mClipTopAmount: " + mClipTopAmount); pw.println("mClipBottomAmount " + mClipBottomAmount); pw.println("mClipToActualHeight: " + mClipToActualHeight); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java index e200b901e815..aabf2954f23c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java @@ -237,9 +237,13 @@ public abstract class StackScrollerDecorView extends ExpandableView { @Override public long performRemoveAnimation(long duration, long delay, float translationDirection, boolean isHeadsUpAnimation, + Runnable onStartedRunnable, Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener) { // TODO: Use duration + if (onStartedRunnable != null) { + onStartedRunnable.run(); + } setContentVisible(false, true /* animate */, (cancelled) -> onFinishedRunnable.run()); return 0; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt index 5d46f52dba87..bae5baaf91ed 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt @@ -25,9 +25,7 @@ import android.util.AttributeSet import com.android.systemui.res.R import com.android.systemui.statusbar.notification.row.ExpandableView -/** - * Root view to insert Lock screen media controls into the notification stack. - */ +/** Root view to insert Lock screen media controls into the notification stack. */ class MediaContainerView(context: Context, attrs: AttributeSet?) : ExpandableView(context, attrs) { override var clipHeight = 0 @@ -46,8 +44,8 @@ class MediaContainerView(context: Context, attrs: AttributeSet?) : ExpandableVie } private fun updateResources() { - cornerRadius = context.resources - .getDimensionPixelSize(R.dimen.notification_corner_radius).toFloat() + cornerRadius = + context.resources.getDimensionPixelSize(R.dimen.notification_corner_radius).toFloat() } public override fun updateClipping() { @@ -70,18 +68,23 @@ class MediaContainerView(context: Context, attrs: AttributeSet?) : ExpandableVie } override fun performRemoveAnimation( - duration: Long, - delay: Long, - translationDirection: Float, - isHeadsUpAnimation: Boolean, - onFinishedRunnable: Runnable?, - animationListener: AnimatorListenerAdapter? + duration: Long, + delay: Long, + translationDirection: Float, + isHeadsUpAnimation: Boolean, + onStartedRunnable: Runnable?, + onFinishedRunnable: Runnable?, + animationListener: AnimatorListenerAdapter? ): Long { return 0 } - override fun performAddAnimation(delay: Long, duration: Long, isHeadsUpAppear: Boolean, - onEnd: Runnable?) { + override fun performAddAnimation( + delay: Long, + duration: Long, + isHeadsUpAppear: Boolean, + onEnd: Runnable? + ) { // No animation, it doesn't need it, this would be local } -}
\ No newline at end of file +} 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 6f3cd5d52e1e..79f8f22fd753 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 @@ -20,6 +20,7 @@ import static android.os.Trace.TRACE_TAG_APP; import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING; import static com.android.internal.jank.InteractionJankMonitor.CUJ_SHADE_CLEAR_ALL; +import static com.android.systemui.flags.Flags.UNCLEARED_TRANSIENT_HUN_FIX; import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT; import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_SWIPE; import static com.android.systemui.util.DumpUtilsKt.println; @@ -576,6 +577,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mSplitShadeStateController = splitShadeStateController; updateSplitNotificationShade(); } + private FeatureFlags mFeatureFlags; private final ExpandableView.OnHeightChangedListener mOnChildHeightChangedListener = new ExpandableView.OnHeightChangedListener() { @@ -628,16 +630,16 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable public NotificationStackScrollLayout(Context context, AttributeSet attrs) { super(context, attrs, 0, 0); Resources res = getResources(); - FeatureFlags featureFlags = Dependency.get(FeatureFlags.class); - mIsSmallLandscapeLockscreenEnabled = featureFlags.isEnabled( + mFeatureFlags = Dependency.get(FeatureFlags.class); + mIsSmallLandscapeLockscreenEnabled = mFeatureFlags.isEnabled( Flags.LOCKSCREEN_ENABLE_LANDSCAPE); - mDebugLines = featureFlags.isEnabled(Flags.NSSL_DEBUG_LINES); - mNewAodTransition = featureFlags.isEnabled(Flags.NEW_AOD_TRANSITION); - mDebugRemoveAnimation = featureFlags.isEnabled(Flags.NSSL_DEBUG_REMOVE_ANIMATION); - mSensitiveRevealAnimEndabled = featureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM); + mDebugLines = mFeatureFlags.isEnabled(Flags.NSSL_DEBUG_LINES); + mNewAodTransition = mFeatureFlags.isEnabled(Flags.NEW_AOD_TRANSITION); + mDebugRemoveAnimation = mFeatureFlags.isEnabled(Flags.NSSL_DEBUG_REMOVE_ANIMATION); + mSensitiveRevealAnimEndabled = mFeatureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM); mAnimatedInsets = - new RefactorFlag(featureFlags, Flags.ANIMATED_NOTIFICATION_SHADE_INSETS); - mShelfRefactor = new RefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR); + new RefactorFlag(mFeatureFlags, Flags.ANIMATED_NOTIFICATION_SHADE_INSETS); + mShelfRefactor = new RefactorFlag(mFeatureFlags, Flags.NOTIFICATION_SHELF_REFACTOR); mSectionsManager = Dependency.get(NotificationSectionsManager.class); mScreenOffAnimationController = Dependency.get(ScreenOffAnimationController.class); @@ -2779,8 +2781,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable if (animationGenerated) { if (!mSwipedOutViews.contains(child) || !isFullySwipedOut(child)) { logAddTransientChild(child, container); - container.addTransientView(child, 0); - child.setTransientContainer(container); + child.addToTransientContainer(container, 0); } } else { mSwipedOutViews.remove(child); @@ -2870,7 +2871,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * Generate a remove animation for a child view. * * @param child The view to generate the remove animation for. - * @return Whether an animation was generated. + * @return Whether a new animation was generated or an existing animation was detected by this + * method. We need this to determine if a transient view is needed. */ boolean generateRemoveAnimation(ExpandableView child) { String key = ""; @@ -2887,10 +2889,23 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mAddedHeadsUpChildren.remove(child); return false; } - if (isClickedHeadsUp(child)) { - // An animation is already running, add it transiently - mClearTransientViewsWhenFinished.add(child); - return true; + if (mFeatureFlags.isEnabled(UNCLEARED_TRANSIENT_HUN_FIX)) { + // Skip adding animation for clicked heads up notifications when the + // Shade is closed, because the animation event is generated in + // generateHeadsUpAnimationEvents. Only report that an animation was + // actually generated (thus requesting the transient view be added) + // if a removal animation is in progress. + if (!isExpanded() && isClickedHeadsUp(child)) { + // An animation is already running, add it transiently + mClearTransientViewsWhenFinished.add(child); + return child.inRemovalAnimation(); + } + } else { + if (isClickedHeadsUp(child)) { + // An animation is already running, add it transiently + mClearTransientViewsWhenFinished.add(child); + return true; + } } if (mDebugRemoveAnimation) { Log.d(TAG, "generateRemove " + key diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java index 69453c65f57d..e94258f416ac 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java @@ -16,6 +16,9 @@ package com.android.systemui.statusbar.notification.stack; +import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR; +import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; @@ -346,21 +349,19 @@ public class StackStateAnimator { ArrayList<NotificationStackScrollLayout.AnimationEvent> animationEvents) { boolean needsCustomAnimation = false; for (NotificationStackScrollLayout.AnimationEvent event : animationEvents) { - final ExpandableView changingView = (ExpandableView) event.mChangingView; + final ExpandableView changingView = event.mChangingView; boolean loggable = false; boolean isHeadsUp = false; - boolean isGroupChild = false; String key = null; if (changingView instanceof ExpandableNotificationRow && mLogger != null) { loggable = true; isHeadsUp = ((ExpandableNotificationRow) changingView).isHeadsUp(); - isGroupChild = changingView.isChildInGroup(); key = ((ExpandableNotificationRow) changingView).getEntry().getKey(); } if (event.animationType == NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD) { - // This item is added, initialize it's properties. + // This item is added, initialize its properties. ExpandableViewState viewState = changingView.getViewState(); if (viewState == null || viewState.gone) { // The position for this child was never generated, let's continue. @@ -374,7 +375,11 @@ public class StackStateAnimator { } else if (event.animationType == NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE) { - if (changingView.getVisibility() != View.VISIBLE) { + int changingViewVisibility = changingView.getVisibility(); + if (loggable) { + mLogger.processAnimationEventsRemoval(key, changingViewVisibility, isHeadsUp); + } + if (changingViewVisibility != View.VISIBLE) { changingView.removeFromTransientContainer(); continue; } @@ -410,30 +415,40 @@ public class StackStateAnimator { translationDirection = Math.max(Math.min(translationDirection, 1.0f),-1.0f); } - Runnable postAnimation = changingView::removeFromTransientContainer; + Runnable postAnimation; + Runnable startAnimation; if (loggable) { String finalKey = key; - if (isHeadsUp) { - mLogger.logHUNViewDisappearingWithRemoveEvent(key); - postAnimation = () -> { - mLogger.disappearAnimationEnded(finalKey); - changingView.removeFromTransientContainer(); - }; - } else if (isGroupChild) { - mLogger.groupChildRemovalEventProcessed(key); - postAnimation = () -> { - mLogger.groupChildRemovalAnimationEnded(finalKey); - changingView.removeFromTransientContainer(); - }; - } + final boolean finalIsHeadsHp = isHeadsUp; + startAnimation = () -> { + mLogger.animationStart(finalKey, "ANIMATION_TYPE_REMOVE", finalIsHeadsHp); + changingView.setInRemovalAnimation(true); + }; + postAnimation = () -> { + mLogger.animationEnd(finalKey, "ANIMATION_TYPE_REMOVE", finalIsHeadsHp); + changingView.setInRemovalAnimation(false); + changingView.removeFromTransientContainer(); + }; + } else { + startAnimation = ()-> { + changingView.setInRemovalAnimation(true); + }; + postAnimation = () -> { + changingView.setInRemovalAnimation(false); + changingView.removeFromTransientContainer(); + }; } changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR, 0 /* delay */, translationDirection, false /* isHeadsUpAppear */, - postAnimation, getGlobalAnimationFinishedListener()); + startAnimation, postAnimation, getGlobalAnimationFinishedListener()); needsCustomAnimation = true; } else if (event.animationType == NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) { - if (mHostLayout.isFullySwipedOut(changingView)) { + boolean isFullySwipedOut = mHostLayout.isFullySwipedOut(changingView); + if (loggable) { + mLogger.processAnimationEventsRemoveSwipeOut(key, isFullySwipedOut, isHeadsUp); + } + if (isFullySwipedOut) { changingView.removeFromTransientContainer(); } } else if (event.animationType == NotificationStackScrollLayout @@ -442,7 +457,7 @@ public class StackStateAnimator { row.prepareExpansionChanged(); } else if (event.animationType == NotificationStackScrollLayout .AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR) { - // This item is added, initialize it's properties. + // This item is added, initialize its properties. ExpandableViewState viewState = changingView.getViewState(); mTmpState.copyFrom(viewState); if (event.headsUpFromBottom) { @@ -464,22 +479,23 @@ public class StackStateAnimator { } mTmpState.applyToView(changingView); - } else if (event.animationType == NotificationStackScrollLayout - .AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR || - event.animationType == NotificationStackScrollLayout - .AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK) { + } else if (event.animationType == ANIMATION_TYPE_HEADS_UP_DISAPPEAR + || event.animationType == ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK) { mHeadsUpDisappearChildren.add(changingView); Runnable endRunnable = null; if (changingView.getParent() == null) { - // This notification was actually removed, so we need to add it transiently + // This notification was actually removed, so we need to add it + // transiently mHostLayout.addTransientView(changingView, 0); changingView.setTransientContainer(mHostLayout); mTmpState.initFrom(changingView); endRunnable = changingView::removeFromTransientContainer; } + boolean needsAnimation = true; if (changingView instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) changingView; + ExpandableNotificationRow row = + (ExpandableNotificationRow) changingView; if (row.isDismissed()) { needsAnimation = false; } @@ -488,21 +504,43 @@ public class StackStateAnimator { // We need to add the global animation listener, since once no animations are // running anymore, the panel will instantly hide itself. We need to wait until // the animation is fully finished for this though. - Runnable postAnimation = endRunnable; + final Runnable tmpEndRunnable = endRunnable; + Runnable postAnimation; + Runnable startAnimation; if (loggable) { - mLogger.logHUNViewDisappearing(key); - - Runnable finalEndRunnable = endRunnable; String finalKey1 = key; + final boolean finalIsHeadsUp = isHeadsUp; + final String type = + event.animationType == ANIMATION_TYPE_HEADS_UP_DISAPPEAR + ? "ANIMATION_TYPE_HEADS_UP_DISAPPEAR" + : "ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK"; + startAnimation = () -> { + mLogger.animationStart(finalKey1, type, finalIsHeadsUp); + changingView.setInRemovalAnimation(true); + }; + postAnimation = () -> { + mLogger.animationEnd(finalKey1, type, finalIsHeadsUp); + changingView.setInRemovalAnimation(false); + if (tmpEndRunnable != null) { + tmpEndRunnable.run(); + } + }; + } else { postAnimation = () -> { - mLogger.disappearAnimationEnded(finalKey1); - if (finalEndRunnable != null) finalEndRunnable.run(); + changingView.setInRemovalAnimation(false); + if (tmpEndRunnable != null) { + tmpEndRunnable.run(); + } + }; + startAnimation = () -> { + changingView.setInRemovalAnimation(true); }; } long removeAnimationDelay = changingView.performRemoveAnimation( ANIMATION_DURATION_HEADS_UP_DISAPPEAR, 0, 0.0f, true /* isHeadsUpAppear */, - postAnimation, getGlobalAnimationFinishedListener()); + startAnimation, postAnimation, + getGlobalAnimationFinishedListener()); mAnimationProperties.delay += removeAnimationDelay; } else if (endRunnable != null) { endRunnable.run(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt index 0b2c4863157c..d635f8938491 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt @@ -5,74 +5,104 @@ import com.android.systemui.log.core.LogLevel import com.android.systemui.log.dagger.NotificationHeadsUpLog import com.android.systemui.log.dagger.NotificationRenderLog import com.android.systemui.statusbar.notification.logKey +import com.android.systemui.util.visibilityString import javax.inject.Inject -class StackStateLogger @Inject constructor( +class StackStateLogger +@Inject +constructor( @NotificationHeadsUpLog private val buffer: LogBuffer, @NotificationRenderLog private val notificationRenderBuffer: LogBuffer ) { - fun logHUNViewDisappearing(key: String) { - buffer.log(TAG, LogLevel.INFO, { - str1 = logKey(key) - }, { - "Heads up view disappearing $str1 " - }) - } fun logHUNViewAppearing(key: String) { - buffer.log(TAG, LogLevel.INFO, { - str1 = logKey(key) - }, { - "Heads up notification view appearing $str1 " - }) + buffer.log( + TAG, + LogLevel.INFO, + { str1 = logKey(key) }, + { "Heads up notification view appearing $str1 " } + ) } - fun logHUNViewDisappearingWithRemoveEvent(key: String) { - buffer.log(TAG, LogLevel.ERROR, { - str1 = logKey(key) - }, { - "Heads up view disappearing $str1 for ANIMATION_TYPE_REMOVE" - }) + fun logHUNViewAppearingWithAddEvent(key: String) { + buffer.log( + TAG, + LogLevel.ERROR, + { str1 = logKey(key) }, + { "Heads up view disappearing $str1 for ANIMATION_TYPE_ADD" } + ) } - fun logHUNViewAppearingWithAddEvent(key: String) { - buffer.log(TAG, LogLevel.ERROR, { - str1 = logKey(key) - }, { - "Heads up view disappearing $str1 for ANIMATION_TYPE_ADD" - }) + fun appearAnimationEnded(key: String) { + buffer.log( + TAG, + LogLevel.INFO, + { str1 = logKey(key) }, + { "Heads up notification appear animation ended $str1 " } + ) } - fun disappearAnimationEnded(key: String) { - buffer.log(TAG, LogLevel.INFO, { - str1 = logKey(key) - }, { - "Heads up notification disappear animation ended $str1 " - }) + fun processAnimationEventsRemoval(key: String, visibility: Int, isHeadsUp: Boolean) { + notificationRenderBuffer.log( + TAG, + LogLevel.INFO, + { + str1 = logKey(key) + int1 = visibility + bool1 = isHeadsUp + }, + { + "ProcessAnimationEvents ANIMATION_TYPE_REMOVE for: $str1, " + + "changingViewVisibility: ${visibilityString(int1)}, isHeadsUp: $bool1" + } + ) } - fun appearAnimationEnded(key: String) { - buffer.log(TAG, LogLevel.INFO, { - str1 = logKey(key) - }, { - "Heads up notification appear animation ended $str1 " - }) + fun processAnimationEventsRemoveSwipeOut( + key: String, + isFullySwipedOut: Boolean, + isHeadsUp: Boolean + ) { + notificationRenderBuffer.log( + TAG, + LogLevel.INFO, + { + str1 = logKey(key) + bool1 = isFullySwipedOut + bool2 = isHeadsUp + }, + { + "ProcessAnimationEvents ANIMATION_TYPE_REMOVE_SWIPED_OUT for: $str1, " + + "isFullySwipedOut: $bool1, isHeadsUp: $bool2" + } + ) } - fun groupChildRemovalEventProcessed(key: String) { - notificationRenderBuffer.log(TAG, LogLevel.DEBUG, { - str1 = logKey(key) - }, { - "Group Child Notification removal event processed $str1 for ANIMATION_TYPE_REMOVE" - }) + fun animationStart(key: String?, animationType: String, isHeadsUp: Boolean) { + notificationRenderBuffer.log( + TAG, + LogLevel.INFO, + { + str1 = logKey(key) + str2 = animationType + bool1 = isHeadsUp + }, + { "Animation Start, type: $str2, notif key: $str1, isHeadsUp: $bool1" } + ) } - fun groupChildRemovalAnimationEnded(key: String) { - notificationRenderBuffer.log(TAG, LogLevel.INFO, { - str1 = logKey(key) - }, { - "Group child notification removal animation ended $str1 " - }) + + fun animationEnd(key: String, animationType: String, isHeadsUp: Boolean) { + notificationRenderBuffer.log( + TAG, + LogLevel.INFO, + { + str1 = logKey(key) + str2 = animationType + bool1 = isHeadsUp + }, + { "Animation End, type: $str2, notif key: $str1, isHeadsUp: $bool1" } + ) } } -private const val TAG = "StackScroll"
\ No newline at end of file +private const val TAG = "StackScroll" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java index 697d2978db0c..3877bb6660ba 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java @@ -28,15 +28,15 @@ import android.os.UserHandle; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; -import com.android.systemui.res.R; import com.android.systemui.dagger.NightDisplayListenerModule; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.qs.AutoAddTracker; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.ReduceBrightColorsController; -import com.android.systemui.qs.SettingObserver; +import com.android.systemui.qs.UserSettingObserver; import com.android.systemui.qs.external.CustomTile; +import com.android.systemui.res.R; import com.android.systemui.statusbar.policy.CastController; import com.android.systemui.statusbar.policy.CastController.CastDevice; import com.android.systemui.statusbar.policy.DataSaverController; @@ -461,8 +461,8 @@ public class AutoTileManager implements UserAwareController { }; @VisibleForTesting - protected SettingObserver getSecureSettingForKey(String key) { - for (SettingObserver s : mAutoAddSettingList) { + protected UserSettingObserver getSecureSettingForKey(String key) { + for (UserSettingObserver s : mAutoAddSettingList) { if (Objects.equals(key, s.getKey())) { return s; } @@ -476,7 +476,7 @@ public class AutoTileManager implements UserAwareController { * When the setting changes to a value different from 0, if the tile has not been auto added * before, it will be added and the listener will be stopped. */ - private class AutoAddSetting extends SettingObserver { + private class AutoAddSetting extends UserSettingObserver { private final String mSpec; AutoAddSetting( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index f380ce53b950..9fb6c1bb2fd0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -312,8 +312,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { }; void onStatusBarWindowStateChanged(@WindowVisibleState int state) { - updateBubblesVisibility(); mStatusBarWindowState = state; + updateBubblesVisibility(); } @Override @@ -1088,6 +1088,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { * @deprecated use {@link * WindowRootViewVisibilityInteractor.isLockscreenOrShadeVisible} instead. */ @VisibleForTesting + @Deprecated void initShadeVisibilityListener() { mShadeController.setVisibilityListener(new ShadeController.ShadeVisibilityListener() { @Override @@ -1725,7 +1726,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { StatusBarMode mode = mStatusBarModeRepository.getStatusBarMode().getValue(); mBubblesOptional.ifPresent(bubbles -> bubbles.onStatusBarVisibilityChanged( mode != StatusBarMode.LIGHTS_OUT - && mode != StatusBarMode.LIGHTS_OUT_TRANSPARENT)); + && mode != StatusBarMode.LIGHTS_OUT_TRANSPARENT + && mStatusBarWindowState != WINDOW_STATE_HIDDEN)); } void checkBarMode( @@ -2602,7 +2604,10 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mShouldDelayWakeUpAnimation); updateIsKeyguard(); + // TODO(b/301913237): can't delay transition if config_displayBlanksAfterDoze=true, + // otherwise, the clock will flicker during LOCKSCREEN_TRANSITION_FROM_AOD mShouldDelayLockscreenTransitionFromAod = mDozeParameters.getAlwaysOn() + && !mDozeParameters.getDisplayNeedsBlanking() && mFeatureFlags.isEnabled( Flags.ZJ_285570694_LOCKSCREEN_TRANSITION_FROM_AOD); if (!mShouldDelayLockscreenTransitionFromAod) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java index 6b4382f731ea..407148faeb30 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java @@ -34,7 +34,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; import com.android.systemui.res.R; -import com.android.systemui.shade.ShadeExpansionStateManager; +import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener; @@ -48,6 +48,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.HeadsUpManagerLogger; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import com.android.systemui.statusbar.policy.OnHeadsUpPhoneListenerChange; +import com.android.systemui.util.kotlin.JavaAdapter; import java.io.PrintWriter; import java.util.ArrayList; @@ -105,7 +106,8 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements OnHeadsUp /////////////////////////////////////////////////////////////////////////////////////////////// // Constructor: @Inject - public HeadsUpManagerPhone(@NonNull final Context context, + public HeadsUpManagerPhone( + @NonNull final Context context, HeadsUpManagerLogger logger, StatusBarStateController statusBarStateController, KeyguardBypassController bypassController, @@ -115,7 +117,8 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements OnHeadsUp @Main Handler handler, AccessibilityManagerWrapper accessibilityManagerWrapper, UiEventLogger uiEventLogger, - ShadeExpansionStateManager shadeExpansionStateManager) { + JavaAdapter javaAdapter, + ShadeInteractor shadeInteractor) { super(context, logger, handler, accessibilityManagerWrapper, uiEventLogger); Resources resources = mContext.getResources(); mExtensionTime = resources.getInteger(R.integer.ambient_notification_extension_time); @@ -136,8 +139,7 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements OnHeadsUp updateResources(); } }); - - shadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged); + javaAdapter.alwaysCollectFlow(shadeInteractor.isAnyExpanded(), this::onShadeOrQsExpanded); } public void setAnimationStateHandler(AnimationStateHandler handler) { @@ -230,7 +232,7 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements OnHeadsUp mTrackingHeadsUp = trackingHeadsUp; } - private void onShadeExpansionFullyChanged(Boolean isExpanded) { + private void onShadeOrQsExpanded(Boolean isExpanded) { if (isExpanded != mIsExpanded) { mIsExpanded = isExpanded; if (isExpanded) { 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/StatusBarHideIconsForBouncerManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt index 4d9de09fded4..fa6d2797a37e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt @@ -3,12 +3,15 @@ package com.android.systemui.statusbar.phone import android.app.StatusBarManager import com.android.systemui.Dumpable 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.shade.ShadeExpansionStateManager +import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.window.StatusBarWindowStateController import com.android.systemui.util.concurrency.DelayableExecutor +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch import java.io.PrintWriter import javax.inject.Inject @@ -25,14 +28,14 @@ import javax.inject.Inject */ @SysUISingleton class StatusBarHideIconsForBouncerManager @Inject constructor( - private val commandQueue: CommandQueue, - @Main private val mainExecutor: DelayableExecutor, - statusBarWindowStateController: StatusBarWindowStateController, - shadeExpansionStateManager: ShadeExpansionStateManager, - dumpManager: DumpManager + @Application private val scope: CoroutineScope, + private val commandQueue: CommandQueue, + @Main private val mainExecutor: DelayableExecutor, + statusBarWindowStateController: StatusBarWindowStateController, + val shadeInteractor: ShadeInteractor, + dumpManager: DumpManager ) : Dumpable { // State variables set by external classes. - private var panelExpanded: Boolean = false private var isOccluded: Boolean = false private var bouncerShowing: Boolean = false private var topAppHidesStatusBar: Boolean = false @@ -49,10 +52,9 @@ class StatusBarHideIconsForBouncerManager @Inject constructor( statusBarWindowStateController.addListener { state -> setStatusBarStateAndTriggerUpdate(state) } - shadeExpansionStateManager.addFullExpansionListener { isExpanded -> - if (panelExpanded != isExpanded) { - panelExpanded = isExpanded - updateHideIconsForBouncer(animate = false) + scope.launch { + shadeInteractor.isAnyExpanded.collect { + updateHideIconsForBouncer(false) } } } @@ -101,7 +103,7 @@ class StatusBarHideIconsForBouncerManager @Inject constructor( topAppHidesStatusBar && isOccluded && (statusBarWindowHidden || bouncerShowing) - val hideBecauseKeyguard = !panelExpanded && !isOccluded && bouncerShowing + val hideBecauseKeyguard = !isShadeOrQsExpanded() && !isOccluded && bouncerShowing val shouldHideIconsForBouncer = hideBecauseApp || hideBecauseKeyguard if (hideIconsForBouncer != shouldHideIconsForBouncer) { hideIconsForBouncer = shouldHideIconsForBouncer @@ -125,9 +127,13 @@ class StatusBarHideIconsForBouncerManager @Inject constructor( } } + private fun isShadeOrQsExpanded(): Boolean { + return shadeInteractor.isAnyExpanded.value + } + override fun dump(pw: PrintWriter, args: Array<out String>) { pw.println("---- State variables set externally ----") - pw.println("panelExpanded=$panelExpanded") + pw.println("isShadeOrQsExpanded=${isShadeOrQsExpanded()}") pw.println("isOccluded=$isOccluded") pw.println("bouncerShowing=$bouncerShowing") pw.println("topAppHideStatusBar=$topAppHidesStatusBar") 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 62b2445de13a..3adf3385e3cc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -296,7 +296,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb final Set<KeyguardViewManagerCallback> mCallbacks = new HashSet<>(); private boolean mIsBackAnimationEnabled; - private final boolean mUdfpsNewTouchDetectionEnabled; private final UdfpsOverlayInteractor mUdfpsOverlayInteractor; private final ActivityStarter mActivityStarter; @@ -398,7 +397,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mAlternateBouncerInteractor = alternateBouncerInteractor; mIsBackAnimationEnabled = featureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_BOUNCER_ANIM); - mUdfpsNewTouchDetectionEnabled = featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION); mUdfpsOverlayInteractor = udfpsOverlayInteractor; mActivityStarter = activityStarter; mKeyguardTransitionInteractor = keyguardTransitionInteractor; @@ -1594,7 +1592,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb final boolean actionDownThenUp = mAlternateBouncerInteractor.getReceivedDownTouch() && event.getActionMasked() == MotionEvent.ACTION_UP; final boolean udfpsOverlayWillForwardEventsOutsideNotificationShade = - mUdfpsNewTouchDetectionEnabled && mKeyguardUpdateManager.isUdfpsEnrolled(); + mKeyguardUpdateManager.isUdfpsEnrolled(); final boolean actionOutsideShouldDismissAlternateBouncer = event.getActionMasked() == MotionEvent.ACTION_OUTSIDE && !udfpsOverlayWillForwardEventsOutsideNotificationShade; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java index ba73c1098637..6d8ec44ad55e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java @@ -39,6 +39,7 @@ import com.android.systemui.res.R; import com.android.systemui.scene.domain.interactor.SceneInteractor; import com.android.systemui.scene.shared.flag.SceneContainerFlags; import com.android.systemui.shade.ShadeExpansionStateManager; +import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; @@ -86,8 +87,9 @@ public final class StatusBarTouchableRegionManager implements Dumpable { ConfigurationController configurationController, HeadsUpManager headsUpManager, ShadeExpansionStateManager shadeExpansionStateManager, + ShadeInteractor shadeInteractor, Provider<SceneInteractor> sceneInteractor, - Provider<JavaAdapter> javaAdapter, + JavaAdapter javaAdapter, SceneContainerFlags sceneContainerFlags, UnlockedScreenOffAnimationController unlockedScreenOffAnimationController, PrimaryBouncerInteractor primaryBouncerInteractor, @@ -126,12 +128,12 @@ public final class StatusBarTouchableRegionManager implements Dumpable { }); mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; - shadeExpansionStateManager.addFullExpansionListener(this::onShadeExpansionFullyChanged); + javaAdapter.alwaysCollectFlow(shadeInteractor.isAnyExpanded(), this::onShadeOrQsExpanded); if (sceneContainerFlags.isEnabled()) { - javaAdapter.get().alwaysCollectFlow( + javaAdapter.alwaysCollectFlow( sceneInteractor.get().isVisible(), - this::onShadeExpansionFullyChanged); + this::onShadeOrQsExpanded); } mPrimaryBouncerInteractor = primaryBouncerInteractor; @@ -151,7 +153,7 @@ public final class StatusBarTouchableRegionManager implements Dumpable { pw.println(mTouchableRegion); } - private void onShadeExpansionFullyChanged(Boolean isExpanded) { + private void onShadeOrQsExpanded(Boolean isExpanded) { if (isExpanded != mIsStatusBarExpanded) { mIsStatusBarExpanded = isExpanded; if (isExpanded) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java index 6dc8065b2822..da91d6a05d6b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java @@ -15,6 +15,9 @@ */ package com.android.systemui.statusbar.phone; +import com.android.systemui.util.annotations.WeaklyReferencedCallback; + +@WeaklyReferencedCallback public interface StatusBarWindowCallback { /** * Invoked when the internal state of NotificationShadeWindowControllerImpl changes. 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/pipeline/airplane/data/repository/AirplaneModeRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt index 8ff9198da119..8862c77c9c4a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepository.kt @@ -17,7 +17,6 @@ package com.android.systemui.statusbar.pipeline.airplane.data.repository import android.os.Handler -import android.os.UserHandle import android.provider.Settings.Global import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton @@ -66,13 +65,7 @@ constructor( override val isAirplaneMode: StateFlow<Boolean> = conflatedCallbackFlow { val observer = - object : - SettingObserver( - globalSettings, - bgHandler, - Global.AIRPLANE_MODE_ON, - UserHandle.USER_ALL - ) { + object : SettingObserver(globalSettings, bgHandler, Global.AIRPLANE_MODE_ON) { override fun handleValueChanged(value: Int, observedChange: Boolean) { trySend(value == 1) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java index 945cc6bc2519..53b343c09329 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java @@ -27,6 +27,7 @@ import android.os.UserHandle; import android.os.UserManager; import androidx.annotation.NonNull; +import androidx.annotation.WorkerThread; import com.android.internal.annotations.GuardedBy; import com.android.settingslib.bluetooth.BluetoothCallback; @@ -36,6 +37,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothProfile; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import com.android.systemui.bluetooth.BluetoothLogger; 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.settings.UserTracker; @@ -81,6 +83,8 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa private int mState; private final BluetoothAdapter mAdapter; + + private final Executor mBackgroundExecutor; /** */ @Inject @@ -90,6 +94,7 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa DumpManager dumpManager, BluetoothLogger logger, BluetoothRepository bluetoothRepository, + @Background Executor executor, @Main Looper mainLooper, @Nullable LocalBluetoothManager localBluetoothManager, @Nullable BluetoothAdapter bluetoothAdapter) { @@ -98,6 +103,7 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa mBluetoothRepository = bluetoothRepository; mLocalBluetoothManager = localBluetoothManager; mHandler = new H(mainLooper); + mBackgroundExecutor = executor; if (mLocalBluetoothManager != null) { mLocalBluetoothManager.getEventManager().registerCallback(this); mLocalBluetoothManager.getProfileManager().addServiceListener(this); @@ -218,6 +224,7 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa return mIsActive; } + @WorkerThread @Override public void setBluetoothEnabled(boolean enabled) { if (mLocalBluetoothManager != null) { @@ -230,6 +237,7 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa return mLocalBluetoothManager != null; } + @WorkerThread @Override public String getConnectedDeviceName() { synchronized (mConnectedDevices) { @@ -251,6 +259,7 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa getDevices(), this::onConnectionStatusFetched); } + // Careful! This may be invoked in the main thread. private void onConnectionStatusFetched(ConnectionStatusModel status) { List<CachedBluetoothDevice> newList = status.getConnectedDevices(); int state = status.getMaxConnectionState(); @@ -282,30 +291,33 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa } private void updateAudioProfile() { - boolean audioProfileConnected = false; - boolean otherProfileConnected = false; - - for (CachedBluetoothDevice device : getDevices()) { - for (LocalBluetoothProfile profile : device.getProfiles()) { - int profileId = profile.getProfileId(); - boolean isConnected = device.isConnectedProfile(profile); - if (profileId == BluetoothProfile.HEADSET - || profileId == BluetoothProfile.A2DP - || profileId == BluetoothProfile.HEARING_AID - || profileId == BluetoothProfile.LE_AUDIO) { - audioProfileConnected |= isConnected; - } else { - otherProfileConnected |= isConnected; + // We want this in the background as calls inside `LocalBluetoothProfile` end up being + // binder calls + mBackgroundExecutor.execute(() -> { + boolean audioProfileConnected = false; + boolean otherProfileConnected = false; + + for (CachedBluetoothDevice device : getDevices()) { + for (LocalBluetoothProfile profile : device.getProfiles()) { + int profileId = profile.getProfileId(); + boolean isConnected = device.isConnectedProfile(profile); + if (profileId == BluetoothProfile.HEADSET + || profileId == BluetoothProfile.A2DP + || profileId == BluetoothProfile.HEARING_AID + || profileId == BluetoothProfile.LE_AUDIO) { + audioProfileConnected |= isConnected; + } else { + otherProfileConnected |= isConnected; + } } } - } - - boolean audioProfileOnly = (audioProfileConnected && !otherProfileConnected); - if (audioProfileOnly != mAudioProfileOnly) { - mAudioProfileOnly = audioProfileOnly; - mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED); - } + boolean audioProfileOnly = (audioProfileConnected && !otherProfileConnected); + if (audioProfileOnly != mAudioProfileOnly) { + mAudioProfileOnly = audioProfileOnly; + mHandler.sendEmptyMessage(H.MSG_STATE_CHANGED); + } + }); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt index 8c61ada3f8ef..8b20283749c5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt @@ -62,7 +62,7 @@ open class DeviceProvisionedControllerImpl @Inject constructor( } private val deviceProvisionedUri = globalSettings.getUriFor(Settings.Global.DEVICE_PROVISIONED) - private val frpActiveUri = secureSettings.getUriFor(Settings.Secure.SECURE_FRP_MODE) + private val frpActiveUri = globalSettings.getUriFor(Settings.Global.SECURE_FRP_MODE) private val userSetupUri = secureSettings.getUriFor(Settings.Secure.USER_SETUP_COMPLETE) private val deviceProvisioned = AtomicBoolean(false) @@ -148,7 +148,7 @@ open class DeviceProvisionedControllerImpl @Inject constructor( .set(globalSettings.getInt(Settings.Global.DEVICE_PROVISIONED, 0) != 0) } if (updateFrp) { - frpActive.set(globalSettings.getInt(Settings.Secure.SECURE_FRP_MODE, 0) != 0) + frpActive.set(globalSettings.getInt(Settings.Global.SECURE_FRP_MODE, 0) != 0) } synchronized(lock) { if (updateUser == ALL_USERS) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepository.kt index 80f3d76f0897..96717c7542d5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepository.kt @@ -38,7 +38,8 @@ interface BluetoothRepository { /** * Fetches the connection statuses for the given [currentDevices] and invokes [callback] once * those statuses have been fetched. The fetching occurs on a background thread because IPCs may - * be required to fetch the statuses (see b/271058380). + * be required to fetch the statuses (see b/271058380). However, the callback will be invoked in + * the main thread. */ fun fetchConnectionStatusInBackground( currentDevices: Collection<CachedBluetoothDevice>, diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt index f40454915ee3..5fc435aae67f 100644 --- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt @@ -23,7 +23,6 @@ import android.os.UserHandle import android.os.UserManager import android.provider.Settings import androidx.annotation.VisibleForTesting -import com.android.systemui.res.R import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton @@ -31,6 +30,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlags +import com.android.systemui.res.R import com.android.systemui.settings.UserTracker import com.android.systemui.user.data.model.SelectedUserModel import com.android.systemui.user.data.model.SelectionStatus @@ -132,7 +132,6 @@ constructor( Settings.Global.ADD_USERS_WHEN_LOCKED, Settings.Global.USER_SWITCHER_ENABLED, ), - userId = UserHandle.USER_SYSTEM, ) .onStart { emit(Unit) } // Forces an initial update. .map { getSettings() } @@ -247,7 +246,7 @@ constructor( private suspend fun getSettings(): UserSwitcherSettingsModel { return withContext(backgroundDispatcher) { val isSimpleUserSwitcher = - globalSettings.getIntForUser( + globalSettings.getInt( SETTING_SIMPLE_USER_SWITCHER, if ( appContext.resources.getBoolean( @@ -258,18 +257,16 @@ constructor( } else { 0 }, - UserHandle.USER_SYSTEM, ) != 0 val isAddUsersFromLockscreen = - globalSettings.getIntForUser( + globalSettings.getInt( Settings.Global.ADD_USERS_WHEN_LOCKED, 0, - UserHandle.USER_SYSTEM, ) != 0 val isUserSwitcherEnabled = - globalSettings.getIntForUser( + globalSettings.getInt( Settings.Global.USER_SWITCHER_ENABLED, if ( appContext.resources.getBoolean( @@ -280,7 +277,6 @@ constructor( } else { 0 }, - UserHandle.USER_SYSTEM, ) != 0 UserSwitcherSettingsModel( diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java index 760fe6a96fda..f5edb7bb5b73 100644 --- a/packages/SystemUI/src/com/android/systemui/util/Utils.java +++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java @@ -42,6 +42,7 @@ public class Utils { * list, then list.get(i) could throw an IndexOutOfBoundsException. This method should not be * used; try using `synchronized` or making a copy of the list instead. */ + @Deprecated public static <T> void safeForeach(List<T> list, Consumer<T> c) { for (int i = list.size() - 1; i >= 0; i--) { T item = list.get(i); diff --git a/packages/SystemUI/src/com/android/systemui/util/annotations/WeaklyReferencedCallback.java b/packages/SystemUI/src/com/android/systemui/util/annotations/WeaklyReferencedCallback.java new file mode 100644 index 000000000000..855bba6cfd24 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/annotations/WeaklyReferencedCallback.java @@ -0,0 +1,35 @@ +/* + * 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.util.annotations; + +import static java.lang.annotation.ElementType.TYPE; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +/** + * Descriptive annotation for clearly tagging callback types that are weakly + * referenced during registration. + * + * This is useful in providing hints to Proguard about certain fields that + * should be kept to preserve strong references. + */ +@Retention(RetentionPolicy.CLASS) +@Target({TYPE}) +public @interface WeaklyReferencedCallback {} diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt index 73e2f97d92ae..ffbc10aa5f59 100644 --- a/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt @@ -19,6 +19,7 @@ package com.android.systemui.util.kotlin class Utils { companion object { fun <A, B, C> toTriple(a: A, bc: Pair<B, C>) = Triple(a, bc.first, bc.second) + fun <A, B, C> toTriple(ab: Pair<A, B>, c: C) = Triple(ab.first, ab.second, c) fun <A, B, C, D> toQuad(a: A, b: B, c: C, d: D) = Quad(a, b, c, d) fun <A, B, C, D> toQuad(a: A, bcd: Triple<B, C, D>) = diff --git a/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java b/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java index 968dcc95ef50..df5162af70c5 100644 --- a/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java +++ b/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java @@ -26,6 +26,7 @@ import android.util.Log; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.settings.UserTracker; +import com.android.systemui.util.annotations.WeaklyReferencedCallback; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -64,6 +65,7 @@ public class ObservableServiceConnection<T> implements ServiceConnection { * An interface for listening to the connection status. * @param <T> The wrapper type. */ + @WeaklyReferencedCallback public interface Callback<T> { /** * Invoked when the service has been successfully connected to. diff --git a/packages/SystemUI/src/com/android/systemui/util/service/Observer.java b/packages/SystemUI/src/com/android/systemui/util/service/Observer.java index 768743217cc7..425336d540f5 100644 --- a/packages/SystemUI/src/com/android/systemui/util/service/Observer.java +++ b/packages/SystemUI/src/com/android/systemui/util/service/Observer.java @@ -16,6 +16,8 @@ package com.android.systemui.util.service; +import com.android.systemui.util.annotations.WeaklyReferencedCallback; + /** * The {@link Observer} interface specifies an entity which listeners * can be informed of changes to the source, which will require updating. Note that this deals @@ -25,6 +27,7 @@ public interface Observer { /** * Callback for receiving updates from the {@link Observer}. */ + @WeaklyReferencedCallback interface Callback { /** * Invoked when the source has changed. diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java b/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java index 85fada20a7ad..42389f0ae627 100644 --- a/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java @@ -16,22 +16,23 @@ package com.android.systemui.util.settings; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.content.ContentResolver; import android.net.Uri; import android.provider.Settings; -import com.android.systemui.settings.UserTracker; - import javax.inject.Inject; +// use UserHandle.USER_SYSTEM everywhere +@SuppressLint("StaticSettingsProvider") class GlobalSettingsImpl implements GlobalSettings { private final ContentResolver mContentResolver; - private final UserTracker mUserTracker; @Inject - GlobalSettingsImpl(ContentResolver contentResolver, UserTracker userTracker) { + GlobalSettingsImpl(ContentResolver contentResolver) { mContentResolver = contentResolver; - mUserTracker = userTracker; } @Override @@ -40,43 +41,23 @@ class GlobalSettingsImpl implements GlobalSettings { } @Override - public UserTracker getUserTracker() { - return mUserTracker; - } - - @Override public Uri getUriFor(String name) { return Settings.Global.getUriFor(name); } @Override - public String getStringForUser(String name, int userHandle) { - return Settings.Global.getStringForUser(mContentResolver, name, - getRealUserHandle(userHandle)); - } - - @Override - public boolean putString(String name, String value, boolean overrideableByRestore) { - throw new UnsupportedOperationException( - "This method only exists publicly for Settings.System and Settings.Secure"); - } - - @Override - public boolean putStringForUser(String name, String value, int userHandle) { - return Settings.Global.putStringForUser(mContentResolver, name, value, - getRealUserHandle(userHandle)); + public String getString(String name) { + return Settings.Global.getString(mContentResolver, name); } @Override - public boolean putStringForUser(String name, String value, String tag, boolean makeDefault, - int userHandle, boolean overrideableByRestore) { - return Settings.Global.putStringForUser( - mContentResolver, name, value, tag, makeDefault, getRealUserHandle(userHandle), - overrideableByRestore); + public boolean putString(String name, String value) { + return Settings.Global.putString(mContentResolver, name, value); } @Override - public boolean putString(String name, String value, String tag, boolean makeDefault) { + public boolean putString(@NonNull String name, @Nullable String value, @Nullable String tag, + boolean makeDefault) { return Settings.Global.putString(mContentResolver, name, value, tag, makeDefault); } } diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettings.java b/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettings.java index 798033e841d5..6031a4e05629 100644 --- a/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettings.java +++ b/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettings.java @@ -22,5 +22,5 @@ package com.android.systemui.util.settings; * See {@link SettingsProxy} for details. */ -public interface SecureSettings extends SettingsProxy { +public interface SecureSettings extends UserSettingsProxy { } diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java b/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java index f995436594b1..6532ce8ddf7d 100644 --- a/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java @@ -20,6 +20,8 @@ import android.content.ContentResolver; import android.net.Uri; import android.provider.Settings; +import androidx.annotation.NonNull; + import com.android.systemui.settings.UserTracker; import javax.inject.Inject; @@ -75,7 +77,7 @@ class SecureSettingsImpl implements SecureSettings { } @Override - public boolean putString(String name, String value, String tag, boolean makeDefault) { + public boolean putString(@NonNull String name, String value, String tag, boolean makeDefault) { return Settings.Secure.putString(mContentResolver, name, value, tag, makeDefault); } } diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java index b6846a34a0bd..6a9edc11add0 100644 --- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java +++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java @@ -18,25 +18,22 @@ package com.android.systemui.util.settings; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.UserIdInt; import android.content.ContentResolver; import android.database.ContentObserver; import android.net.Uri; -import android.os.UserHandle; import android.provider.Settings; -import com.android.systemui.settings.UserTracker; - /** - * Used to interact with Settings.Secure, Settings.Global, and Settings.System. - * + * Used to interact with mainly with Settings.Global, but can also be used for Settings.System + * and Settings.Secure. To use the per-user System and Secure settings, {@link UserSettingsProxy} + * must be used instead. + * <p> * This interface can be implemented to give instance method (instead of static method) versions - * of Settings.Secure, Settings.Global, and Settings.System. It can be injected into class - * constructors and then faked or mocked as needed in tests. - * - * You can ask for {@link SecureSettings}, {@link GlobalSettings}, or {@link SystemSettings} to be - * injected as needed. - * + * of Settings.Global. It can be injected into class constructors and then faked or mocked as needed + * in tests. + * <p> + * You can ask for {@link GlobalSettings} to be injected as needed. + * <p> * This class also provides {@link #registerContentObserver(String, ContentObserver)} methods, * normally found on {@link ContentResolver} instances, unifying setting related actions in one * place. @@ -49,29 +46,6 @@ public interface SettingsProxy { ContentResolver getContentResolver(); /** - * Returns that {@link UserTracker} this instance was constructed with. - */ - UserTracker getUserTracker(); - - /** - * Returns the user id for the associated {@link ContentResolver}. - */ - default int getUserId() { - return getContentResolver().getUserId(); - } - - /** - * Returns the actual current user handle when querying with the current user. Otherwise, - * returns the passed in user id. - */ - default int getRealUserHandle(int userHandle) { - if (userHandle != UserHandle.USER_CURRENT) { - return userHandle; - } - return getUserTracker().getUserId(); - } - - /** * Construct the content URI for a particular name/value pair, * useful for monitoring changes with a ContentObserver. * @param name to look up in the table @@ -82,7 +56,7 @@ public interface SettingsProxy { /** * Convenience wrapper around * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.' - * + * <p> * Implicitly calls {@link #getUriFor(String)} on the passed in name. */ default void registerContentObserver(String name, ContentObserver settingsObserver) { @@ -94,13 +68,13 @@ public interface SettingsProxy { * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.' */ default void registerContentObserver(Uri uri, ContentObserver settingsObserver) { - registerContentObserverForUser(uri, settingsObserver, getUserId()); + registerContentObserver(uri, false, settingsObserver); } /** * Convenience wrapper around * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.' - * + * <p> * Implicitly calls {@link #getUriFor(String)} on the passed in name. */ default void registerContentObserver(String name, boolean notifyForDescendants, @@ -114,53 +88,8 @@ public interface SettingsProxy { */ default void registerContentObserver(Uri uri, boolean notifyForDescendants, ContentObserver settingsObserver) { - registerContentObserverForUser(uri, notifyForDescendants, settingsObserver, getUserId()); - } - - /** - * Convenience wrapper around - * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)} - * - * Implicitly calls {@link #getUriFor(String)} on the passed in name. - */ - default void registerContentObserverForUser( - String name, ContentObserver settingsObserver, int userHandle) { - registerContentObserverForUser( - getUriFor(name), settingsObserver, userHandle); - } - - /** - * Convenience wrapper around - * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)} - */ - default void registerContentObserverForUser( - Uri uri, ContentObserver settingsObserver, int userHandle) { - registerContentObserverForUser( - uri, false, settingsObserver, userHandle); - } - - /** - * Convenience wrapper around - * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)} - * - * Implicitly calls {@link #getUriFor(String)} on the passed in name. - */ - default void registerContentObserverForUser( - String name, boolean notifyForDescendants, ContentObserver settingsObserver, - int userHandle) { - registerContentObserverForUser( - getUriFor(name), notifyForDescendants, settingsObserver, userHandle); - } - - /** - * Convenience wrapper around - * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)} - */ - default void registerContentObserverForUser( - Uri uri, boolean notifyForDescendants, ContentObserver settingsObserver, - int userHandle) { getContentResolver().registerContentObserver( - uri, notifyForDescendants, settingsObserver, getRealUserHandle(userHandle)); + uri, notifyForDescendants, settingsObserver); } /** See {@link ContentResolver#unregisterContentObserver(ContentObserver)}. */ @@ -173,22 +102,7 @@ public interface SettingsProxy { * @param name to look up in the table * @return the corresponding value, or null if not present */ - default String getString(String name) { - return getStringForUser(name, getUserId()); - } - - /**See {@link #getString(String)}. */ - String getStringForUser(String name, int userHandle); - - /** - * Store a name/value pair into the database. Values written by this method will be - * overridden if a restore happens in the future. - * - * @param name to store - * @param value to associate with the name - * @return true if the value was set, false on database errors - */ - boolean putString(String name, String value, boolean overrideableByRestore); + String getString(String name); /** * Store a name/value pair into the database. @@ -196,16 +110,7 @@ public interface SettingsProxy { * @param value to associate with the name * @return true if the value was set, false on database errors */ - default boolean putString(String name, String value) { - return putStringForUser(name, value, getUserId()); - } - - /** See {@link #putString(String, String)}. */ - boolean putStringForUser(String name, String value, int userHandle); - - /** See {@link #putString(String, String)}. */ - boolean putStringForUser(@NonNull String name, @Nullable String value, @Nullable String tag, - boolean makeDefault, @UserIdInt int userHandle, boolean overrideableByRestore); + boolean putString(String name, String value); /** * Store a name/value pair into the database. @@ -262,12 +167,7 @@ public interface SettingsProxy { * or not a valid integer. */ default int getInt(String name, int def) { - return getIntForUser(name, def, getUserId()); - } - - /** See {@link #getInt(String, int)}. */ - default int getIntForUser(String name, int def, int userHandle) { - String v = getStringForUser(name, userHandle); + String v = getString(name); try { return v != null ? Integer.parseInt(v) : def; } catch (NumberFormatException e) { @@ -292,14 +192,9 @@ public interface SettingsProxy { * * @return The setting's current value. */ - default int getInt(String name) throws Settings.SettingNotFoundException { - return getIntForUser(name, getUserId()); - } - - /** See {@link #getInt(String)}. */ - default int getIntForUser(String name, int userHandle) + default int getInt(String name) throws Settings.SettingNotFoundException { - String v = getStringForUser(name, userHandle); + String v = getString(name); try { return Integer.parseInt(v); } catch (NumberFormatException e) { @@ -320,12 +215,7 @@ public interface SettingsProxy { * @return true if the value was set, false on database errors */ default boolean putInt(String name, int value) { - return putIntForUser(name, value, getUserId()); - } - - /** See {@link #putInt(String, int)}. */ - default boolean putIntForUser(String name, int value, int userHandle) { - return putStringForUser(name, Integer.toString(value), userHandle); + return putString(name, Integer.toString(value)); } /** @@ -342,12 +232,7 @@ public interface SettingsProxy { * or not a valid boolean. */ default boolean getBool(String name, boolean def) { - return getBoolForUser(name, def, getUserId()); - } - - /** See {@link #getBool(String, boolean)}. */ - default boolean getBoolForUser(String name, boolean def, int userHandle) { - return getIntForUser(name, def ? 1 : 0, userHandle) != 0; + return getInt(name, def ? 1 : 0) != 0; } /** @@ -367,14 +252,9 @@ public interface SettingsProxy { * * @return The setting's current value. */ - default boolean getBool(String name) throws Settings.SettingNotFoundException { - return getBoolForUser(name, getUserId()); - } - - /** See {@link #getBool(String)}. */ - default boolean getBoolForUser(String name, int userHandle) + default boolean getBool(String name) throws Settings.SettingNotFoundException { - return getIntForUser(name, userHandle) != 0; + return getInt(name) != 0; } /** @@ -390,12 +270,7 @@ public interface SettingsProxy { * @return true if the value was set, false on database errors */ default boolean putBool(String name, boolean value) { - return putBoolForUser(name, value, getUserId()); - } - - /** See {@link #putBool(String, boolean)}. */ - default boolean putBoolForUser(String name, boolean value, int userHandle) { - return putIntForUser(name, value ? 1 : 0, userHandle); + return putInt(name, value ? 1 : 0); } /** @@ -412,12 +287,12 @@ public interface SettingsProxy { * or not a valid {@code long}. */ default long getLong(String name, long def) { - return getLongForUser(name, def, getUserId()); + String valString = getString(name); + return parseLongOrUseDefault(valString, def); } - /** See {@link #getLong(String, long)}. */ - default long getLongForUser(String name, long def, int userHandle) { - String valString = getStringForUser(name, userHandle); + /** Convert a string to a long, or uses a default if the string is malformed or null */ + static long parseLongOrUseDefault(String valString, long def) { long value; try { value = valString != null ? Long.parseLong(valString) : def; @@ -443,14 +318,15 @@ public interface SettingsProxy { * @throws Settings.SettingNotFoundException Thrown if a setting by the given * name can't be found or the setting value is not an integer. */ - default long getLong(String name) throws Settings.SettingNotFoundException { - return getLongForUser(name, getUserId()); + default long getLong(String name) + throws Settings.SettingNotFoundException { + String valString = getString(name); + return parseLongOrThrow(name, valString); } - /** See {@link #getLong(String)}. */ - default long getLongForUser(String name, int userHandle) + /** Convert a string to a long, or throws an exception if the string is malformed or null */ + static long parseLongOrThrow(String name, String valString) throws Settings.SettingNotFoundException { - String valString = getStringForUser(name, userHandle); try { return Long.parseLong(valString); } catch (NumberFormatException e) { @@ -471,12 +347,7 @@ public interface SettingsProxy { * @return true if the value was set, false on database errors */ default boolean putLong(String name, long value) { - return putLongForUser(name, value, getUserId()); - } - - /** See {@link #putLong(String, long)}. */ - default boolean putLongForUser(String name, long value, int userHandle) { - return putStringForUser(name, Long.toString(value), userHandle); + return putString(name, Long.toString(value)); } /** @@ -493,12 +364,12 @@ public interface SettingsProxy { * or not a valid float. */ default float getFloat(String name, float def) { - return getFloatForUser(name, def, getUserId()); + String v = getString(name); + return parseFloat(v, def); } - /** See {@link #getFloat(String)}. */ - default float getFloatForUser(String name, float def, int userHandle) { - String v = getStringForUser(name, userHandle); + /** Convert a string to a float, or uses a default if the string is malformed or null */ + static float parseFloat(String v, float def) { try { return v != null ? Float.parseFloat(v) : def; } catch (NumberFormatException e) { @@ -523,14 +394,15 @@ public interface SettingsProxy { * * @return The setting's current value. */ - default float getFloat(String name) throws Settings.SettingNotFoundException { - return getFloatForUser(name, getUserId()); + default float getFloat(String name) + throws Settings.SettingNotFoundException { + String v = getString(name); + return parseFloatOrThrow(name, v); } - /** See {@link #getFloat(String, float)}. */ - default float getFloatForUser(String name, int userHandle) + /** Convert a string to a float, or throws an exception if the string is malformed or null */ + static float parseFloatOrThrow(String name, String v) throws Settings.SettingNotFoundException { - String v = getStringForUser(name, userHandle); if (v == null) { throw new Settings.SettingNotFoundException(name); } @@ -554,11 +426,6 @@ public interface SettingsProxy { * @return true if the value was set, false on database errors */ default boolean putFloat(String name, float value) { - return putFloatForUser(name, value, getUserId()); - } - - /** See {@link #putFloat(String, float)} */ - default boolean putFloatForUser(String name, float value, int userHandle) { - return putStringForUser(name, Float.toString(value), userHandle); + return putString(name, Float.toString(value)); } } diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt index 561495e9d092..74843685893c 100644 --- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt +++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt @@ -27,7 +27,7 @@ import kotlinx.coroutines.flow.Flow object SettingsProxyExt { /** Returns a flow of [Unit] that is invoked each time that content is updated. */ - fun SettingsProxy.observerFlow( + fun UserSettingsProxy.observerFlow( @UserIdInt userId: Int, vararg names: String, ): Flow<Unit> { @@ -44,4 +44,22 @@ object SettingsProxyExt { awaitClose { unregisterContentObserver(observer) } } } + + /** Returns a flow of [Unit] that is invoked each time that content is updated. */ + fun SettingsProxy.observerFlow( + vararg names: String, + ): Flow<Unit> { + return conflatedCallbackFlow { + val observer = + object : ContentObserver(null) { + override fun onChange(selfChange: Boolean) { + trySend(Unit) + } + } + + names.forEach { name -> registerContentObserver(name, observer) } + + awaitClose { unregisterContentObserver(observer) } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettings.java b/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettings.java index d57d7496381c..c67c60375b2c 100644 --- a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettings.java +++ b/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettings.java @@ -21,5 +21,5 @@ package com.android.systemui.util.settings; * * See {@link SettingsProxy} for details. */ -public interface SystemSettings extends SettingsProxy { +public interface SystemSettings extends UserSettingsProxy { } diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java b/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java index fba7ddf5fe34..658b2992bfad 100644 --- a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java @@ -20,6 +20,8 @@ import android.content.ContentResolver; import android.net.Uri; import android.provider.Settings; +import androidx.annotation.NonNull; + import com.android.systemui.settings.UserTracker; import javax.inject.Inject; @@ -74,7 +76,7 @@ class SystemSettingsImpl implements SystemSettings { } @Override - public boolean putString(String name, String value, String tag, boolean makeDefault) { + public boolean putString(@NonNull String name, String value, String tag, boolean makeDefault) { throw new UnsupportedOperationException( "This method only exists publicly for Settings.Secure and Settings.Global"); } diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.java b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.java new file mode 100644 index 000000000000..0d6c0f59b2d0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.java @@ -0,0 +1,272 @@ +/* + * 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.util.settings; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.content.ContentResolver; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.UserHandle; +import android.provider.Settings; + +import com.android.systemui.settings.UserTracker; + +/** + * Used to interact with per-user Settings.Secure and Settings.System settings (but not + * Settings.Global, since those do not vary per-user) + * <p> + * This interface can be implemented to give instance method (instead of static method) versions + * of Settings.Secure and Settings.System. It can be injected into class constructors and then + * faked or mocked as needed in tests. + * <p> + * You can ask for {@link SecureSettings} or {@link SystemSettings} to be injected as needed. + * <p> + * This class also provides {@link #registerContentObserver(String, ContentObserver)} methods, + * normally found on {@link ContentResolver} instances, unifying setting related actions in one + * place. + */ +public interface UserSettingsProxy extends SettingsProxy { + + /** + * Returns that {@link UserTracker} this instance was constructed with. + */ + UserTracker getUserTracker(); + + /** + * Returns the user id for the associated {@link ContentResolver}. + */ + default int getUserId() { + return getContentResolver().getUserId(); + } + + /** + * Returns the actual current user handle when querying with the current user. Otherwise, + * returns the passed in user id. + */ + default int getRealUserHandle(int userHandle) { + if (userHandle != UserHandle.USER_CURRENT) { + return userHandle; + } + return getUserTracker().getUserId(); + } + + @Override + default void registerContentObserver(Uri uri, ContentObserver settingsObserver) { + registerContentObserverForUser(uri, settingsObserver, getUserId()); + } + + /** + * Convenience wrapper around + * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.' + */ + @Override + default void registerContentObserver(Uri uri, boolean notifyForDescendants, + ContentObserver settingsObserver) { + registerContentObserverForUser(uri, notifyForDescendants, settingsObserver, getUserId()); + } + + /** + * Convenience wrapper around + * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)} + * + * Implicitly calls {@link #getUriFor(String)} on the passed in name. + */ + default void registerContentObserverForUser( + String name, ContentObserver settingsObserver, int userHandle) { + registerContentObserverForUser( + getUriFor(name), settingsObserver, userHandle); + } + + /** + * Convenience wrapper around + * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)} + */ + default void registerContentObserverForUser( + Uri uri, ContentObserver settingsObserver, int userHandle) { + registerContentObserverForUser( + uri, false, settingsObserver, userHandle); + } + + /** + * Convenience wrapper around + * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)} + * + * Implicitly calls {@link #getUriFor(String)} on the passed in name. + */ + default void registerContentObserverForUser( + String name, boolean notifyForDescendants, ContentObserver settingsObserver, + int userHandle) { + registerContentObserverForUser( + getUriFor(name), notifyForDescendants, settingsObserver, userHandle); + } + + /** + * Convenience wrapper around + * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)} + */ + default void registerContentObserverForUser( + Uri uri, boolean notifyForDescendants, ContentObserver settingsObserver, + int userHandle) { + getContentResolver().registerContentObserver( + uri, notifyForDescendants, settingsObserver, getRealUserHandle(userHandle)); + } + + /** + * Look up a name in the database. + * @param name to look up in the table + * @return the corresponding value, or null if not present + */ + @Override + default String getString(String name) { + return getStringForUser(name, getUserId()); + } + + /**See {@link #getString(String)}. */ + String getStringForUser(String name, int userHandle); + + /** + * Store a name/value pair into the database. Values written by this method will be + * overridden if a restore happens in the future. + * + * @param name to store + * @param value to associate with the name + * @return true if the value was set, false on database errors + */ + boolean putString(String name, String value, boolean overrideableByRestore); + + @Override + default boolean putString(String name, String value) { + return putStringForUser(name, value, getUserId()); + } + + /** See {@link #putString(String, String)}. */ + boolean putStringForUser(String name, String value, int userHandle); + + /** See {@link #putString(String, String)}. */ + boolean putStringForUser(@NonNull String name, @Nullable String value, @Nullable String tag, + boolean makeDefault, @UserIdInt int userHandle, boolean overrideableByRestore); + + @Override + default int getInt(String name, int def) { + return getIntForUser(name, def, getUserId()); + } + + /** See {@link #getInt(String, int)}. */ + default int getIntForUser(String name, int def, int userHandle) { + String v = getStringForUser(name, userHandle); + try { + return v != null ? Integer.parseInt(v) : def; + } catch (NumberFormatException e) { + return def; + } + } + + @Override + default int getInt(String name) throws Settings.SettingNotFoundException { + return getIntForUser(name, getUserId()); + } + + /** See {@link #getInt(String)}. */ + default int getIntForUser(String name, int userHandle) + throws Settings.SettingNotFoundException { + String v = getStringForUser(name, userHandle); + try { + return Integer.parseInt(v); + } catch (NumberFormatException e) { + throw new Settings.SettingNotFoundException(name); + } + } + + @Override + default boolean putInt(String name, int value) { + return putIntForUser(name, value, getUserId()); + } + + /** See {@link #putInt(String, int)}. */ + default boolean putIntForUser(String name, int value, int userHandle) { + return putStringForUser(name, Integer.toString(value), userHandle); + } + + @Override + default boolean getBool(String name, boolean def) { + return getBoolForUser(name, def, getUserId()); + } + + /** See {@link #getBool(String, boolean)}. */ + default boolean getBoolForUser(String name, boolean def, int userHandle) { + return getIntForUser(name, def ? 1 : 0, userHandle) != 0; + } + + @Override + default boolean getBool(String name) throws Settings.SettingNotFoundException { + return getBoolForUser(name, getUserId()); + } + + /** See {@link #getBool(String)}. */ + default boolean getBoolForUser(String name, int userHandle) + throws Settings.SettingNotFoundException { + return getIntForUser(name, userHandle) != 0; + } + + @Override + default boolean putBool(String name, boolean value) { + return putBoolForUser(name, value, getUserId()); + } + + /** See {@link #putBool(String, boolean)}. */ + default boolean putBoolForUser(String name, boolean value, int userHandle) { + return putIntForUser(name, value ? 1 : 0, userHandle); + } + + /** See {@link #getLong(String, long)}. */ + default long getLongForUser(String name, long def, int userHandle) { + String valString = getStringForUser(name, userHandle); + return SettingsProxy.parseLongOrUseDefault(valString, def); + } + + /** See {@link #getLong(String)}. */ + default long getLongForUser(String name, int userHandle) + throws Settings.SettingNotFoundException { + String valString = getStringForUser(name, userHandle); + return SettingsProxy.parseLongOrThrow(name, valString); + } + + /** See {@link #putLong(String, long)}. */ + default boolean putLongForUser(String name, long value, int userHandle) { + return putStringForUser(name, Long.toString(value), userHandle); + } + + /** See {@link #getFloat(String)}. */ + default float getFloatForUser(String name, float def, int userHandle) { + String v = getStringForUser(name, userHandle); + return SettingsProxy.parseFloat(v, def); + } + + /** See {@link #getFloat(String, float)}. */ + default float getFloatForUser(String name, int userHandle) + throws Settings.SettingNotFoundException { + String v = getStringForUser(name, userHandle); + return SettingsProxy.parseFloatOrThrow(name, v); + } + + /** See {@link #putFloat(String, float)} */ + default boolean putFloatForUser(String name, float value, int userHandle) { + return putStringForUser(name, Float.toString(value), userHandle); + } +} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt index 62f9a9dcce70..20d4eb907944 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt @@ -810,7 +810,6 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { SceneKey.Bouncer, flowOf(.5f), false, - isUserInputOngoing = flowOf(false), ) runCurrent() sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer, null), "reason") @@ -826,8 +825,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { SceneKey.Bouncer, SceneKey.Gone, flowOf(.5f), - false, - isUserInputOngoing = flowOf(false), + false ) runCurrent() sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone, null), "reason") @@ -844,8 +842,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { SceneKey.Gone, SceneKey.Bouncer, flowOf(.5f), - false, - isUserInputOngoing = flowOf(false), + false ) runCurrent() sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer, null), "reason") @@ -863,8 +860,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { SceneKey.Bouncer, SceneKey.Gone, flowOf(.5f), - false, - isUserInputOngoing = flowOf(false), + false ) runCurrent() sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone, null), "reason") @@ -880,7 +876,6 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { SceneKey.Lockscreen, flowOf(.5f), false, - isUserInputOngoing = flowOf(false), ) runCurrent() sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen, null), "reason") @@ -898,7 +893,6 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { SceneKey.Gone, flowOf(.5f), false, - isUserInputOngoing = flowOf(false), ) runCurrent() sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone, null), "reason") diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt index e9e9624580b3..ebe13feb0f6f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt @@ -29,7 +29,6 @@ import android.testing.TestableLooper.RunWithLooper import android.view.LayoutInflater import android.view.MotionEvent import android.view.Surface -import android.view.Surface.ROTATION_0 import android.view.Surface.Rotation import android.view.View import android.view.WindowManager @@ -45,7 +44,6 @@ import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.LockscreenShadeTransitionController @@ -141,7 +139,6 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { ) { controllerOverlay = UdfpsControllerOverlay( context, - fingerprintManager, inflater, windowManager, accessibilityManager, @@ -155,7 +152,6 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { keyguardStateController, unlockedScreenOffAnimationController, udfpsDisplayMode, - secureSettings, REQUEST_ID, reason, controllerCallback, @@ -165,7 +161,6 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { primaryBouncerInteractor, alternateBouncerInteractor, isDebuggable, - udfpsUtils, udfpsKeyguardAccessibilityDelegate, udfpsKeyguardViewModels, ) @@ -212,8 +207,8 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { val lp = layoutParamsCaptor.value assertThat(lp.x).isEqualTo(0) assertThat(lp.y).isEqualTo(0) - assertThat(lp.width).isEqualTo(SENSOR_WIDTH) - assertThat(lp.height).isEqualTo(SENSOR_HEIGHT) + assertThat(lp.width).isEqualTo(DISPLAY_WIDTH) + assertThat(lp.height).isEqualTo(DISPLAY_HEIGHT) } } @@ -230,8 +225,8 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { val lp = layoutParamsCaptor.value assertThat(lp.x).isEqualTo(0) assertThat(lp.y).isEqualTo(0) - assertThat(lp.width).isEqualTo(SENSOR_WIDTH) - assertThat(lp.height).isEqualTo(SENSOR_HEIGHT) + assertThat(lp.width).isEqualTo(DISPLAY_WIDTH) + assertThat(lp.height).isEqualTo(DISPLAY_HEIGHT) } } @@ -247,9 +242,9 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { // Sensor should be in the bottom left corner in ROTATION_90. val lp = layoutParamsCaptor.value assertThat(lp.x).isEqualTo(0) - assertThat(lp.y).isEqualTo(DISPLAY_WIDTH - SENSOR_WIDTH) - assertThat(lp.width).isEqualTo(SENSOR_HEIGHT) - assertThat(lp.height).isEqualTo(SENSOR_WIDTH) + assertThat(lp.y).isEqualTo(0) + assertThat(lp.width).isEqualTo(DISPLAY_HEIGHT) + assertThat(lp.height).isEqualTo(DISPLAY_WIDTH) } } @@ -264,10 +259,10 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { // Sensor should be in the top right corner in ROTATION_270. val lp = layoutParamsCaptor.value - assertThat(lp.x).isEqualTo(DISPLAY_HEIGHT - SENSOR_HEIGHT) + assertThat(lp.x).isEqualTo(0) assertThat(lp.y).isEqualTo(0) - assertThat(lp.width).isEqualTo(SENSOR_HEIGHT) - assertThat(lp.height).isEqualTo(SENSOR_WIDTH) + assertThat(lp.width).isEqualTo(DISPLAY_HEIGHT) + assertThat(lp.height).isEqualTo(DISPLAY_WIDTH) } } @@ -345,11 +340,10 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { } @Test - fun smallOverlayOnEnrollmentWithA11y() = withRotation(ROTATION_0) { + fun smallOverlayOnEnrollmentWithA11y() = withRotation(Surface.ROTATION_0) { withReason(REASON_ENROLL_ENROLLING) { // When a11y enabled during enrollment whenever(accessibilityManager.isTouchExplorationEnabled).thenReturn(true) - whenever(featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true) controllerOverlay.show(udfpsController, overlayParams) verify(windowManager).addView( @@ -363,22 +357,4 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { assertThat(lp.height).isEqualTo(overlayParams.sensorBounds.height()) } } - - @Test - fun fullScreenOverlayWithNewTouchDetectionEnabled() = withRotation(ROTATION_0) { - withReason(REASON_AUTH_KEYGUARD) { - whenever(featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true) - - controllerOverlay.show(udfpsController, overlayParams) - verify(windowManager).addView( - eq(controllerOverlay.overlayView), - layoutParamsCaptor.capture() - ) - - // Layout params should use natural display width and height - val lp = layoutParamsCaptor.value - assertThat(lp.width).isEqualTo(overlayParams.naturalDisplayWidth) - assertThat(lp.height).isEqualTo(overlayParams.naturalDisplayHeight) - } - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index a36f4e9ac217..dcb53984ad87 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -88,7 +88,6 @@ import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor; import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels; @@ -105,7 +104,6 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.concurrency.FakeExecution; import com.android.systemui.util.concurrency.FakeExecutor; -import com.android.systemui.util.settings.SecureSettings; import com.android.systemui.util.time.FakeSystemClock; import com.android.systemui.util.time.SystemClock; @@ -122,7 +120,6 @@ import org.mockito.junit.MockitoRule; import java.util.ArrayList; import java.util.List; -import java.util.Optional; import javax.inject.Provider; @@ -206,8 +203,6 @@ public class UdfpsControllerTest extends SysuiTestCase { @Mock private ActivityLaunchAnimator mActivityLaunchAnimator; @Mock - private AlternateUdfpsTouchProvider mAlternateTouchProvider; - @Mock private PrimaryBouncerInteractor mPrimaryBouncerInteractor; @Mock private SinglePointerTouchProcessor mSinglePointerTouchProcessor; @@ -216,8 +211,6 @@ public class UdfpsControllerTest extends SysuiTestCase { @Mock private AlternateBouncerInteractor mAlternateBouncerInteractor; @Mock - private SecureSettings mSecureSettings; - @Mock private UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate; @Mock private Provider<UdfpsKeyguardViewModels> mUdfpsKeyguardViewModels; @@ -239,7 +232,6 @@ public class UdfpsControllerTest extends SysuiTestCase { private ScreenLifecycle.Observer mScreenObserver; private FingerprintSensorPropertiesInternal mOpticalProps; private FingerprintSensorPropertiesInternal mUltrasonicProps; - private UdfpsUtils mUdfpsUtils; @Mock private InputManager mInputManager; @Mock @@ -250,8 +242,6 @@ public class UdfpsControllerTest extends SysuiTestCase { mContext.getOrCreateTestableResources() .addOverride(com.android.internal.R.bool.config_ignoreUdfpsVote, false); - mUdfpsUtils = new UdfpsUtils(); - when(mLayoutInflater.inflate(R.layout.udfps_view, null, false)) .thenReturn(mUdfpsView); when(mLayoutInflater.inflate(R.layout.udfps_keyguard_view_legacy, null)) @@ -292,24 +282,13 @@ public class UdfpsControllerTest extends SysuiTestCase { // Create a fake background executor. mBiometricExecutor = new FakeExecutor(new FakeSystemClock()); - initUdfpsController(true /* hasAlternateTouchProvider */); - } - - - private void initUdfpsController(boolean hasAlternateTouchProvider) { - initUdfpsController(mOpticalProps, hasAlternateTouchProvider); + initUdfpsController(mOpticalProps); } - private void initUdfpsController(FingerprintSensorPropertiesInternal sensorProps, - boolean hasAlternateTouchProvider) { + private void initUdfpsController(FingerprintSensorPropertiesInternal sensorProps) { reset(mFingerprintManager); reset(mScreenLifecycle); - final Optional<Provider<AlternateUdfpsTouchProvider>> alternateTouchProvider = - hasAlternateTouchProvider ? Optional.of( - (Provider<AlternateUdfpsTouchProvider>) () -> mAlternateTouchProvider) - : Optional.empty(); - mUdfpsController = new UdfpsController( mContext, new FakeExecution(), @@ -339,15 +318,12 @@ public class UdfpsControllerTest extends SysuiTestCase { mSystemUIDialogManager, mLatencyTracker, mActivityLaunchAnimator, - alternateTouchProvider, mBiometricExecutor, mPrimaryBouncerInteractor, mSinglePointerTouchProcessor, mSessionTracker, mAlternateBouncerInteractor, - mSecureSettings, mInputManager, - mUdfpsUtils, mock(KeyguardFaceAuthInteractor.class), mUdfpsKeyguardAccessibilityDelegate, mUdfpsKeyguardViewModels @@ -374,17 +350,15 @@ public class UdfpsControllerTest extends SysuiTestCase { public void onActionDownTouch_whenCanDismissLockScreen_entersDevice() throws RemoteException { // GIVEN can dismiss lock screen and the current animation is an UdfpsKeyguardViewController when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true); - when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); when(mUdfpsView.getAnimationViewController()).thenReturn(mUdfpsKeyguardViewController); - // GIVEN that the overlay is showing - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); + final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult = + givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false); // WHEN ACTION_DOWN is received - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); - MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + touchProcessorResult.first); + MotionEvent downEvent = obtainMotionEvent(ACTION_DOWN, 0, 0, 0, 0); mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); mBiometricExecutor.runAllReady(); downEvent.recycle(); @@ -408,16 +382,14 @@ public class UdfpsControllerTest extends SysuiTestCase { throws RemoteException { // GIVEN can dismiss lock screen and the current animation is an UdfpsKeyguardViewController when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true); - when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); when(mUdfpsView.getAnimationViewController()).thenReturn(mUdfpsKeyguardViewController); - // GIVEN that the overlay is showing - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); + final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult = + givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false); // WHEN ACTION_MOVE is received - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + touchProcessorResult.first); MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0); if (stale) { mOverlayController.hideUdfpsOverlay(mOpticalProps.sensorId); @@ -436,22 +408,22 @@ public class UdfpsControllerTest extends SysuiTestCase { public void onMultipleTouch_whenCanDismissLockScreen_entersDeviceOnce() throws RemoteException { // GIVEN can dismiss lock screen and the current animation is an UdfpsKeyguardViewController when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true); - when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); when(mUdfpsView.getAnimationViewController()).thenReturn(mUdfpsKeyguardViewController); - // GIVEN that the overlay is showing - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); + final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult = + givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UNCHANGED, false); - // WHEN multiple touches are received - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); + // GIVEN that the overlay is showing + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + touchProcessorResult.first); MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); mBiometricExecutor.runAllReady(); downEvent.recycle(); + MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent); + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + touchProcessorResult.second); mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent); mBiometricExecutor.runAllReady(); moveEvent.recycle(); @@ -593,22 +565,17 @@ public class UdfpsControllerTest extends SysuiTestCase { private static class TestParams { public final FingerprintSensorPropertiesInternal sensorProps; - public final boolean hasAlternateTouchProvider; - TestParams(FingerprintSensorPropertiesInternal sensorProps, - boolean hasAlternateTouchProvider) { + TestParams(FingerprintSensorPropertiesInternal sensorProps) { this.sensorProps = sensorProps; - this.hasAlternateTouchProvider = hasAlternateTouchProvider; } } private void runWithAllParams(ThrowingConsumer<TestParams> testParamsConsumer) { for (FingerprintSensorPropertiesInternal sensorProps : List.of(mOpticalProps, mUltrasonicProps)) { - for (boolean hasAlternateTouchProvider : new boolean[]{false, true}) { - initUdfpsController(sensorProps, hasAlternateTouchProvider); - testParamsConsumer.accept(new TestParams(sensorProps, hasAlternateTouchProvider)); - } + initUdfpsController(sensorProps); + testParamsConsumer.accept(new TestParams(sensorProps)); } } @@ -621,23 +588,33 @@ public class UdfpsControllerTest extends SysuiTestCase { private void onTouch_propagatesTouchInNativeOrientationAndResolutionParameterized( TestParams testParams) throws RemoteException { reset(mUdfpsView); + when(mUdfpsView.getViewRootImpl()).thenReturn(mViewRootImpl); final Rect sensorBounds = new Rect(1000, 1900, 1080, 1920); // Bottom right corner. + final int pointerId = 0; final int displayWidth = 1080; final int displayHeight = 1920; - final float scaleFactor = 0.75f; // This means the native resolution is 1440x2560. + final float scaleFactor = 1f; // This means the native resolution is 1440x2560. final float touchMinor = 10f; final float touchMajor = 20f; + final float orientation = 30f; // Expecting a touch at the very bottom right corner in native orientation and resolution. - final int expectedX = (int) (displayWidth / scaleFactor); - final int expectedY = (int) (displayHeight / scaleFactor); + final float expectedX = displayWidth / scaleFactor; + final float expectedY = displayHeight / scaleFactor; final float expectedMinor = touchMinor / scaleFactor; final float expectedMajor = touchMajor / scaleFactor; // Configure UdfpsView to accept the ACTION_DOWN event when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); + + // GIVEN a valid touch on sensor + NormalizedTouchData touchData = new NormalizedTouchData(pointerId, displayWidth, + displayHeight, touchMinor, touchMajor, orientation, 0L, 0L); + TouchProcessorResult processorDownResult = new TouchProcessorResult.ProcessedTouch( + InteractionEvent.DOWN, 1, touchData); + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + processorDownResult); // Show the overlay. mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, @@ -654,21 +631,12 @@ public class UdfpsControllerTest extends SysuiTestCase { mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); mBiometricExecutor.runAllReady(); event.recycle(); - event = obtainMotionEvent(ACTION_MOVE, displayWidth, displayHeight, touchMinor, touchMajor); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); - mBiometricExecutor.runAllReady(); - event.recycle(); - if (testParams.hasAlternateTouchProvider) { - verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX), - eq(expectedY), eq(expectedMinor), eq(expectedMajor)); - } else { - verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID), - eq(testParams.sensorProps.sensorId), eq(expectedX), eq(expectedY), - eq(expectedMinor), eq(expectedMajor)); - } + verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID), + eq(testParams.sensorProps.sensorId), eq(pointerId), eq(expectedX), eq(expectedY), + eq(expectedMinor), eq(expectedMajor), eq(orientation), anyLong(), anyLong(), + anyBoolean()); // Test ROTATION_90 - reset(mAlternateTouchProvider); reset(mFingerprintManager); mUdfpsController.updateOverlayParams(testParams.sensorProps, new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight, @@ -677,21 +645,12 @@ public class UdfpsControllerTest extends SysuiTestCase { mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); mBiometricExecutor.runAllReady(); event.recycle(); - event = obtainMotionEvent(ACTION_MOVE, displayHeight, 0, touchMinor, touchMajor); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); - mBiometricExecutor.runAllReady(); - event.recycle(); - if (testParams.hasAlternateTouchProvider) { - verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX), - eq(expectedY), eq(expectedMinor), eq(expectedMajor)); - } else { - verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID), - eq(testParams.sensorProps.sensorId), eq(expectedX), eq(expectedY), - eq(expectedMinor), eq(expectedMajor)); - } + verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID), + eq(testParams.sensorProps.sensorId), eq(pointerId), eq(expectedX), eq(expectedY), + eq(expectedMinor), eq(expectedMajor), eq(orientation), anyLong(), anyLong(), + anyBoolean()); // Test ROTATION_270 - reset(mAlternateTouchProvider); reset(mFingerprintManager); mUdfpsController.updateOverlayParams(testParams.sensorProps, new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight, @@ -700,21 +659,12 @@ public class UdfpsControllerTest extends SysuiTestCase { mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); mBiometricExecutor.runAllReady(); event.recycle(); - event = obtainMotionEvent(ACTION_MOVE, 0, displayWidth, touchMinor, touchMajor); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); - mBiometricExecutor.runAllReady(); - event.recycle(); - if (testParams.hasAlternateTouchProvider) { - verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX), - eq(expectedY), eq(expectedMinor), eq(expectedMajor)); - } else { - verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID), - eq(testParams.sensorProps.sensorId), eq(expectedX), eq(expectedY), - eq(expectedMinor), eq(expectedMajor)); - } + verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID), + eq(testParams.sensorProps.sensorId), eq(pointerId), eq(expectedX), eq(expectedY), + eq(expectedMinor), eq(expectedMajor), eq(orientation), anyLong(), anyLong(), + anyBoolean()); // Test ROTATION_180 - reset(mAlternateTouchProvider); reset(mFingerprintManager); mUdfpsController.updateOverlayParams(testParams.sensorProps, new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight, @@ -724,18 +674,10 @@ public class UdfpsControllerTest extends SysuiTestCase { mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); mBiometricExecutor.runAllReady(); event.recycle(); - event = obtainMotionEvent(ACTION_MOVE, displayWidth, displayHeight, touchMinor, touchMajor); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); - mBiometricExecutor.runAllReady(); - event.recycle(); - if (testParams.hasAlternateTouchProvider) { - verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(expectedX), - eq(expectedY), eq(expectedMinor), eq(expectedMajor)); - } else { - verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID), - eq(testParams.sensorProps.sensorId), eq(expectedX), eq(expectedY), - eq(expectedMinor), eq(expectedMajor)); - } + verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID), + eq(testParams.sensorProps.sensorId), eq(pointerId), eq(expectedX), eq(expectedY), + eq(expectedMinor), eq(expectedMajor), eq(orientation), anyLong(), anyLong(), + anyBoolean()); } @Test @@ -744,46 +686,36 @@ public class UdfpsControllerTest extends SysuiTestCase { } private void fingerDownParameterized(TestParams testParams) throws RemoteException { - reset(mUdfpsView, mAlternateTouchProvider, mFingerprintManager, mLatencyTracker, + reset(mUdfpsView, mFingerprintManager, mLatencyTracker, mKeyguardUpdateMonitor); + when(mUdfpsView.getViewRootImpl()).thenReturn(mViewRootImpl); // Configure UdfpsView to accept the ACTION_DOWN event when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true); - // GIVEN that the overlay is showing + final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, + 0L); + final TouchProcessorResult processorResultDown = new TouchProcessorResult.ProcessedTouch( + InteractionEvent.DOWN, 1 /* pointerId */, touchData); + + initUdfpsController(testParams.sensorProps); mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); // WHEN ACTION_DOWN is received + verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + processorResultDown); MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); mBiometricExecutor.runAllReady(); downEvent.recycle(); - MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent); - mBiometricExecutor.runAllReady(); - moveEvent.recycle(); - - mFgExecutor.runAllReady(); - // THEN the touch provider is notified about onPointerDown. - if (testParams.hasAlternateTouchProvider) { - verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(0), eq(0), eq(0f), - eq(0f)); - verify(mFingerprintManager, never()).onPointerDown(anyLong(), anyInt(), anyInt(), - anyInt(), anyFloat(), anyFloat()); - verify(mKeyguardUpdateMonitor).onUdfpsPointerDown(eq((int) TEST_REQUEST_ID)); - } else { - verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID), - eq(testParams.sensorProps.sensorId), eq(0), eq(0), eq(0f), eq(0f)); - verify(mAlternateTouchProvider, never()).onPointerDown(anyInt(), anyInt(), anyInt(), - anyFloat(), anyFloat()); - } + verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), anyFloat(), + anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean()); // AND display configuration begins if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { @@ -800,33 +732,20 @@ public class UdfpsControllerTest extends SysuiTestCase { // AND onDisplayConfigured notifies FingerprintManager about onUiReady mOnDisplayConfiguredCaptor.getValue().run(); mBiometricExecutor.runAllReady(); - if (testParams.hasAlternateTouchProvider) { - InOrder inOrder = inOrder(mAlternateTouchProvider, mLatencyTracker); - inOrder.verify(mAlternateTouchProvider).onUiReady(); - inOrder.verify(mLatencyTracker).onActionEnd( - eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE)); - verify(mFingerprintManager, never()).onUdfpsUiEvent( - eq(FingerprintManager.UDFPS_UI_READY), anyLong(), anyInt()); - } else { - InOrder inOrder = inOrder(mFingerprintManager, mLatencyTracker); - inOrder.verify(mFingerprintManager).onUdfpsUiEvent( - eq(FingerprintManager.UDFPS_UI_READY), eq(TEST_REQUEST_ID), - eq(testParams.sensorProps.sensorId)); - inOrder.verify(mLatencyTracker).onActionEnd( - eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE)); - verify(mAlternateTouchProvider, never()).onUiReady(); - } + InOrder inOrder = inOrder(mFingerprintManager, mLatencyTracker); + inOrder.verify(mFingerprintManager).onUdfpsUiEvent( + eq(FingerprintManager.UDFPS_UI_READY), eq(TEST_REQUEST_ID), + eq(testParams.sensorProps.sensorId)); + inOrder.verify(mLatencyTracker).onActionEnd( + eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE)); } else { verify(mFingerprintManager, never()).onUdfpsUiEvent( eq(FingerprintManager.UDFPS_UI_READY), anyLong(), anyInt()); - verify(mAlternateTouchProvider, never()).onUiReady(); verify(mLatencyTracker, never()).onActionEnd( eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE)); } } - - @Test public void aodInterrupt() { runWithAllParams(this::aodInterruptParameterized); @@ -834,8 +753,9 @@ public class UdfpsControllerTest extends SysuiTestCase { private void aodInterruptParameterized(TestParams testParams) throws RemoteException { mUdfpsController.cancelAodSendFingerUpAction(); - reset(mUdfpsView, mAlternateTouchProvider, mFingerprintManager, mKeyguardUpdateMonitor); + reset(mUdfpsView, mFingerprintManager, mKeyguardUpdateMonitor); when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true); + when(mUdfpsView.getViewRootImpl()).thenReturn(mViewRootImpl); // GIVEN that the overlay is showing and screen is on and fp is running mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, @@ -855,19 +775,8 @@ public class UdfpsControllerTest extends SysuiTestCase { } mBiometricExecutor.runAllReady(); - if (testParams.hasAlternateTouchProvider) { - verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(0), eq(0), - eq(3f) /* minor */, eq(2f) /* major */); - verify(mFingerprintManager, never()).onPointerDown(anyLong(), anyInt(), anyInt(), - anyInt(), anyFloat(), anyFloat()); - verify(mKeyguardUpdateMonitor).onUdfpsPointerDown(eq((int) TEST_REQUEST_ID)); - } else { - verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID), - eq(testParams.sensorProps.sensorId), eq(0), eq(0), eq(3f) /* minor */, - eq(2f) /* major */); - verify(mAlternateTouchProvider, never()).onPointerDown(anyLong(), anyInt(), anyInt(), - anyFloat(), anyFloat()); - } + verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), anyFloat(), + anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean()); } @Test @@ -907,10 +816,12 @@ public class UdfpsControllerTest extends SysuiTestCase { private void onFingerUp_displayConfigurationParameterized(TestParams testParams) throws RemoteException { reset(mUdfpsView); + when(mUdfpsView.getViewRootImpl()).thenReturn(mViewRootImpl); + + final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult = + givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false); // GIVEN AOD interrupt - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mScreenObserver.onScreenTurnedOn(); mFgExecutor.runAllReady(); mUdfpsController.onAodInterrupt(0, 0, 0f, 0f); @@ -918,7 +829,8 @@ public class UdfpsControllerTest extends SysuiTestCase { when(mUdfpsView.isDisplayConfigured()).thenReturn(true); // WHEN up-action received - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + touchProcessorResult.second); MotionEvent upEvent = MotionEvent.obtain(0, 0, ACTION_UP, 0, 0, 0); mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent); mBiometricExecutor.runAllReady(); @@ -931,7 +843,8 @@ public class UdfpsControllerTest extends SysuiTestCase { when(mUdfpsView.isDisplayConfigured()).thenReturn(false); // WHEN up-action received - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + touchProcessorResult.second); MotionEvent upEvent = MotionEvent.obtain(0, 0, ACTION_UP, 0, 0, 0); mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent); mBiometricExecutor.runAllReady(); @@ -1015,16 +928,19 @@ public class UdfpsControllerTest extends SysuiTestCase { private void aodInterruptCancelTimeoutActionOnFingerUpParameterized(TestParams testParams) throws RemoteException { reset(mUdfpsView); - when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); + when(mUdfpsView.getViewRootImpl()).thenReturn(mViewRootImpl); // GIVEN AOD interrupt - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mScreenObserver.onScreenTurnedOn(); mFgExecutor.runAllReady(); mUdfpsController.onAodInterrupt(0, 0, 0f, 0f); mFgExecutor.runAllReady(); + final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult = + givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false); + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, + BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { // Configure UdfpsView to accept the ACTION_UP event when(mUdfpsView.isDisplayConfigured()).thenReturn(true); @@ -1033,7 +949,8 @@ public class UdfpsControllerTest extends SysuiTestCase { } // WHEN ACTION_UP is received - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + touchProcessorResult.second); MotionEvent upEvent = MotionEvent.obtain(0, 0, ACTION_UP, 0, 0, 0); mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent); mBiometricExecutor.runAllReady(); @@ -1043,16 +960,13 @@ public class UdfpsControllerTest extends SysuiTestCase { when(mUdfpsView.isDisplayConfigured()).thenReturn(false); // WHEN ACTION_DOWN is received + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + touchProcessorResult.first); MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); mBiometricExecutor.runAllReady(); downEvent.recycle(); - // WHEN ACTION_MOVE is received - MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent); - mBiometricExecutor.runAllReady(); - moveEvent.recycle(); mFgExecutor.runAllReady(); if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { @@ -1122,24 +1036,16 @@ public class UdfpsControllerTest extends SysuiTestCase { @Test public void playHapticOnTouchUdfpsArea_a11yTouchExplorationEnabled() throws RemoteException { - // Configure UdfpsView to accept the ACTION_DOWN event - when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); - - // GIVEN that the overlay is showing and a11y touch exploration enabled - when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true); - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); + final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult = + givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, true); // WHEN ACTION_HOVER is received + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + touchProcessorResult.first); verify(mUdfpsView).setOnHoverListener(mHoverListenerCaptor.capture()); MotionEvent enterEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_ENTER, 0, 0, 0); mHoverListenerCaptor.getValue().onHover(mUdfpsView, enterEvent); enterEvent.recycle(); - MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_MOVE, 0, 0, 0); - mHoverListenerCaptor.getValue().onHover(mUdfpsView, moveEvent); - moveEvent.recycle(); // THEN tick haptic is played verify(mVibrator).vibrate( @@ -1159,24 +1065,16 @@ public class UdfpsControllerTest extends SysuiTestCase { public void playHapticOnTouchUdfpsArea_a11yTouchExplorationEnabled_oneWayHapticsEnabled() throws RemoteException { when(mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)).thenReturn(true); - // Configure UdfpsView to accept the ACTION_DOWN event - when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); - // GIVEN that the overlay is showing and a11y touch exploration enabled - when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true); - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); + final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult = + givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, true); // WHEN ACTION_HOVER is received - verify(mUdfpsView).setOnHoverListener(mHoverListenerCaptor.capture()); + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + touchProcessorResult.first); MotionEvent enterEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_ENTER, 0, 0, 0); mHoverListenerCaptor.getValue().onHover(mUdfpsView, enterEvent); enterEvent.recycle(); - MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_MOVE, 0, 0, 0); - mHoverListenerCaptor.getValue().onHover(mUdfpsView, moveEvent); - moveEvent.recycle(); // THEN context click haptic is played verify(mVibrator).performHapticFeedback( @@ -1187,26 +1085,16 @@ public class UdfpsControllerTest extends SysuiTestCase { @Test public void noHapticOnTouchUdfpsArea_a11yTouchExplorationDisabled() throws RemoteException { - // Configure UdfpsView to accept the ACTION_DOWN event - when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); - - // GIVEN that the overlay is showing and a11y touch exploration NOT enabled - when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false); - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); + final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult = + givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false); // WHEN ACTION_DOWN is received - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + touchProcessorResult.first); MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); mBiometricExecutor.runAllReady(); downEvent.recycle(); - MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent); - mBiometricExecutor.runAllReady(); - moveEvent.recycle(); // THEN NO haptic played verify(mVibrator, never()).vibrate( @@ -1221,80 +1109,26 @@ public class UdfpsControllerTest extends SysuiTestCase { public void noHapticOnTouchUdfpsArea_a11yTouchExplorationDisabled__oneWayHapticsEnabled() throws RemoteException { when(mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)).thenReturn(true); - // Configure UdfpsView to accept the ACTION_DOWN event - when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); - // GIVEN that the overlay is showing and a11y touch exploration NOT enabled - when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false); - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); + final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult = + givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false); // WHEN ACTION_DOWN is received - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + touchProcessorResult.first); MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); mBiometricExecutor.runAllReady(); downEvent.recycle(); - MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent); - mBiometricExecutor.runAllReady(); - moveEvent.recycle(); // THEN NO haptic played verify(mVibrator, never()).performHapticFeedback(any(), anyInt()); } @Test - public void onTouch_withoutNewTouchDetection_shouldCallOldFingerprintManagerPath() - throws RemoteException { - // Disable new touch detection. - when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(false); - - // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider. - initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */); - - // Configure UdfpsView to accept the ACTION_DOWN event - when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); - - // GIVEN that the overlay is showing and a11y touch exploration NOT enabled - when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false); - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); - - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); - - // WHEN ACTION_DOWN is received - MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); - mBiometricExecutor.runAllReady(); - downEvent.recycle(); - - // AND ACTION_MOVE is received - MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent); - mBiometricExecutor.runAllReady(); - moveEvent.recycle(); - - // AND ACTION_UP is received - MotionEvent upEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent); - mBiometricExecutor.runAllReady(); - upEvent.recycle(); - - // THEN the old FingerprintManager path is invoked. - verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), anyInt(), - anyFloat(), anyFloat()); - verify(mFingerprintManager).onPointerUp(anyLong(), anyInt()); - } - - @Test public void fingerDown_falsingManagerInformed() throws RemoteException { final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult = - givenAcceptFingerDownEvent(); + givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false); // WHEN ACTION_DOWN is received when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( @@ -1308,85 +1142,46 @@ public class UdfpsControllerTest extends SysuiTestCase { verify(mFalsingManager).isFalseTouch(UDFPS_AUTHENTICATION); } - @Test - public void onTouch_withNewTouchDetection_shouldCallNewFingerprintManagerPath() - throws RemoteException { - final Pair<TouchProcessorResult, TouchProcessorResult> processorResultDownAndUp = - givenAcceptFingerDownEvent(); - - // WHEN ACTION_DOWN is received - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - processorResultDownAndUp.first); - MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); - mBiometricExecutor.runAllReady(); - downEvent.recycle(); - - // AND ACTION_UP is received - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - processorResultDownAndUp.second); - MotionEvent upEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent); - mBiometricExecutor.runAllReady(); - upEvent.recycle(); - - // THEN the new FingerprintManager path is invoked. - verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), anyFloat(), - anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean()); - verify(mFingerprintManager).onPointerUp(anyLong(), anyInt(), anyInt(), anyFloat(), - anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean()); - } - - private Pair<TouchProcessorResult, TouchProcessorResult> givenAcceptFingerDownEvent() + private Pair<TouchProcessorResult, TouchProcessorResult> givenFingerEvent( + InteractionEvent event1, InteractionEvent event2, boolean a11y) throws RemoteException { final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, 0L); final TouchProcessorResult processorResultDown = new TouchProcessorResult.ProcessedTouch( - InteractionEvent.DOWN, 1 /* pointerId */, touchData); + event1, 1 /* pointerId */, touchData); final TouchProcessorResult processorResultUp = new TouchProcessorResult.ProcessedTouch( - InteractionEvent.UP, 1 /* pointerId */, touchData); - - // Enable new touch detection. - when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true); + event2, 1 /* pointerId */, touchData); // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider. - initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */); + initUdfpsController(mOpticalProps); // Configure UdfpsView to accept the ACTION_DOWN event when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); // GIVEN that the overlay is showing and a11y touch exploration NOT enabled - when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false); + when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(a11y); mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); + if (a11y) { + verify(mUdfpsView).setOnHoverListener(mHoverListenerCaptor.capture()); + } else { + verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); + } return new Pair<>(processorResultDown, processorResultUp); } @Test - public void onTouch_WithNewTouchDetection_forwardToKeyguard() throws RemoteException { + public void onTouch_forwardToKeyguard() throws RemoteException { final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, 0L); final TouchProcessorResult processorResultDown = new TouchProcessorResult.ProcessedTouch( InteractionEvent.UNCHANGED, -1 /* pointerOnSensorId */, touchData); - // Enable new touch detection. - when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true); - - // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider. - initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */); - - // Configure UdfpsView to accept the ACTION_DOWN event - when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(false); - // GIVEN that the overlay is showing and a11y touch exploration NOT enabled when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); - when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false); mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); @@ -1402,47 +1197,23 @@ public class UdfpsControllerTest extends SysuiTestCase { // THEN the touch is forwarded to Keyguard verify(mStatusBarKeyguardViewManager).onTouch(downEvent); - downEvent.recycle(); } @Test - public void onTouch_withNewTouchDetection_pilferPointer() throws RemoteException { - final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, - 0L); - final TouchProcessorResult processorResultDown = new TouchProcessorResult.ProcessedTouch( - InteractionEvent.DOWN, 1 /* pointerId */, touchData); - - // Enable new touch detection. - when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true); - - // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider. - initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */); - - // Configure UdfpsView to accept the ACTION_DOWN event - when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); - - // GIVEN that the overlay is showing and a11y touch exploration NOT enabled - when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false); - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); - - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); + public void onTouch_pilferPointer() throws RemoteException { + final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult = + givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UNCHANGED, false); // WHEN ACTION_DOWN is received when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - processorResultDown); + touchProcessorResult.first); MotionEvent event = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); mBiometricExecutor.runAllReady(); // WHEN ACTION_MOVE is received after - final TouchProcessorResult processorResultUnchanged = - new TouchProcessorResult.ProcessedTouch( - InteractionEvent.UNCHANGED, 1 /* pointerId */, touchData); when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - processorResultUnchanged); + touchProcessorResult.second); event.setAction(ACTION_MOVE); mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); mBiometricExecutor.runAllReady(); @@ -1453,25 +1224,13 @@ public class UdfpsControllerTest extends SysuiTestCase { } @Test - public void onTouch_withNewTouchDetection_doNotPilferPointer() throws RemoteException { + public void onTouch_doNotPilferPointer() throws RemoteException { final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, 0L); final TouchProcessorResult processorResultUnchanged = new TouchProcessorResult.ProcessedTouch(InteractionEvent.UNCHANGED, - 1 /* pointerId */, touchData); - - // Enable new touch detection. - when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true); + -1 /* pointerId */, touchData); - // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider. - initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */); - - // Configure UdfpsView to not accept the ACTION_DOWN event - when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(false); - - // GIVEN that the overlay is showing and a11y touch exploration NOT enabled - when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false); mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); @@ -1491,36 +1250,17 @@ public class UdfpsControllerTest extends SysuiTestCase { } @Test - public void onTouch_withNewTouchDetection_pilferPointerWhenAltBouncerShowing() + public void onTouch_pilferPointerWhenAltBouncerShowing() throws RemoteException { - final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, - 0L); - final TouchProcessorResult processorResultUnchanged = - new TouchProcessorResult.ProcessedTouch(InteractionEvent.UNCHANGED, - 1 /* pointerId */, touchData); - - // Enable new touch detection. - when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true); - - // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider. - initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */); - - // Configure UdfpsView to not accept the ACTION_DOWN event - when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(false); + final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult = + givenFingerEvent(InteractionEvent.UNCHANGED, InteractionEvent.UP, false); - // GIVEN that the alternate bouncer is showing and a11y touch exploration NOT enabled - when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false); + // WHEN alternate bouncer is showing when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); - - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); // WHEN ACTION_DOWN is received and touch is not within sensor when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - processorResultUnchanged); + touchProcessorResult.first); MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); mBiometricExecutor.runAllReady(); @@ -1531,32 +1271,10 @@ public class UdfpsControllerTest extends SysuiTestCase { } @Test - public void onTouch_withNewTouchDetection_doNotProcessTouchWhenPullingUpBouncer() + public void onTouch_doNotProcessTouchWhenPullingUpBouncer() throws RemoteException { - final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, - 0L); - final TouchProcessorResult processorResultMove = - new TouchProcessorResult.ProcessedTouch(InteractionEvent.DOWN, - 1 /* pointerId */, touchData); - - // Enable new touch detection. - when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true); - - // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider. - initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */); - - // Configure UdfpsView to accept the ACTION_MOVE event - when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); - - // GIVEN that the alternate bouncer is not showing and a11y touch exploration NOT enabled - when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false); - when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(false); - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); - - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); + final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult = + givenFingerEvent(InteractionEvent.UNCHANGED, InteractionEvent.UP, false); // GIVEN a swipe up to bring up primary bouncer is in progress or swipe down for QS when(mPrimaryBouncerInteractor.isInTransit()).thenReturn(true); @@ -1564,7 +1282,7 @@ public class UdfpsControllerTest extends SysuiTestCase { // WHEN ACTION_MOVE is received and touch is within sensor when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - processorResultMove); + touchProcessorResult.first); MotionEvent moveEvent = MotionEvent.obtain(0, 0, ACTION_MOVE, 0, 0, 0); mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent); mBiometricExecutor.runAllReady(); @@ -1578,40 +1296,19 @@ public class UdfpsControllerTest extends SysuiTestCase { @Test - public void onTouch_withNewTouchDetection_qsDrag_processesTouchWhenAlternateBouncerVisible() + public void onTouch_qsDrag_processesTouchWhenAlternateBouncerVisible() throws RemoteException { - final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, - 0L); - final TouchProcessorResult processorResultMove = - new TouchProcessorResult.ProcessedTouch(InteractionEvent.DOWN, - 1 /* pointerId */, touchData); - - // Enable new touch detection. - when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true); - - // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider. - initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */); - - // Configure UdfpsView to accept the ACTION_MOVE event - when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); + final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult = + givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false); - // GIVEN that the alternate bouncer is showing and a11y touch exploration NOT enabled - when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false); when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); - - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); - // GIVEN swipe down for QS when(mPrimaryBouncerInteractor.isInTransit()).thenReturn(false); when(mLockscreenShadeTransitionController.getQSDragProgress()).thenReturn(1f); // WHEN ACTION_MOVE is received and touch is within sensor when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - processorResultMove); + touchProcessorResult.first); MotionEvent moveEvent = MotionEvent.obtain(0, 0, ACTION_MOVE, 0, 0, 0); mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent); mBiometricExecutor.runAllReady(); @@ -1689,43 +1386,4 @@ public class UdfpsControllerTest extends SysuiTestCase { // THEN vibrate is used verify(mVibrator).performHapticFeedback(any(), eq(UdfpsController.LONG_PRESS)); } - - @Test - public void aodInterrupt_withNewTouchDetection() throws RemoteException { - mUdfpsController.cancelAodSendFingerUpAction(); - final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, - 0L); - final TouchProcessorResult processorResultDown = - new TouchProcessorResult.ProcessedTouch(InteractionEvent.DOWN, - 1 /* pointerId */, touchData); - - // Enable new touch detection. - when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true); - - // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider. - initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */); - - // GIVEN that the overlay is showing and screen is on and fp is running - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, 0, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mScreenObserver.onScreenTurnedOn(); - mFgExecutor.runAllReady(); - - // WHEN fingerprint is requested because of AOD interrupt - mUdfpsController.onAodInterrupt(0, 0, 2f, 3f); - - // Check case where touch driver sends touch to UdfpsView as well - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); - when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - processorResultDown); - MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); - - mBiometricExecutor.runAllReady(); - - // THEN only one onPointerDown is sent - verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), anyFloat(), - anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean()); - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java index 3276e6624004..e512adcc0542 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java @@ -30,7 +30,6 @@ import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FakeFeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shade.ShadeExpansionChangeEvent; @@ -116,7 +115,7 @@ public class UdfpsKeyguardViewLegacyControllerBaseTest extends SysuiTestCase { } public UdfpsKeyguardViewControllerLegacy createUdfpsKeyguardViewController() { - return createUdfpsKeyguardViewController(false, false); + return createUdfpsKeyguardViewController(false); } public void captureKeyGuardViewManagerCallback() { @@ -126,8 +125,7 @@ public class UdfpsKeyguardViewLegacyControllerBaseTest extends SysuiTestCase { } protected UdfpsKeyguardViewControllerLegacy createUdfpsKeyguardViewController( - boolean useModernBouncer, boolean useExpandedOverlay) { - mFeatureFlags.set(Flags.UDFPS_NEW_TOUCH_DETECTION, useExpandedOverlay); + boolean useModernBouncer) { UdfpsKeyguardViewControllerLegacy controller = new UdfpsKeyguardViewControllerLegacy( mView, mStatusBarStateController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java index b018a3e2ca6c..21928cd606ed 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java @@ -18,15 +18,12 @@ package com.android.systemui.biometrics; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.atLeast; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.testing.TestableLooper; -import android.view.MotionEvent; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; @@ -44,8 +41,7 @@ public class UdfpsKeyguardViewLegacyControllerTest extends UdfpsKeyguardViewLegacyControllerBaseTest { @Override public UdfpsKeyguardViewControllerLegacy createUdfpsKeyguardViewController() { - return createUdfpsKeyguardViewController(/* useModernBouncer */ false, - /* useExpandedOverlay */ false); + return createUdfpsKeyguardViewController(/* useModernBouncer */ false); } @Test @@ -216,37 +212,4 @@ public class UdfpsKeyguardViewLegacyControllerTest extends sendStatusBarStateChanged(StatusBarState.SHADE_LOCKED); assertTrue(mController.shouldPauseAuth()); } - - @Test - // TODO(b/259264861): Tracking Bug - public void testUdfpsExpandedOverlayOn() { - // GIVEN view is attached and useExpandedOverlay is true - mController = createUdfpsKeyguardViewController(false, true); - mController.onViewAttached(); - captureKeyGuardViewManagerCallback(); - - // WHEN a touch is received - mKeyguardViewManagerCallback.onTouch( - MotionEvent.obtain(0, 0, 0, 0, 0, 0)); - - // THEN udfpsController onTouch is not called - assertTrue(mView.mUseExpandedOverlay); - verify(mUdfpsController, never()).onTouch(any()); - } - - @Test - // TODO(b/259264861): Tracking Bug - public void testUdfpsExpandedOverlayOff() { - // GIVEN view is attached and useExpandedOverlay is false - mController.onViewAttached(); - captureKeyGuardViewManagerCallback(); - - // WHEN a touch is received - mKeyguardViewManagerCallback.onTouch( - MotionEvent.obtain(0, 0, 0, 0, 0, 0)); - - // THEN udfpsController onTouch is called - assertFalse(mView.mUseExpandedOverlay); - verify(mUdfpsController).onTouch(any()); - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt index da4548bc14c0..02ee53879ea0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt @@ -106,10 +106,7 @@ class UdfpsKeyguardViewLegacyControllerWithCoroutinesTest : mock(SystemClock::class.java), mKeyguardUpdateMonitor, ) - return createUdfpsKeyguardViewController( - /* useModernBouncer */ true, /* useExpandedOverlay */ - false - ) + return createUdfpsKeyguardViewController(/* useModernBouncer */ true) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt index 0c8e7a5d356a..9fbe09619ff1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt @@ -16,8 +16,6 @@ package com.android.systemui.biometrics -import android.graphics.PointF -import android.graphics.RectF import android.hardware.biometrics.SensorLocationInternal import android.testing.TestableLooper import android.testing.ViewUtils @@ -42,7 +40,6 @@ import org.mockito.Mockito.never import org.mockito.Mockito.nullable import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit -import org.mockito.Mockito.`when` as whenever private const val SENSOR_X = 50 private const val SENSOR_Y = 250 @@ -80,56 +77,7 @@ class UdfpsViewTest : SysuiTestCase() { ViewUtils.detachView(view) } - @Test - fun layoutSizeFitsSensor() { - val params = withArgCaptor<RectF> { - verify(animationViewController).onSensorRectUpdated(capture()) - } - assertThat(params.width()).isAtLeast(2f * SENSOR_RADIUS) - assertThat(params.height()).isAtLeast(2f * SENSOR_RADIUS) - } - - @Test - fun isWithinSensorAreaAndPaused() = isWithinSensorArea(paused = true) - - @Test - fun isWithinSensorAreaAndNotPaused() = isWithinSensorArea(paused = false) - - private fun isWithinSensorArea(paused: Boolean) { - whenever(animationViewController.shouldPauseAuth()).thenReturn(paused) - whenever(animationViewController.touchTranslation).thenReturn(PointF(0f, 0f)) - val end = (SENSOR_RADIUS * 2) - 1 - for (x in 1 until end) { - for (y in 1 until end) { - assertThat(view.isWithinSensorArea(x.toFloat(), y.toFloat())).isEqualTo(!paused) - } - } - } - - @Test - fun isWithinSensorAreaWhenTranslated() { - val offset = PointF(100f, 200f) - whenever(animationViewController.touchTranslation).thenReturn(offset) - val end = (SENSOR_RADIUS * 2) - 1 - for (x in 0 until offset.x.toInt() step 2) { - for (y in 0 until offset.y.toInt() step 2) { - assertThat(view.isWithinSensorArea(x.toFloat(), y.toFloat())).isFalse() - } - } - for (x in offset.x.toInt() + 1 until offset.x.toInt() + end) { - for (y in offset.y.toInt() + 1 until offset.y.toInt() + end) { - assertThat(view.isWithinSensorArea(x.toFloat(), y.toFloat())).isTrue() - } - } - } - - @Test - fun isNotWithinSensorArea() { - whenever(animationViewController.touchTranslation).thenReturn(PointF(0f, 0f)) - assertThat(view.isWithinSensorArea(SENSOR_RADIUS * 2.5f, SENSOR_RADIUS.toFloat())) - .isFalse() - assertThat(view.isWithinSensorArea(SENSOR_RADIUS.toFloat(), SENSOR_RADIUS * 2.5f)).isFalse() - } + // TODO: Add test to verify view is size of screen @Test fun startAndStopIllumination() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt new file mode 100644 index 000000000000..30a5497d0a14 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt @@ -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.systemui.communal.data.repository + +import android.content.pm.UserInfo +import android.provider.Settings +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.core.FakeLogBuffer +import com.android.systemui.settings.FakeUserTracker +import com.android.systemui.user.data.repository.FakeUserRepository +import com.android.systemui.util.settings.FakeSettings +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 +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidJUnit4::class) +class CommunalTutorialRepositoryImplTest : SysuiTestCase() { + private lateinit var secureSettings: FakeSettings + private lateinit var userRepository: FakeUserRepository + private lateinit var userTracker: FakeUserTracker + private lateinit var logBuffer: LogBuffer + + private val testDispatcher = StandardTestDispatcher() + private val testScope = TestScope(testDispatcher) + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + logBuffer = FakeLogBuffer.Factory.create() + secureSettings = FakeSettings() + userRepository = FakeUserRepository() + val listOfUserInfo = listOf(MAIN_USER_INFO) + userRepository.setUserInfos(listOfUserInfo) + + userTracker = FakeUserTracker() + userTracker.set( + userInfos = listOfUserInfo, + selectedUserIndex = 0, + ) + } + + @Test + fun tutorialSettingState_defaultToNotStarted() = + testScope.runTest { + val repository = initCommunalTutorialRepository() + val tutorialSettingState = collectLastValue(repository.tutorialSettingState)() + assertThat(tutorialSettingState) + .isEqualTo(Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED) + } + + @Test + fun tutorialSettingState_whenTutorialSettingsUpdatedToStarted() = + testScope.runTest { + val repository = initCommunalTutorialRepository() + setTutorialStateSetting(Settings.Secure.HUB_MODE_TUTORIAL_STARTED) + val tutorialSettingState = collectLastValue(repository.tutorialSettingState)() + assertThat(tutorialSettingState).isEqualTo(Settings.Secure.HUB_MODE_TUTORIAL_STARTED) + } + + @Test + fun tutorialSettingState_whenTutorialSettingsUpdatedToCompleted() = + testScope.runTest { + val repository = initCommunalTutorialRepository() + setTutorialStateSetting(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED) + val tutorialSettingState = collectLastValue(repository.tutorialSettingState)() + assertThat(tutorialSettingState).isEqualTo(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED) + } + + private fun initCommunalTutorialRepository(): CommunalTutorialRepositoryImpl { + return CommunalTutorialRepositoryImpl( + testScope.backgroundScope, + testDispatcher, + userRepository, + secureSettings, + userTracker, + logBuffer + ) + } + + private fun setTutorialStateSetting( + @Settings.Secure.HubModeTutorialState state: Int, + user: UserInfo = MAIN_USER_INFO + ) { + secureSettings.putIntForUser(Settings.Secure.HUB_MODE_TUTORIAL_STATE, state, user.id) + } + + companion object { + private val MAIN_USER_INFO = + UserInfo(/* id= */ 0, /* name= */ "primary", /* flags= */ UserInfo.FLAG_MAIN) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt new file mode 100644 index 000000000000..0a9a15e06b1b --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt @@ -0,0 +1,113 @@ +/* + * 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.domain.interactor + +import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED +import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED +import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_STARTED +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory +import com.android.systemui.settings.UserTracker +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(JUnit4::class) +class CommunalTutorialInteractorTest : SysuiTestCase() { + + @Mock private lateinit var userTracker: UserTracker + + private val testDispatcher = StandardTestDispatcher() + private val testScope = TestScope(testDispatcher) + + private lateinit var underTest: CommunalTutorialInteractor + private lateinit var keyguardRepository: FakeKeyguardRepository + private lateinit var keyguardInteractor: KeyguardInteractor + private lateinit var communalTutorialRepository: FakeCommunalTutorialRepository + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + val withDeps = KeyguardInteractorFactory.create() + keyguardInteractor = withDeps.keyguardInteractor + keyguardRepository = withDeps.repository + communalTutorialRepository = FakeCommunalTutorialRepository() + + underTest = + CommunalTutorialInteractor( + keyguardInteractor = keyguardInteractor, + communalTutorialRepository = communalTutorialRepository, + ) + + whenever(userTracker.userHandle).thenReturn(mock()) + } + + @Test + fun tutorialUnavailable_whenKeyguardNotVisible() = + testScope.runTest { + val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable) + communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED) + keyguardRepository.setKeyguardShowing(false) + assertThat(isTutorialAvailable).isFalse() + } + + @Test + fun tutorialUnavailable_whenTutorialIsCompleted() = + testScope.runTest { + val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable) + keyguardRepository.setKeyguardShowing(true) + keyguardRepository.setKeyguardOccluded(false) + communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) + assertThat(isTutorialAvailable).isFalse() + } + + @Test + fun tutorialAvailable_whenTutorialNotStarted() = + testScope.runTest { + val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable) + keyguardRepository.setKeyguardShowing(true) + keyguardRepository.setKeyguardOccluded(false) + communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED) + assertThat(isTutorialAvailable).isTrue() + } + + @Test + fun tutorialAvailable_whenTutorialIsStarted() = + testScope.runTest { + val isTutorialAvailable by collectLastValue(underTest.isTutorialAvailable) + keyguardRepository.setKeyguardShowing(true) + keyguardRepository.setKeyguardOccluded(false) + communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_STARTED) + assertThat(isTutorialAvailable).isTrue() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt index 41a8be9663b7..33a666700877 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt @@ -6,6 +6,7 @@ import androidx.constraintlayout.widget.ConstraintSet import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.ui.view.layout.sections.DefaultCommunalHubSection import com.android.systemui.communal.ui.view.layout.sections.DefaultCommunalWidgetSection import org.junit.Before import org.junit.Test @@ -18,6 +19,7 @@ import org.mockito.MockitoAnnotations @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest class DefaultCommunalBlueprintTest : SysuiTestCase() { + @Mock private lateinit var hubSection: DefaultCommunalHubSection @Mock private lateinit var widgetSection: DefaultCommunalWidgetSection private lateinit var blueprint: DefaultCommunalBlueprint @@ -25,13 +27,14 @@ class DefaultCommunalBlueprintTest : SysuiTestCase() { @Before fun setup() { MockitoAnnotations.initMocks(this) - blueprint = DefaultCommunalBlueprint(widgetSection) + blueprint = DefaultCommunalBlueprint(hubSection, widgetSection) } @Test fun addView() { val constraintLayout = ConstraintLayout(context, null) blueprint.replaceViews(null, constraintLayout) + verify(hubSection).addViews(constraintLayout) verify(widgetSection).addViews(constraintLayout) } @@ -39,6 +42,7 @@ class DefaultCommunalBlueprintTest : SysuiTestCase() { fun applyConstraints() { val cs = ConstraintSet() blueprint.applyConstraints(cs) + verify(hubSection).applyConstraints(cs) verify(widgetSection).applyConstraints(cs) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/demomode/DemoModeControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/demomode/DemoModeControllerTest.kt index 68ea7ebb3358..6c2e13691fba 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/demomode/DemoModeControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/demomode/DemoModeControllerTest.kt @@ -25,7 +25,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.demomode.DemoMode.ACTION_DEMO import com.android.systemui.demomode.DemoMode.COMMAND_STATUS import com.android.systemui.dump.DumpManager -import com.android.systemui.util.settings.FakeSettings +import com.android.systemui.util.settings.FakeGlobalSettings import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.launch @@ -48,7 +48,7 @@ class DemoModeControllerTest : SysuiTestCase() { @Mock private lateinit var dumpManager: DumpManager - private val globalSettings = FakeSettings() + private val globalSettings = FakeGlobalSettings() private val testDispatcher = UnconfinedTestDispatcher() private val testScope = TestScope(testDispatcher) 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 14c5ec0361f6..c12a581fb2c1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt @@ -36,7 +36,6 @@ import java.util.function.Consumer import org.junit.Assert import org.junit.Before import org.junit.Test -import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock import org.mockito.Mockito.anyBoolean import org.mockito.Mockito.anyString @@ -479,7 +478,7 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() { verify(flagManager, times(numReads)) .readFlagValue(eq(name), any<FlagSerializer<*>>()) verify(flagManager).nameToSettingsKey(eq(name)) - verify(globalSettings).putStringForUser(eq("key-$name"), eq(data), anyInt()) + verify(globalSettings).putString(eq("key-$name"), eq(data)) verify(flagManager).dispatchListenersAndMaybeRestart(eq(name), any()) } .verifyNoMoreInteractions() diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java index b1cf0517ddd1..2d3f69d95204 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java @@ -78,6 +78,8 @@ import com.android.systemui.statusbar.window.StatusBarWindowController; import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.util.RingerModeLiveData; import com.android.systemui.util.RingerModeTracker; +import com.android.systemui.util.settings.FakeGlobalSettings; +import com.android.systemui.util.settings.FakeSettings; import com.android.systemui.util.settings.GlobalSettings; import com.android.systemui.util.settings.SecureSettings; @@ -105,8 +107,8 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { @Mock private LockPatternUtils mLockPatternUtils; @Mock private BroadcastDispatcher mBroadcastDispatcher; @Mock private TelephonyListenerManager mTelephonyListenerManager; - @Mock private GlobalSettings mGlobalSettings; - @Mock private SecureSettings mSecureSettings; + private GlobalSettings mGlobalSettings; + private SecureSettings mSecureSettings; @Mock private Resources mResources; @Mock private ConfigurationController mConfigurationController; @Mock private UserTracker mUserTracker; @@ -148,6 +150,9 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { when(mResources.getConfiguration()).thenReturn( getContext().getResources().getConfiguration()); + mGlobalSettings = new FakeGlobalSettings(); + mSecureSettings = new FakeSettings(); + mGlobalActionsDialogLite = new GlobalActionsDialogLite(mContext, mWindowManagerFuncs, mAudioManager, @@ -592,8 +597,8 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { UserInfo currentUser = mockCurrentUser(FLAG_ADMIN); when(mGlobalActionsDialogLite.getCurrentUser()).thenReturn(currentUser); - when(mGlobalSettings.getIntForUser(Settings.Secure.BUGREPORT_IN_POWER_MENU, - 0, currentUser.id)).thenReturn(1); + mSecureSettings.putIntForUser(Settings.Secure.BUGREPORT_IN_POWER_MENU, 1, + currentUser.id); GlobalActionsDialogLite.BugReportAction bugReportAction = mGlobalActionsDialogLite.makeBugReportActionForTesting(); @@ -605,8 +610,8 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { UserInfo currentUser = mockCurrentUser(0); when(mGlobalActionsDialogLite.getCurrentUser()).thenReturn(currentUser); - doReturn(1).when(mGlobalSettings) - .getIntForUser(Settings.Secure.BUGREPORT_IN_POWER_MENU, 0, currentUser.id); + mSecureSettings.putIntForUser(Settings.Secure.BUGREPORT_IN_POWER_MENU, 1, + currentUser.id); GlobalActionsDialogLite.BugReportAction bugReportAction = mGlobalActionsDialogLite.makeBugReportActionForTesting(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt index faf97517ac59..977f1db44258 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt @@ -25,13 +25,13 @@ import android.provider.Settings.Secure.ZEN_DURATION_PROMPT import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.notification.EnableZenModeDialog -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.animation.Expandable 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.keyguard.shared.quickaffordance.ActivationState +import com.android.systemui.res.R import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.policy.ZenModeController import com.android.systemui.util.mockito.argumentCaptor diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt index 1e80fb69c107..26fcb234843d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt @@ -22,8 +22,8 @@ import android.content.res.Resources import android.provider.Settings import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase +import com.android.systemui.res.R import com.android.systemui.settings.FakeUserTracker import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots import com.android.systemui.util.FakeSharedPreferences @@ -70,8 +70,7 @@ class KeyguardQuickAffordanceLegacySettingSyncerTest : SysuiTestCase() { val resources: Resources = mock() whenever(resources.getStringArray(R.array.config_keyguardQuickAffordanceDefaults)) .thenReturn(emptyArray()) - whenever(resources.getBoolean(R.bool.custom_lockscreen_shortcuts_enabled)) - .thenReturn(true) + whenever(resources.getBoolean(R.bool.custom_lockscreen_shortcuts_enabled)).thenReturn(true) whenever(context.resources).thenReturn(resources) testDispatcher = UnconfinedTestDispatcher() diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt index 9ee22c89405d..b32905fd3b79 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt @@ -204,8 +204,7 @@ class KeyguardInteractorTest : SysuiTestCase() { fromScene = SceneKey.Gone, toScene = SceneKey.Lockscreen, progress = flowOf(0f), - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) runCurrent() assertThat(isAnimate).isFalse() diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt index e10815d9de61..a5d74572eb98 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt @@ -35,7 +35,6 @@ import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.ExperimentalCoroutinesApi import org.junit.Before import org.junit.Rule import org.junit.Test @@ -47,7 +46,6 @@ import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit -@ExperimentalCoroutinesApi @SmallTest @RunWith(AndroidJUnit4::class) class KeyguardKeyEventInteractorTest : SysuiTestCase() { @@ -130,73 +128,58 @@ class KeyguardKeyEventInteractorTest : SysuiTestCase() { } @Test - fun dispatchKeyEvent_menuActionUp_awakeKeyguard_showsPrimaryBouncer() { + fun dispatchKeyEvent_menuActionUp_interactiveKeyguard_collapsesShade() { powerInteractor.setAwakeForTest() whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) whenever(statusBarKeyguardViewManager.shouldDismissOnMenuPressed()).thenReturn(true) - verifyActionUpShowsPrimaryBouncer(KeyEvent.KEYCODE_MENU) + val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MENU) + assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue() + verify(shadeController).animateCollapseShadeForced() } @Test - fun dispatchKeyEvent_menuActionUp_awakeShadeLocked_collapsesShade() { + fun dispatchKeyEvent_menuActionUp_interactiveShadeLocked_collapsesShade() { powerInteractor.setAwakeForTest() whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED) whenever(statusBarKeyguardViewManager.shouldDismissOnMenuPressed()).thenReturn(true) - verifyActionUpCollapsesTheShade(KeyEvent.KEYCODE_MENU) + // action down: does NOT collapse the shade + val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU) + assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse() + verify(shadeController, never()).animateCollapseShadeForced() + + // action up: collapses the shade + val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MENU) + assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue() + verify(shadeController).animateCollapseShadeForced() } @Test - fun dispatchKeyEvent_menuActionUp_asleepKeyguard_neverCollapsesShade() { + fun dispatchKeyEvent_menuActionUp_nonInteractiveKeyguard_neverCollapsesShade() { powerInteractor.setAsleepForTest() whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) whenever(statusBarKeyguardViewManager.shouldDismissOnMenuPressed()).thenReturn(true) - verifyActionsDoNothing(KeyEvent.KEYCODE_MENU) - } - - @Test - fun dispatchKeyEvent_spaceActionUp_awakeKeyguard_collapsesShade() { - powerInteractor.setAwakeForTest() - whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) - whenever(statusBarKeyguardViewManager.primaryBouncerIsShowing()).thenReturn(false) - - verifyActionUpShowsPrimaryBouncer(KeyEvent.KEYCODE_SPACE) - } - - @Test - fun dispatchKeyEvent_spaceActionUp_shadeLocked_collapsesShade() { - powerInteractor.setAwakeForTest() - whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED) - - verifyActionUpCollapsesTheShade(KeyEvent.KEYCODE_SPACE) - } - - @Test - fun dispatchKeyEvent_enterActionUp_awakeKeyguard_showsPrimaryBouncer() { - powerInteractor.setAwakeForTest() - whenever(statusBarKeyguardViewManager.primaryBouncerIsShowing()).thenReturn(false) - whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) - - verifyActionUpShowsPrimaryBouncer(KeyEvent.KEYCODE_ENTER) + val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MENU) + assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isFalse() + verify(shadeController, never()).animateCollapseShadeForced() } @Test - fun dispatchKeyEvent_enterActionUp_awakeKeyguard_primaryBouncerAlreadyShowing() { + fun dispatchKeyEvent_spaceActionUp_interactiveKeyguard_collapsesShade() { powerInteractor.setAwakeForTest() - whenever(statusBarKeyguardViewManager.primaryBouncerIsShowing()).thenReturn(true) whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) - verifyActionsDoNothing(KeyEvent.KEYCODE_ENTER) - } - - @Test - fun dispatchKeyEvent_enterActionUp_shadeLocked_collapsesShade() { - powerInteractor.setAwakeForTest() - whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE_LOCKED) + // action down: does NOT collapse the shade + val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_SPACE) + assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse() + verify(shadeController, never()).animateCollapseShadeForced() - verifyActionUpCollapsesTheShade(KeyEvent.KEYCODE_ENTER) + // action up: collapses the shade + val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SPACE) + assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue() + verify(shadeController).animateCollapseShadeForced() } @Test @@ -266,42 +249,4 @@ class KeyguardKeyEventInteractorTest : SysuiTestCase() { .isFalse() verify(statusBarKeyguardViewManager, never()).interceptMediaKey(any()) } - - private fun verifyActionUpCollapsesTheShade(keycode: Int) { - // action down: does NOT collapse the shade - val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, keycode) - assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse() - verify(shadeController, never()).animateCollapseShadeForced() - - // action up: collapses the shade - val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, keycode) - assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue() - verify(shadeController).animateCollapseShadeForced() - } - - private fun verifyActionUpShowsPrimaryBouncer(keycode: Int) { - // action down: does NOT collapse the shade - val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, keycode) - assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse() - verify(statusBarKeyguardViewManager, never()).showPrimaryBouncer(any()) - - // action up: collapses the shade - val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, keycode) - assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue() - verify(statusBarKeyguardViewManager).showPrimaryBouncer(eq(true)) - } - - private fun verifyActionsDoNothing(keycode: Int) { - // action down: does nothing - val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, keycode) - assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse() - verify(shadeController, never()).animateCollapseShadeForced() - verify(statusBarKeyguardViewManager, never()).showPrimaryBouncer(any()) - - // action up: doesNothing - val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, keycode) - assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isFalse() - verify(shadeController, never()).animateCollapseShadeForced() - verify(statusBarKeyguardViewManager, never()).showPrimaryBouncer(any()) - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index 2cf0e77b5994..5d5ece0f8dcb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -433,7 +433,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED runTransitionAndSetWakefulness( - KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED) + KeyguardState.GONE, + KeyguardState.DREAMING_LOCKSCREEN_HOSTED + ) // WHEN the lockscreen hosted dream stops keyguardRepository.setIsActiveDreamLockscreenHosted(false) @@ -457,7 +459,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { testScope.runTest { // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED runTransitionAndSetWakefulness( - KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED) + KeyguardState.GONE, + KeyguardState.DREAMING_LOCKSCREEN_HOSTED + ) // WHEN biometrics succeeds with wake and unlock from dream mode keyguardRepository.setBiometricUnlockState( @@ -487,7 +491,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED runTransitionAndSetWakefulness( - KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED) + KeyguardState.GONE, + KeyguardState.DREAMING_LOCKSCREEN_HOSTED + ) // WHEN the primary bouncer is set to show bouncerRepository.setPrimaryShow(true) @@ -515,7 +521,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED runTransitionAndSetWakefulness( - KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED) + KeyguardState.GONE, + KeyguardState.DREAMING_LOCKSCREEN_HOSTED + ) // WHEN the device begins to sleep keyguardRepository.setIsActiveDreamLockscreenHosted(false) @@ -547,7 +555,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED runTransitionAndSetWakefulness( - KeyguardState.GONE, KeyguardState.DREAMING_LOCKSCREEN_HOSTED) + KeyguardState.GONE, + KeyguardState.DREAMING_LOCKSCREEN_HOSTED + ) // WHEN the keyguard is occluded and the lockscreen hosted dream stops keyguardRepository.setIsActiveDreamLockscreenHosted(false) @@ -783,7 +793,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { testScope.runTest { // GIVEN a prior transition has run to ALTERNATE_BOUNCER runTransitionAndSetWakefulness( - KeyguardState.LOCKSCREEN, KeyguardState.ALTERNATE_BOUNCER) + KeyguardState.LOCKSCREEN, + KeyguardState.ALTERNATE_BOUNCER + ) // WHEN the alternateBouncer stops showing and then the primary bouncer shows bouncerRepository.setPrimaryShow(true) @@ -808,7 +820,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // GIVEN a prior transition has run to ALTERNATE_BOUNCER bouncerRepository.setAlternateVisible(true) runTransitionAndSetWakefulness( - KeyguardState.LOCKSCREEN, KeyguardState.ALTERNATE_BOUNCER) + KeyguardState.LOCKSCREEN, + KeyguardState.ALTERNATE_BOUNCER + ) // GIVEN the primary bouncer isn't showing, aod available and starting to sleep bouncerRepository.setPrimaryShow(false) @@ -838,7 +852,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // GIVEN a prior transition has run to ALTERNATE_BOUNCER bouncerRepository.setAlternateVisible(true) runTransitionAndSetWakefulness( - KeyguardState.LOCKSCREEN, KeyguardState.ALTERNATE_BOUNCER) + KeyguardState.LOCKSCREEN, + KeyguardState.ALTERNATE_BOUNCER + ) // GIVEN the primary bouncer isn't showing, aod not available and starting to sleep // to sleep @@ -869,7 +885,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // GIVEN a prior transition has run to ALTERNATE_BOUNCER bouncerRepository.setAlternateVisible(true) runTransitionAndSetWakefulness( - KeyguardState.LOCKSCREEN, KeyguardState.ALTERNATE_BOUNCER) + KeyguardState.LOCKSCREEN, + KeyguardState.ALTERNATE_BOUNCER + ) // GIVEN the primary bouncer isn't showing and device not sleeping bouncerRepository.setPrimaryShow(false) @@ -980,7 +998,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // GIVEN a prior transition has run to PRIMARY_BOUNCER bouncerRepository.setPrimaryShow(true) runTransitionAndSetWakefulness( - KeyguardState.DREAMING_LOCKSCREEN_HOSTED, KeyguardState.PRIMARY_BOUNCER) + KeyguardState.DREAMING_LOCKSCREEN_HOSTED, + KeyguardState.PRIMARY_BOUNCER + ) // WHEN the primary bouncer stops showing and lockscreen hosted dream still active bouncerRepository.setPrimaryShow(false) @@ -1161,6 +1181,57 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + fun dreamingToOccluded() = + testScope.runTest { + // GIVEN a prior transition has run to DREAMING + keyguardRepository.setDreaming(true) + runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.DREAMING) + runCurrent() + + // WHEN the keyguard is occluded and device wakes up and is no longer dreaming + keyguardRepository.setDreaming(false) + keyguardRepository.setKeyguardOccluded(true) + powerInteractor.setAwakeForTest() + runCurrent() + + val info = + withArgCaptor<TransitionInfo> { + verify(transitionRepository).startTransition(capture(), anyBoolean()) + } + // THEN a transition to OCCLUDED should occur + assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor") + assertThat(info.from).isEqualTo(KeyguardState.DREAMING) + assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED) + assertThat(info.animator).isNotNull() + + coroutineContext.cancelChildren() + } + + @Test + fun lockscreenToOccluded() = + testScope.runTest { + // GIVEN a prior transition has run to LOCKSCREEN + runTransitionAndSetWakefulness(KeyguardState.GONE, KeyguardState.LOCKSCREEN) + runCurrent() + + // WHEN the keyguard is occluded + keyguardRepository.setKeyguardOccluded(true) + runCurrent() + + val info = + withArgCaptor<TransitionInfo> { + verify(transitionRepository).startTransition(capture(), anyBoolean()) + } + // THEN a transition to OCCLUDED should occur + assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor") + assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN) + assertThat(info.to).isEqualTo(KeyguardState.OCCLUDED) + assertThat(info.animator).isNotNull() + + coroutineContext.cancelChildren() + } + + @Test fun aodToOccluded() = testScope.runTest { // GIVEN a prior transition has run to AOD @@ -1286,8 +1357,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } private suspend fun TestScope.runTransitionAndSetWakefulness( - from: KeyguardState, - to: KeyguardState + from: KeyguardState, + to: KeyguardState ) { transitionRepository.sendTransitionStep( TransitionStep( diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt index 7940b450b932..50ee02609635 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt @@ -23,6 +23,7 @@ import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.ui.view.layout.sections.CommunalTutorialIndicatorSection import com.android.systemui.keyguard.shared.model.KeyguardBlueprint import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.view.KeyguardRootView @@ -65,6 +66,7 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() { @Mock private lateinit var splitShadeGuidelines: SplitShadeGuidelines @Mock private lateinit var aodNotificationIconsSection: AodNotificationIconsSection @Mock private lateinit var aodBurnInSection: AodBurnInSection + @Mock private lateinit var communalTutorialIndicatorSection: CommunalTutorialIndicatorSection @Before fun setup() { @@ -83,6 +85,7 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() { splitShadeGuidelines, aodNotificationIconsSection, aodBurnInSection, + communalTutorialIndicatorSection, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt index 93640975901b..d7802aabb298 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt @@ -60,10 +60,7 @@ class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) repository = FakeKeyguardTransitionRepository() val featureFlags = - FakeFeatureFlags().apply { - set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false) - set(Flags.UDFPS_NEW_TOUCH_DETECTION, true) - } + FakeFeatureFlags().apply { set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false) } val interactor = KeyguardTransitionInteractorFactory.create( scope = TestScope().backgroundScope, diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt index fb0a4be25aa0..59d81049ad46 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt @@ -48,7 +48,6 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito import com.android.internal.logging.InstanceId import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.InstanceIdSequenceFake -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dump.DumpManager @@ -63,6 +62,7 @@ import com.android.systemui.media.controls.util.MediaControllerFactory import com.android.systemui.media.controls.util.MediaFlags import com.android.systemui.media.controls.util.MediaUiEventLogger import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.res.R import com.android.systemui.statusbar.SbnBuilder import com.android.systemui.tuner.TunerService import com.android.systemui.util.concurrency.FakeExecutor @@ -2204,6 +2204,85 @@ class MediaDataManagerTest : SysuiTestCase() { } @Test + fun testSessionPlayer_sessionDestroyed_noResume_fullyRemoved() { + whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true) + addPlaybackStateAction() + + // When a media control with PlaybackState actions is added, times out, + // and then the session is destroyed + addNotificationAndLoad() + val data = mediaDataCaptor.value + assertThat(data.active).isTrue() + mediaDataManager.setTimedOut(KEY, timedOut = true) + sessionCallbackCaptor.value.invoke(KEY) + + // It is fully removed. + verify(listener).onMediaDataRemoved(eq(KEY)) + verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) + verify(listener, never()) + .onMediaDataLoaded( + eq(PACKAGE_NAME), + eq(KEY), + capture(mediaDataCaptor), + eq(true), + eq(0), + eq(false) + ) + } + + @Test + fun testSessionPlayer_destroyedWhileActive_noResume_fullyRemoved() { + whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true) + addPlaybackStateAction() + + // When a media control using session actions is added, and then the session is destroyed + // without timing out first + addNotificationAndLoad() + val data = mediaDataCaptor.value + assertThat(data.active).isTrue() + sessionCallbackCaptor.value.invoke(KEY) + + // It is fully removed + verify(listener).onMediaDataRemoved(eq(KEY)) + verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) + verify(listener, never()) + .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean()) + } + + @Test + fun testSessionPlayer_canResume_destroyedWhileActive_setToResume() { + whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true) + addPlaybackStateAction() + + // When a media control using session actions and that does allow resumption is added, + addNotificationAndLoad() + val dataResumable = mediaDataCaptor.value.copy(resumeAction = Runnable {}) + mediaDataManager.onMediaDataLoaded(KEY, null, dataResumable) + + // And then the session is destroyed without timing out first + sessionCallbackCaptor.value.invoke(KEY) + + // It is converted to a resume player + verify(listener) + .onMediaDataLoaded( + eq(PACKAGE_NAME), + eq(KEY), + capture(mediaDataCaptor), + eq(true), + eq(0), + eq(false) + ) + assertThat(mediaDataCaptor.value.resumption).isTrue() + assertThat(mediaDataCaptor.value.active).isFalse() + verify(logger) + .logActiveConvertedToResume( + anyInt(), + eq(PACKAGE_NAME), + eq(mediaDataCaptor.value.instanceId) + ) + } + + @Test fun testSessionDestroyed_noNotificationKey_stillRemoved() { whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true) whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt index 85d3fbad5a6e..deefab670c71 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt @@ -21,6 +21,7 @@ import android.bluetooth.BluetoothLeBroadcastMetadata import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.graphics.drawable.Drawable +import android.media.MediaRoute2Info import android.media.MediaRouter2Manager import android.media.RoutingSessionInfo import android.media.session.MediaController @@ -34,15 +35,18 @@ import com.android.settingslib.bluetooth.LocalBluetoothManager import com.android.settingslib.bluetooth.LocalBluetoothProfileManager import com.android.settingslib.media.LocalMediaManager import com.android.settingslib.media.MediaDevice -import com.android.systemui.res.R +import com.android.settingslib.media.PhoneMediaDevice import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager +import com.android.systemui.flags.FakeFeatureFlagsClassic +import com.android.systemui.flags.Flags import com.android.systemui.media.controls.MediaTestUtils import com.android.systemui.media.controls.models.player.MediaData import com.android.systemui.media.controls.models.player.MediaDeviceData import com.android.systemui.media.controls.util.MediaControllerFactory import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManager import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManagerFactory +import com.android.systemui.res.R import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.eq @@ -95,6 +99,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { @Mock private lateinit var device: MediaDevice @Mock private lateinit var icon: Drawable @Mock private lateinit var route: RoutingSessionInfo + @Mock private lateinit var selectedRoute: MediaRoute2Info @Mock private lateinit var controller: MediaController @Mock private lateinit var playbackInfo: PlaybackInfo @Mock private lateinit var configurationController: ConfigurationController @@ -107,6 +112,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { private lateinit var session: MediaSession private lateinit var mediaData: MediaData @JvmField @Rule val mockito = MockitoJUnit.rule() + private val featureFlags = FakeFeatureFlagsClassic() @Before fun setUp() { @@ -124,7 +130,8 @@ public class MediaDeviceManagerTest : SysuiTestCase() { localBluetoothManager, fakeFgExecutor, fakeBgExecutor, - dumpster + dumpster, + featureFlags, ) manager.addListener(listener) @@ -143,6 +150,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { MediaTestUtils.emptyMediaData.copy(packageName = PACKAGE, token = session.sessionToken) whenever(controllerFactory.create(session.sessionToken)).thenReturn(controller) setupLeAudioConfiguration(false) + featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, false) } @After @@ -454,9 +462,54 @@ public class MediaDeviceManagerTest : SysuiTestCase() { } @Test - fun mr2ReturnsRouteWithNullName_useLocalDeviceName() { + fun mr2ReturnsSystemRouteWithNullName_isPhone_usePhoneName() { + featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) + // When the routing session name is null, and is a system session for a PhoneMediaDevice + val phoneDevice = mock(PhoneMediaDevice::class.java) + whenever(phoneDevice.iconWithoutBackground).thenReturn(icon) + whenever(lmm.currentConnectedDevice).thenReturn(phoneDevice) + whenever(route.isSystemSession).thenReturn(true) + + whenever(route.name).thenReturn(null) + whenever(mr2.getSelectedRoutes(any())).thenReturn(listOf(selectedRoute)) + whenever(selectedRoute.name).thenReturn(REMOTE_DEVICE_NAME) + whenever(selectedRoute.type).thenReturn(MediaRoute2Info.TYPE_BUILTIN_SPEAKER) + + manager.onMediaDataLoaded(KEY, null, mediaData) + fakeBgExecutor.runAllReady() + fakeFgExecutor.runAllReady() + + // Then the device name is the PhoneMediaDevice string + val data = captureDeviceData(KEY) + assertThat(data.name) + .isEqualTo( + context.getString(com.android.settingslib.R.string.media_transfer_this_device_name) + ) + } + + @Test + fun mr2ReturnsSystemRouteWithNullName_useSelectedRouteName() { + featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) + // When the routing session does not have a name, and is a system session + whenever(route.name).thenReturn(null) + whenever(mr2.getSelectedRoutes(any())).thenReturn(listOf(selectedRoute)) + whenever(selectedRoute.name).thenReturn(REMOTE_DEVICE_NAME) + whenever(route.isSystemSession).thenReturn(true) + + manager.onMediaDataLoaded(KEY, null, mediaData) + fakeBgExecutor.runAllReady() + fakeFgExecutor.runAllReady() + + // Then the device name is the selected route name + val data = captureDeviceData(KEY) + assertThat(data.name).isEqualTo(REMOTE_DEVICE_NAME) + } + + @Test + fun mr2ReturnsNonSystemRouteWithNullName_useLocalDeviceName() { // GIVEN that MR2Manager returns a routing session that does not have a name whenever(route.name).thenReturn(null) + whenever(route.isSystemSession).thenReturn(false) // WHEN a notification is added manager.onMediaDataLoaded(KEY, null, mediaData) fakeBgExecutor.runAllReady() @@ -672,13 +725,108 @@ public class MediaDeviceManagerTest : SysuiTestCase() { assertThat(data.showBroadcastButton).isFalse() } - fun captureCallback(): LocalMediaManager.DeviceCallback { + // Duplicates of above tests with MEDIA_DEVICE_NAME_FIX enabled + + @Test + fun loadMediaDataWithNullToken_withNameFix() { + featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) + manager.onMediaDataLoaded(KEY, null, mediaData.copy(token = null)) + fakeBgExecutor.runAllReady() + fakeFgExecutor.runAllReady() + val data = captureDeviceData(KEY) + assertThat(data.enabled).isTrue() + assertThat(data.name).isEqualTo(DEVICE_NAME) + } + + @Test + fun onAboutToConnectDeviceAdded_findsDeviceInfoFromAddress_withNameFix() { + featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) + manager.onMediaDataLoaded(KEY, null, mediaData) + // Run and reset the executors and listeners so we only focus on new events. + fakeBgExecutor.runAllReady() + fakeFgExecutor.runAllReady() + reset(listener) + + // Ensure we'll get device info when using the address + val fullMediaDevice = mock(MediaDevice::class.java) + val address = "fakeAddress" + val nameFromDevice = "nameFromDevice" + val iconFromDevice = mock(Drawable::class.java) + whenever(lmm.getMediaDeviceById(eq(address))).thenReturn(fullMediaDevice) + whenever(fullMediaDevice.name).thenReturn(nameFromDevice) + whenever(fullMediaDevice.iconWithoutBackground).thenReturn(iconFromDevice) + + // WHEN the about-to-connect device changes to non-null + val deviceCallback = captureCallback() + val nameFromParam = "nameFromParam" + val iconFromParam = mock(Drawable::class.java) + deviceCallback.onAboutToConnectDeviceAdded(address, nameFromParam, iconFromParam) + assertThat(fakeFgExecutor.runAllReady()).isEqualTo(1) + + // THEN the about-to-connect device based on the address is returned + val data = captureDeviceData(KEY) + assertThat(data.enabled).isTrue() + assertThat(data.name).isEqualTo(nameFromDevice) + assertThat(data.name).isNotEqualTo(nameFromParam) + assertThat(data.icon).isEqualTo(iconFromDevice) + assertThat(data.icon).isNotEqualTo(iconFromParam) + } + + @Test + fun deviceNameFromMR2RouteInfo_withNameFix() { + featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) + // GIVEN that MR2Manager returns a valid routing session + whenever(route.name).thenReturn(REMOTE_DEVICE_NAME) + // WHEN a notification is added + manager.onMediaDataLoaded(KEY, null, mediaData) + fakeBgExecutor.runAllReady() + fakeFgExecutor.runAllReady() + // THEN it uses the route name (instead of device name) + val data = captureDeviceData(KEY) + assertThat(data.enabled).isTrue() + assertThat(data.name).isEqualTo(REMOTE_DEVICE_NAME) + } + + @Test + fun deviceDisabledWhenMR2ReturnsNullRouteInfo_withNameFix() { + featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) + // GIVEN that MR2Manager returns null for routing session + whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null) + // WHEN a notification is added + manager.onMediaDataLoaded(KEY, null, mediaData) + fakeBgExecutor.runAllReady() + fakeFgExecutor.runAllReady() + // THEN the device is disabled and name is set to null + val data = captureDeviceData(KEY) + assertThat(data.enabled).isFalse() + assertThat(data.name).isNull() + } + + @Test + fun mr2ReturnsNonSystemRouteWithNullName_useLocalDeviceName_withNameFix() { + featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true) + // GIVEN that MR2Manager returns a routing session that does not have a name + whenever(route.name).thenReturn(null) + whenever(route.isSystemSession).thenReturn(false) + // WHEN a notification is added + manager.onMediaDataLoaded(KEY, null, mediaData) + fakeBgExecutor.runAllReady() + fakeFgExecutor.runAllReady() + // THEN the device is enabled and uses the current connected device name + val data = captureDeviceData(KEY) + assertThat(data.name).isEqualTo(DEVICE_NAME) + assertThat(data.enabled).isTrue() + } + + // End duplicate tests + + private fun captureCallback(): LocalMediaManager.DeviceCallback { val captor = ArgumentCaptor.forClass(LocalMediaManager.DeviceCallback::class.java) verify(lmm).registerCallback(captor.capture()) return captor.getValue() } - fun setupBroadcastCallback(): BluetoothLeBroadcast.Callback { + private fun setupBroadcastCallback(): BluetoothLeBroadcast.Callback { val callback: BluetoothLeBroadcast.Callback = object : BluetoothLeBroadcast.Callback { override fun onBroadcastStarted(reason: Int, broadcastId: Int) {} @@ -699,7 +847,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { return callback } - fun setupLeAudioConfiguration(isLeAudio: Boolean) { + private fun setupLeAudioConfiguration(isLeAudio: Boolean) { whenever(localBluetoothManager.profileManager).thenReturn(localBluetoothProfileManager) whenever(localBluetoothProfileManager.leAudioBroadcastProfile) .thenReturn(localBluetoothLeBroadcast) @@ -707,7 +855,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { whenever(localBluetoothLeBroadcast.appSourceName).thenReturn(BROADCAST_APP_NAME) } - fun setupBroadcastPackage(currentName: String) { + private fun setupBroadcastPackage(currentName: String) { whenever(lmm.packageName).thenReturn(PACKAGE) whenever(packageManager.getApplicationInfo(eq(PACKAGE), anyInt())) .thenReturn(applicationInfo) @@ -715,7 +863,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { context.setMockPackageManager(packageManager) } - fun captureDeviceData(key: String, oldKey: String? = null): MediaDeviceData { + private fun captureDeviceData(key: String, oldKey: String? = null): MediaDeviceData { val captor = ArgumentCaptor.forClass(MediaDeviceData::class.java) verify(listener).onMediaDeviceChanged(eq(key), eq(oldKey), captor.capture()) return captor.getValue() diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt index de57b603c7fe..5296f1abd756 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt @@ -24,7 +24,6 @@ import android.view.ViewGroup import android.widget.FrameLayout import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardViewController -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq import com.android.systemui.dreams.DreamOverlayStateController @@ -32,6 +31,7 @@ import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.media.controls.pipeline.MediaDataManager import com.android.systemui.media.dream.MediaDreamComplication import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.res.R import com.android.systemui.shade.ShadeExpansionStateManager import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.SysuiStatusBarStateController 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 f25cd24dfcb0..34360d2ddd5c 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 @@ -7,12 +7,16 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.mediaprojection.appselector.data.RecentTask import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider +import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver +import com.android.systemui.shared.recents.model.ThumbnailData import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -34,6 +38,8 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { private val view: MediaProjectionAppSelectorView = mock() private val policyResolver: ScreenCaptureDevicePolicyResolver = mock() + private val thumbnailLoader = FakeThumbnailLoader() + private val controller = MediaProjectionAppSelectorController( taskListProvider, @@ -42,7 +48,8 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { personalUserHandle, scope, appSelectorComponentName, - callerPackageName + callerPackageName, + thumbnailLoader, ) @Before @@ -69,6 +76,22 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { } @Test + fun init_refreshesThumbnailsOfForegroundTasks() = runTest { + val tasks = + listOf( + createRecentTask(taskId = 1, isForegroundTask = false), + createRecentTask(taskId = 2, isForegroundTask = true), + createRecentTask(taskId = 3, isForegroundTask = true), + createRecentTask(taskId = 4, isForegroundTask = false), + ) + taskListProvider.tasks = tasks + + controller.init() + + assertThat(thumbnailLoader.capturedTaskIds).containsExactly(2, 3) + } + + @Test fun initMultipleRecentTasksWithoutAppSelectorTask_bindsListInTheSameOrder() { val tasks = listOf( @@ -188,14 +211,16 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { private fun createRecentTask( taskId: Int, topActivityComponent: ComponentName? = null, - userId: Int = personalUserHandle.identifier + userId: Int = personalUserHandle.identifier, + isForegroundTask: Boolean = false ): RecentTask { return RecentTask( taskId = taskId, topActivityComponent = topActivityComponent, baseIntentComponent = ComponentName("com", "Test"), userId = userId, - colorBackground = 0 + colorBackground = 0, + isForegroundTask = isForegroundTask, ) } @@ -205,4 +230,18 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() { override suspend fun loadRecentTasks(): List<RecentTask> = tasks } + + private class FakeThumbnailLoader : RecentTaskThumbnailLoader { + + val capturedTaskIds = mutableListOf<Int>() + + override suspend fun loadThumbnail(taskId: Int): ThumbnailData? { + return null + } + + override suspend fun captureThumbnail(taskId: Int): ThumbnailData? { + capturedTaskIds += taskId + return null + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt new file mode 100644 index 000000000000..db275ec190ac --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt @@ -0,0 +1,109 @@ +package com.android.systemui.mediaprojection.appselector.data + +import android.app.WindowConfiguration +import android.content.ComponentName +import android.content.res.Configuration +import android.graphics.ColorSpace +import android.graphics.Point +import android.graphics.Rect +import android.hardware.HardwareBuffer +import android.testing.AndroidTestingRunner +import android.view.Surface +import android.window.TaskSnapshot +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.shared.recents.model.ThumbnailData +import com.android.systemui.shared.system.ActivityManagerWrapper +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidTestingRunner::class) +@SmallTest +@OptIn(ExperimentalCoroutinesApi::class) +class ActivityTaskManagerThumbnailLoaderTest : SysuiTestCase() { + + private val dispatcher = UnconfinedTestDispatcher() + private val testScope = TestScope(dispatcher) + private val activityManager = mock<ActivityManagerWrapper>() + private val loader = ActivityTaskManagerThumbnailLoader(dispatcher, activityManager) + + @Test + fun loadThumbnail_emptyThumbnail_returnsNull() = + testScope.runTest { + val taskId = 123 + val isLowResolution = false + val thumbnailData = ThumbnailData() + whenever(activityManager.getTaskThumbnail(taskId, isLowResolution)) + .thenReturn(thumbnailData) + + assertThat(loader.loadThumbnail(taskId)).isNull() + } + + @Test + fun loadThumbnail_thumbnailAvailable_returnsThumbnailData() = + testScope.runTest { + val taskId = 123 + val isLowResolution = false + val snapshot = createTaskSnapshot() + val thumbnailData = ThumbnailData(snapshot) + whenever(activityManager.getTaskThumbnail(taskId, isLowResolution)) + .thenReturn(thumbnailData) + + assertThat(loader.loadThumbnail(taskId)).isEqualTo(thumbnailData) + } + + @Test + fun captureThumbnail_emptyThumbnail_returnsNull() = + testScope.runTest { + val taskId = 321 + val emptyThumbnailData = ThumbnailData() + + whenever(activityManager.takeTaskThumbnail(taskId)).thenReturn(emptyThumbnailData) + + assertThat(loader.captureThumbnail(taskId)).isNull() + } + + @Test + fun captureThumbnail_thumbnailAvailable_returnsThumbnailData() = + testScope.runTest { + val taskId = 321 + val thumbnailData = ThumbnailData(createTaskSnapshot()) + + whenever(activityManager.takeTaskThumbnail(taskId)).thenReturn(thumbnailData) + + assertThat(loader.captureThumbnail(taskId)).isEqualTo(thumbnailData) + } + + private fun createTaskSnapshot() = + TaskSnapshot( + /* id= */ 123, + /* captureTime= */ 0, + /* topActivityComponent= */ ComponentName("package", "class"), + /* snapshot= */ HardwareBuffer.create( + /* width= */ 100, + /* height= */ 100, + HardwareBuffer.RGBA_8888, + /* layers= */ 1, + /* usage= */ HardwareBuffer.USAGE_CPU_READ_OFTEN + ), + ColorSpace.get(ColorSpace.Named.SRGB), + Configuration.ORIENTATION_PORTRAIT, + Surface.ROTATION_0, + /* taskSize= */ Point(100, 100), + /* contentInsets= */ Rect(), + /* letterboxInsets= */ Rect(), + /* isLowResolution= */ false, + /* isRealSnapshot= */ true, + WindowConfiguration.WINDOWING_MODE_FULLSCREEN, + /* appearance= */ 0, + /* isTranslucent= */ false, + /* hasImeSurface= */ false + ) +} 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 d35a21236ae8..2c7ee56e9408 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 @@ -11,7 +11,7 @@ import com.android.systemui.util.mockito.whenever import com.android.wm.shell.recents.RecentTasks import com.android.wm.shell.util.GroupedRecentTaskInfo import com.google.common.truth.Truth.assertThat -import java.util.* +import java.util.Optional import java.util.function.Consumer import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking @@ -52,12 +52,7 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() { val result = runBlocking { recentTaskListProvider.loadRecentTasks() } - assertThat(result) - .containsExactly( - createRecentTask(taskId = 1), - createRecentTask(taskId = 2), - createRecentTask(taskId = 3), - ) + assertThat(result.map { it.taskId }).containsExactly(1, 2, 3).inOrder() } @Test @@ -66,8 +61,7 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() { val result = runBlocking { recentTaskListProvider.loadRecentTasks() } - assertThat(result) - .containsExactly(createRecentTask(taskId = 1), createRecentTask(taskId = 2)) + assertThat(result.map { it.taskId }).containsExactly(1, 2).inOrder() } @Test @@ -81,15 +75,46 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() { val result = runBlocking { recentTaskListProvider.loadRecentTasks() } - assertThat(result) - .containsExactly( - createRecentTask(taskId = 1), - createRecentTask(taskId = 2), - createRecentTask(taskId = 3), - createRecentTask(taskId = 4), - createRecentTask(taskId = 5), - createRecentTask(taskId = 6), - ) + assertThat(result.map { it.taskId }).containsExactly(1, 2, 3, 4, 5, 6).inOrder() + } + + @Test + fun loadRecentTasks_singleTask_returnsTaskAsNotForeground() { + givenRecentTasks( + createSingleTask(taskId = 1), + ) + + val result = runBlocking { recentTaskListProvider.loadRecentTasks() } + + assertThat(result[0].isForegroundTask).isFalse() + } + + @Test + fun loadRecentTasks_multipleTasks_returnsSecondTaskAsForegroundTask() { + givenRecentTasks( + createSingleTask(taskId = 1), + createSingleTask(taskId = 2), + createSingleTask(taskId = 3), + ) + + val result = runBlocking { recentTaskListProvider.loadRecentTasks() } + + assertThat(result.map { it.isForegroundTask }).containsExactly(false, true, false).inOrder() + } + + @Test + fun loadRecentTasks_secondTaskIsGrouped_marksBothGroupedTasksAsForeground() { + givenRecentTasks( + createSingleTask(taskId = 1), + createTaskPair(taskId1 = 2, taskId2 = 3), + createSingleTask(taskId = 4), + ) + + val result = runBlocking { recentTaskListProvider.loadRecentTasks() } + + assertThat(result.map { it.isForegroundTask }) + .containsExactly(false, true, true, false) + .inOrder() } @Suppress("UNCHECKED_CAST") @@ -106,7 +131,8 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() { userId = 0, topActivityComponent = null, baseIntentComponent = null, - colorBackground = null + colorBackground = null, + isForegroundTask = false, ) private fun createSingleTask(taskId: Int): GroupedRecentTaskInfo = diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java index 8019fb513be2..6248bb1009dc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java @@ -57,7 +57,7 @@ import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.util.NotificationChannels; -import com.android.systemui.util.settings.FakeSettings; +import com.android.systemui.util.settings.FakeGlobalSettings; import com.android.systemui.util.settings.GlobalSettings; import org.junit.Before; @@ -77,7 +77,7 @@ public class PowerNotificationWarningsTest extends SysuiTestCase { public static final String FORMATTED_45M = "0h 45m"; public static final String FORMATTED_HOUR = "1h 0m"; private final NotificationManager mMockNotificationManager = mock(NotificationManager.class); - private final GlobalSettings mGlobalSettings = new FakeSettings(); + private final GlobalSettings mGlobalSettings = new FakeGlobalSettings(); private PowerNotificationWarnings mPowerNotificationWarnings; @Mock diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/SettingObserverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/UserSettingObserverTest.kt index 4be689065d3f..8f06fe2e3050 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/SettingObserverTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/UserSettingObserverTest.kt @@ -35,7 +35,7 @@ private typealias Callback = (Int, Boolean) -> Unit @SmallTest @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper -class SettingObserverTest : SysuiTestCase() { +class UserSettingObserverTest : SysuiTestCase() { companion object { private const val TEST_SETTING = "setting" @@ -46,7 +46,7 @@ class SettingObserverTest : SysuiTestCase() { } private lateinit var testableLooper: TestableLooper - private lateinit var setting: SettingObserver + private lateinit var setting: UserSettingObserver private lateinit var secureSettings: SecureSettings private lateinit var callback: Callback @@ -56,17 +56,19 @@ class SettingObserverTest : SysuiTestCase() { testableLooper = TestableLooper.get(this) secureSettings = FakeSettings() - setting = object : SettingObserver( - secureSettings, - Handler(testableLooper.looper), - TEST_SETTING, - USER, - DEFAULT_VALUE - ) { - override fun handleValueChanged(value: Int, observedChange: Boolean) { - callback(value, observedChange) + setting = + object : + UserSettingObserver( + secureSettings, + Handler(testableLooper.looper), + TEST_SETTING, + USER, + DEFAULT_VALUE + ) { + override fun handleValueChanged(value: Int, observedChange: Boolean) { + callback(value, observedChange) + } } - } // Default empty callback callback = { _, _ -> Unit } @@ -162,4 +164,4 @@ class SettingObserverTest : SysuiTestCase() { setting.isListening = true assertThat(setting.value).isEqualTo(DEFAULT_VALUE) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java index 67587e3a8914..6cc52d70611a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java @@ -29,12 +29,14 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.doAnswer; 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.compat.CompatChanges; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -73,16 +75,18 @@ import org.mockito.MockitoSession; @SmallTest @RunWith(AndroidJUnit4.class) public class TileLifecycleManagerTest extends SysuiTestCase { - private static final int TEST_FAIL_TIMEOUT = 5000; private final PackageManagerAdapter mMockPackageManagerAdapter = mock(PackageManagerAdapter.class); private final BroadcastDispatcher mMockBroadcastDispatcher = mock(BroadcastDispatcher.class); private final IQSTileService.Stub mMockTileService = mock(IQSTileService.Stub.class); + private final ActivityManager mActivityManager = mock(ActivityManager.class); + private ComponentName mTileServiceComponentName; private Intent mTileServiceIntent; private UserHandle mUser; + private FakeSystemClock mClock; private FakeExecutor mExecutor; private HandlerThread mThread; private Handler mHandler; @@ -112,13 +116,15 @@ public class TileLifecycleManagerTest extends SysuiTestCase { mThread = new HandlerThread("TestThread"); mThread.start(); mHandler = Handler.createAsync(mThread.getLooper()); - mExecutor = new FakeExecutor(new FakeSystemClock()); + mClock = new FakeSystemClock(); + mExecutor = new FakeExecutor(mClock); mStateManager = new TileLifecycleManager(mHandler, mWrappedContext, mock(IQSService.class), mMockPackageManagerAdapter, mMockBroadcastDispatcher, mTileServiceIntent, mUser, + mActivityManager, mExecutor); } @@ -294,11 +300,32 @@ public class TileLifecycleManagerTest extends SysuiTestCase { mStateManager.onStartListening(); mStateManager.executeSetBindService(true); mExecutor.runAllReady(); - mStateManager.setBindRetryDelay(0); - mExecutor.runAllReady(); mStateManager.onServiceDisconnected(mTileServiceComponentName); + mClock.advanceTime(5000); + + // Two calls: one for the first bind, one for the restart. + verifyBind(2); + verify(mMockTileService, times(2)).onStartListening(); + } + + @Test + public void testKillProcessLowMemory() throws Exception { + doAnswer(invocation -> { + ActivityManager.MemoryInfo memoryInfo = invocation.getArgument(0); + memoryInfo.lowMemory = true; + return null; + }).when(mActivityManager).getMemoryInfo(any()); + mStateManager.onStartListening(); + mStateManager.executeSetBindService(true); mExecutor.runAllReady(); + mStateManager.onServiceDisconnected(mTileServiceComponentName); + + // Longer delay than a regular one + mClock.advanceTime(5000); + verifyBind(1); + verify(mMockTileService, times(1)).onStartListening(); + mClock.advanceTime(20000); // Two calls: one for the first bind, one for the restart. verifyBind(2); verify(mMockTileService, times(2)).onStartListening(); @@ -319,6 +346,7 @@ public class TileLifecycleManagerTest extends SysuiTestCase { mMockBroadcastDispatcher, mTileServiceIntent, mUser, + mActivityManager, mExecutor); manager.executeSetBindService(true); @@ -340,6 +368,7 @@ public class TileLifecycleManagerTest extends SysuiTestCase { mMockBroadcastDispatcher, mTileServiceIntent, mUser, + mActivityManager, mExecutor); manager.executeSetBindService(true); @@ -361,6 +390,7 @@ public class TileLifecycleManagerTest extends SysuiTestCase { mMockBroadcastDispatcher, mTileServiceIntent, mUser, + mActivityManager, mExecutor); manager.executeSetBindService(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java index 4bc16a52f8dc..d0118218134c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java @@ -304,7 +304,7 @@ public class TileServicesTest extends SysuiTestCase { CustomTileAddedRepository customTileAddedRepository, DelayableExecutor executor) { super(host, handlerProvider, broadcastDispatcher, userTracker, keyguardStateController, commandQueue, statusBarIconController, panelInteractor, - customTileAddedRepository, executor); + mTileLifecycleManagerFactory, customTileAddedRepository, executor); } @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt index 4ada44c40f49..9b1f8303f1ec 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt @@ -25,7 +25,6 @@ import android.view.ContextThemeWrapper import androidx.test.filters.SmallTest import com.android.settingslib.Utils import com.android.settingslib.drawable.UserIconDrawable -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.common.shared.model.ContentDescription @@ -35,6 +34,7 @@ import com.android.systemui.qs.FakeFgsManagerController import com.android.systemui.qs.QSSecurityFooterUtils import com.android.systemui.qs.footer.FooterActionsTestUtils import com.android.systemui.qs.footer.domain.model.SecurityButtonConfig +import com.android.systemui.res.R import com.android.systemui.security.data.model.SecurityModel import com.android.systemui.settings.FakeUserTracker import com.android.systemui.statusbar.policy.FakeSecurityController @@ -44,7 +44,7 @@ import com.android.systemui.statusbar.policy.MockUserSwitcherControllerWrapper import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.nullable -import com.android.systemui.util.settings.FakeSettings +import com.android.systemui.util.settings.FakeGlobalSettings import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.launch @@ -128,7 +128,7 @@ class FooterActionsViewModelTest : SysuiTestCase() { fun userSwitcher() = runTest { val picture: Drawable = mock() val userInfoController = FakeUserInfoController(FakeInfo(picture = picture)) - val settings = FakeSettings() + val settings = FakeGlobalSettings() val userId = 42 val userTracker = FakeUserTracker(userId) val userSwitcherControllerWrapper = @@ -167,14 +167,9 @@ class FooterActionsViewModelTest : SysuiTestCase() { // for the current user will be fired to notify us of that change. isUserSwitcherEnabled = true - // Update the setting for a random user: nothing should change, given that at this point we - // weren't notified of the change yet. - utils.setUserSwitcherEnabled(settings, true, 3) - assertThat(currentUserSwitcher()).isNull() - // Update the setting for the observed user: now we will be notified and the button should // be there. - utils.setUserSwitcherEnabled(settings, true, userId) + utils.setUserSwitcherEnabled(settings, true) val userSwitcher = currentUserSwitcher() assertThat(userSwitcher).isNotNull() assertThat(userSwitcher!!.icon) @@ -372,9 +367,7 @@ class FooterActionsViewModelTest : SysuiTestCase() { ), ) - val job = launch { - underTest.observeDeviceMonitoringDialogRequests(quickSettingsContext = mock()) - } + val job = launch { underTest.observeDeviceMonitoringDialogRequests(mock()) } advanceUntilIdle() assertThat(nDialogRequests).isEqualTo(3) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt index a0ff2ab330b8..dcda005b6109 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt @@ -32,6 +32,7 @@ import com.android.systemui.util.mockito.nullable import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.test.TestCoroutineScheduler @@ -95,8 +96,7 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() { testScope.backgroundScope, dispatcher, ) - `when`(deviceItemInteractor.deviceItemUpdate) - .thenReturn(MutableStateFlow(null).asStateFlow()) + `when`(deviceItemInteractor.deviceItemUpdate).thenReturn(MutableSharedFlow()) `when`(bluetoothStateInteractor.bluetoothStateUpdate) .thenReturn(MutableStateFlow(null).asStateFlow()) `when`(deviceItemInteractor.deviceItemUpdateRequest) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt index 3593075c70fd..428f79cf9b1b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt @@ -27,10 +27,11 @@ import com.android.internal.logging.UiEventLogger import com.android.settingslib.bluetooth.CachedBluetoothDevice import com.android.settingslib.bluetooth.LocalBluetoothManager import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Rule @@ -78,7 +79,7 @@ class DeviceItemInteractorTest : SysuiTestCase() { @Before fun setUp() { - dispatcher = StandardTestDispatcher() + dispatcher = UnconfinedTestDispatcher() testScope = TestScope(dispatcher) interactor = DeviceItemInteractor( @@ -107,9 +108,10 @@ class DeviceItemInteractorTest : SysuiTestCase() { listOf(createFactory({ true }, deviceItem1)) ) + val latest by collectLastValue(interactor.deviceItemUpdate) interactor.updateDeviceItems(mContext) - assertThat(interactor.deviceItemUpdate.value).isEmpty() + assertThat(latest).isEqualTo(emptyList<DeviceItem>()) } } @@ -121,9 +123,10 @@ class DeviceItemInteractorTest : SysuiTestCase() { listOf(createFactory({ false }, deviceItem1)) ) + val latest by collectLastValue(interactor.deviceItemUpdate) interactor.updateDeviceItems(mContext) - assertThat(interactor.deviceItemUpdate.value).isEmpty() + assertThat(latest).isEqualTo(emptyList<DeviceItem>()) } } @@ -135,10 +138,10 @@ class DeviceItemInteractorTest : SysuiTestCase() { listOf(createFactory({ true }, deviceItem1)) ) + val latest by collectLastValue(interactor.deviceItemUpdate) interactor.updateDeviceItems(mContext) - assertThat(interactor.deviceItemUpdate.value).hasSize(1) - assertThat(interactor.deviceItemUpdate.value!![0]).isEqualTo(deviceItem1) + assertThat(latest).isEqualTo(listOf(deviceItem1)) } } @@ -150,11 +153,10 @@ class DeviceItemInteractorTest : SysuiTestCase() { listOf(createFactory({ false }, deviceItem1), createFactory({ true }, deviceItem2)) ) + val latest by collectLastValue(interactor.deviceItemUpdate) interactor.updateDeviceItems(mContext) - assertThat(interactor.deviceItemUpdate.value).hasSize(2) - assertThat(interactor.deviceItemUpdate.value!![0]).isEqualTo(deviceItem2) - assertThat(interactor.deviceItemUpdate.value!![1]).isEqualTo(deviceItem2) + assertThat(latest).isEqualTo(listOf(deviceItem2, deviceItem2)) } } @@ -177,10 +179,10 @@ class DeviceItemInteractorTest : SysuiTestCase() { `when`(deviceItem1.type).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE) `when`(deviceItem2.type).thenReturn(DeviceItemType.SAVED_BLUETOOTH_DEVICE) + val latest by collectLastValue(interactor.deviceItemUpdate) interactor.updateDeviceItems(mContext) - assertThat(interactor.deviceItemUpdate.value) - .isEqualTo(listOf(deviceItem2, deviceItem1)) + assertThat(latest).isEqualTo(listOf(deviceItem2, deviceItem1)) } } @@ -200,10 +202,10 @@ class DeviceItemInteractorTest : SysuiTestCase() { `when`(deviceItem1.type).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE) `when`(deviceItem2.type).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE) + val latest by collectLastValue(interactor.deviceItemUpdate) interactor.updateDeviceItems(mContext) - assertThat(interactor.deviceItemUpdate.value) - .isEqualTo(listOf(deviceItem2, deviceItem1)) + assertThat(latest).isEqualTo(listOf(deviceItem2, deviceItem1)) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt index 421c35595026..fe80f702a3ae 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt @@ -21,7 +21,7 @@ import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.util.settings.FakeSettings +import com.android.systemui.util.settings.FakeGlobalSettings import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.StandardTestDispatcher @@ -36,7 +36,7 @@ import org.junit.runner.RunWith @RunWith(AndroidTestingRunner::class) class RetailModeSettingsRepositoryTest : SysuiTestCase() { - private val globalSettings = FakeSettings() + private val globalSettings = FakeGlobalSettings() private val testDispatcher = StandardTestDispatcher() private val testScope = TestScope(testDispatcher) 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 2e16577bb24f..61dd69a8126b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -52,7 +52,6 @@ import com.google.common.truth.Truth.assertWithMessage import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.launch import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent @@ -463,8 +462,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { fromScene = getCurrentSceneInUi(), toScene = to.key, progress = progressFlow, - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) runCurrent() diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt index 740c6d9329df..432bd0f2a050 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt @@ -29,7 +29,6 @@ import com.android.systemui.scene.shared.model.SceneModel import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith @@ -120,8 +119,7 @@ class SceneContainerRepositoryTest : SysuiTestCase() { fromScene = SceneKey.Lockscreen, toScene = SceneKey.Shade, progress = progress, - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) assertThat(reflectedTransitionState).isEqualTo(transitionState.value) diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt index 31d26c0d88f9..8b23d183f1a6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt @@ -83,8 +83,7 @@ class SceneInteractorTest : SysuiTestCase() { fromScene = SceneKey.Lockscreen, toScene = SceneKey.Shade, progress = progress, - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) assertThat(reflectedTransitionState).isEqualTo(transitionState.value) @@ -122,8 +121,7 @@ class SceneInteractorTest : SysuiTestCase() { fromScene = underTest.desiredScene.value.key, toScene = SceneKey.Shade, progress = progress, - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) assertThat(transitionTo).isEqualTo(SceneKey.Shade) @@ -160,8 +158,7 @@ class SceneInteractorTest : SysuiTestCase() { fromScene = SceneKey.Gone, toScene = SceneKey.Lockscreen, progress = flowOf(0.5f), - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) ) val transitioning by @@ -180,8 +177,7 @@ class SceneInteractorTest : SysuiTestCase() { fromScene = SceneKey.Shade, toScene = SceneKey.QuickSettings, progress = flowOf(0.5f), - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) ) underTest.setTransitionState(transitionState) @@ -198,8 +194,7 @@ class SceneInteractorTest : SysuiTestCase() { fromScene = SceneKey.Shade, toScene = SceneKey.Lockscreen, progress = flowOf(0.5f), - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) ) val transitioning by @@ -227,8 +222,7 @@ class SceneInteractorTest : SysuiTestCase() { fromScene = SceneKey.Shade, toScene = SceneKey.Lockscreen, progress = flowOf(0.5f), - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) assertThat(transitioning).isTrue() diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt index 3b9621e5c6e0..7b13de657657 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt @@ -109,8 +109,7 @@ class SceneContainerStartableTest : SysuiTestCase() { fromScene = SceneKey.Gone, toScene = SceneKey.Shade, progress = flowOf(0.5f), - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) assertThat(isVisible).isTrue() sceneInteractor.onSceneChanged(SceneModel(SceneKey.Shade), "reason") @@ -123,8 +122,7 @@ class SceneContainerStartableTest : SysuiTestCase() { fromScene = SceneKey.Shade, toScene = SceneKey.Gone, progress = flowOf(0.5f), - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) assertThat(isVisible).isTrue() sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone), "reason") 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/TakeScreenshotExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt index a105c15b630a..d8821aa6aa00 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt @@ -63,8 +63,8 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { @Before fun setUp() { - whenever(controllerFactory.create(eq(0))).thenReturn(controller0) - whenever(controllerFactory.create(eq(1))).thenReturn(controller1) + whenever(controllerFactory.create(eq(0), any())).thenReturn(controller0) + whenever(controllerFactory.create(eq(1), any())).thenReturn(controller1) } @Test @@ -74,8 +74,8 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { val onSaved = { _: Uri -> } screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) - verify(controllerFactory).create(eq(0)) - verify(controllerFactory).create(eq(1)) + verify(controllerFactory).create(eq(0), any()) + verify(controllerFactory).create(eq(1), any()) val capturer = ArgumentCaptor<ScreenshotData>() @@ -107,8 +107,8 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { callback ) - verify(controllerFactory).create(eq(0)) - verify(controllerFactory, never()).create(eq(1)) + verify(controllerFactory).create(eq(0), any()) + verify(controllerFactory, never()).create(eq(1), any()) val capturer = ArgumentCaptor<ScreenshotData>() @@ -139,7 +139,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { @Test fun executeScreenshots_allowedTypes_allCaptured() = testScope.runTest { - whenever(controllerFactory.create(any())).thenReturn(controller0) + whenever(controllerFactory.create(any(), any())).thenReturn(controller0) setDisplays( display(TYPE_INTERNAL, id = 0), 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 6205d90197a2..5091a7004f79 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt @@ -86,7 +86,7 @@ class TakeScreenshotServiceTest : SysuiTestCase() { ) .thenReturn(false) whenever(userManager.isUserUnlocked).thenReturn(true) - whenever(controllerFactory.create(any())).thenReturn(controller) + whenever(controllerFactory.create(any(), any())).thenReturn(controller) // Stub request processor as a synchronous no-op for tests with the flag enabled doAnswer { 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..700c9ae9006a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -84,6 +84,7 @@ import com.android.systemui.biometrics.AuthController; import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.classifier.FalsingManagerFake; +import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository; import com.android.systemui.common.ui.view.LongPressHandlingView; import com.android.systemui.doze.DozeLog; import com.android.systemui.dump.DumpManager; @@ -120,6 +121,8 @@ import com.android.systemui.plugins.qs.QS; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.qs.QSFragmentLegacy; import com.android.systemui.res.R; +import com.android.systemui.scene.SceneTestUtils; +import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags; import com.android.systemui.screenrecord.RecordingController; import com.android.systemui.shade.data.repository.FakeShadeRepository; import com.android.systemui.shade.data.repository.ShadeRepository; @@ -137,6 +140,7 @@ import com.android.systemui.statusbar.QsFrameTranslateController; import com.android.systemui.statusbar.StatusBarStateControllerImpl; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.VibratorHelper; +import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository; import com.android.systemui.statusbar.notification.ConversationNotificationManager; import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; @@ -157,6 +161,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; @@ -167,6 +172,7 @@ import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager; import com.android.systemui.statusbar.phone.TapAgainViewController; import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; +import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository; import com.android.systemui.statusbar.policy.CastController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.HeadsUpManager; @@ -175,8 +181,10 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController; import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView; import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController; +import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository; import com.android.systemui.statusbar.window.StatusBarWindowStateController; import com.android.systemui.unfold.SysUIUnfoldComponent; +import com.android.systemui.user.data.repository.FakeUserRepository; import com.android.systemui.util.kotlin.JavaAdapter; import com.android.systemui.util.time.FakeSystemClock; import com.android.systemui.util.time.SystemClock; @@ -196,6 +204,8 @@ import java.util.List; import java.util.Optional; import kotlinx.coroutines.CoroutineDispatcher; +import kotlinx.coroutines.flow.StateFlowKt; +import kotlinx.coroutines.test.TestScope; public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { @@ -323,16 +333,19 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mEmptySpaceClickListenerCaptor; @Mock protected ActivityStarter mActivityStarter; @Mock protected KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor; - @Mock private ShadeInteractor mShadeInteractor; @Mock private JavaAdapter mJavaAdapter; @Mock private CastController mCastController; @Mock private KeyguardRootView mKeyguardRootView; @Mock private SharedNotificationContainerInteractor mSharedNotificationContainerInteractor; + @Mock private KeyguardClockPositionAlgorithm mKeyguardClockPositionAlgorithm; protected final int mMaxUdfpsBurnInOffsetY = 5; protected KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor; protected FakeKeyguardRepository mFakeKeyguardRepository; protected KeyguardInteractor mKeyguardInteractor; + protected SceneTestUtils mUtils = new SceneTestUtils(this); + protected TestScope mTestScope = mUtils.getTestScope(); + protected ShadeInteractor mShadeInteractor; protected PowerInteractor mPowerInteractor; protected NotificationPanelViewController.TouchHandler mTouchHandler; protected ConfigurationController mConfigurationController; @@ -368,10 +381,31 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mKeyguardInteractor = keyguardInteractorDeps.getKeyguardInteractor(); mShadeRepository = new FakeShadeRepository(); mPowerInteractor = keyguardInteractorDeps.getPowerInteractor(); + when(mKeyguardTransitionInteractor.isInTransitionToStateWhere(any())).thenReturn( + StateFlowKt.MutableStateFlow(false)); + mShadeInteractor = new ShadeInteractor( + mTestScope.getBackgroundScope(), + new FakeDeviceProvisioningRepository(), + new FakeDisableFlagsRepository(), + mDozeParameters, + new FakeSceneContainerFlags(), + mUtils::sceneInteractor, + mFakeKeyguardRepository, + mKeyguardTransitionInteractor, + mPowerInteractor, + new FakeUserSetupRepository(), + new FakeUserRepository(), + new SharedNotificationContainerInteractor( + new FakeConfigurationRepository(), + mContext, + new ResourcesSplitShadeStateController() + ), + mShadeRepository + ); SystemClock systemClock = new FakeSystemClock(); mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger, mDumpManager, - mInteractionJankMonitor, mShadeExpansionStateManager); + mInteractionJankMonitor, () -> mShadeInteractor); KeyguardStatusView keyguardStatusView = new KeyguardStatusView(mContext); keyguardStatusView.setId(R.id.keyguard_status_view); @@ -530,7 +564,8 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mDumpManager, mock(HeadsUpManager.class), new StatusBarStateControllerImpl(new UiEventLoggerFake(), mDumpManager, - mInteractionJankMonitor, mShadeExpansionStateManager), + mInteractionJankMonitor, + () -> mShadeInteractor), mKeyguardBypassController, mDozeParameters, mScreenOffAnimationController, @@ -667,7 +702,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/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java index eb006100d535..d4fb387f9670 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java @@ -90,7 +90,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController; import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository; -import com.android.systemui.user.domain.interactor.UserInteractor; +import com.android.systemui.user.data.repository.FakeUserRepository; import com.google.common.util.concurrent.MoreExecutors; @@ -230,7 +230,7 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { keyguardTransitionInteractor, powerInteractor, new FakeUserSetupRepository(), - mock(UserInteractor.class), + new FakeUserRepository(), new SharedNotificationContainerInteractor( configurationRepository, mContext, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java index 65174bab7f63..8a876c80194d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java @@ -95,15 +95,16 @@ import com.android.systemui.statusbar.policy.CastController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController; import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository; -import com.android.systemui.user.domain.interactor.UserInteractor; +import com.android.systemui.user.data.repository.FakeUserRepository; import com.android.systemui.util.kotlin.JavaAdapter; +import dagger.Lazy; + import org.junit.After; import org.junit.Before; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import dagger.Lazy; import kotlinx.coroutines.test.TestScope; public class QuickSettingsControllerBaseTest extends SysuiTestCase { @@ -162,7 +163,6 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { @Mock protected DumpManager mDumpManager; @Mock protected UiEventLogger mUiEventLogger; @Mock protected CastController mCastController; - @Mock protected UserInteractor mUserInteractor; protected FakeDisableFlagsRepository mDisableFlagsRepository = new FakeDisableFlagsRepository(); protected FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository(); @@ -186,7 +186,7 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); when(mPanelViewControllerLazy.get()).thenReturn(mNotificationPanelViewController); mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger, mDumpManager, - mInteractionJankMonitor, mShadeExpansionStateManager); + mInteractionJankMonitor, () -> mShadeInteractor); FakeDeviceProvisioningRepository deviceProvisioningRepository = new FakeDeviceProvisioningRepository(); @@ -266,7 +266,7 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { keyguardTransitionInteractor, powerInteractor, new FakeUserSetupRepository(), - mUserInteractor, + new FakeUserRepository(), new SharedNotificationContainerInteractor( configurationRepository, mContext, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt index bcb060ddb417..81382a44def6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt @@ -60,7 +60,6 @@ import dagger.BindsInstance import dagger.Component import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent @@ -595,8 +594,7 @@ class ShadeInteractorTest : SysuiTestCase() { fromScene = SceneKey.Lockscreen, toScene = key, progress = progress, - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) ) sceneInteractor.setTransitionState(transitionState) @@ -633,8 +631,7 @@ class ShadeInteractorTest : SysuiTestCase() { fromScene = key, toScene = SceneKey.Lockscreen, progress = progress, - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) ) sceneInteractor.setTransitionState(transitionState) @@ -670,8 +667,7 @@ class ShadeInteractorTest : SysuiTestCase() { fromScene = SceneKey.Lockscreen, toScene = SceneKey.Shade, progress = progress, - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) ) sceneInteractor.setTransitionState(transitionState) @@ -947,8 +943,7 @@ class ShadeInteractorTest : SysuiTestCase() { fromScene = SceneKey.Lockscreen, toScene = key, progress = progress, - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) ) sceneInteractor.setTransitionState(transitionState) @@ -985,8 +980,7 @@ class ShadeInteractorTest : SysuiTestCase() { fromScene = SceneKey.Lockscreen, toScene = key, progress = progress, - isInitiatedByUserInput = true, - isUserInputOngoing = flowOf(false), + isUserInputDriven = true, ) ) sceneInteractor.setTransitionState(transitionState) @@ -1023,8 +1017,7 @@ class ShadeInteractorTest : SysuiTestCase() { fromScene = key, toScene = SceneKey.Lockscreen, progress = progress, - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) ) sceneInteractor.setTransitionState(transitionState) @@ -1061,8 +1054,7 @@ class ShadeInteractorTest : SysuiTestCase() { fromScene = key, toScene = SceneKey.Lockscreen, progress = progress, - isInitiatedByUserInput = true, - isUserInputOngoing = flowOf(false), + isUserInputDriven = true, ) ) sceneInteractor.setTransitionState(transitionState) @@ -1097,9 +1089,8 @@ class ShadeInteractorTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = SceneKey.Lockscreen, toScene = SceneKey.QuickSettings, - progress = MutableStateFlow(0f), - isInitiatedByUserInput = true, - isUserInputOngoing = flowOf(false), + progress = progress, + isUserInputDriven = true, ) ) sceneInteractor.setTransitionState(transitionState) 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 bb20d94e7d39..607cdab12f56 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 @@ -17,7 +17,6 @@ import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnec import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test @@ -85,8 +84,7 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { fromScene = SceneKey.Shade, toScene = SceneKey.QuickSettings, progress = MutableStateFlow(0.5f), - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) ) ) @@ -104,8 +102,7 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { fromScene = SceneKey.QuickSettings, toScene = SceneKey.Shade, progress = MutableStateFlow(0.5f), - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) ) ) @@ -123,8 +120,7 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { fromScene = SceneKey.Gone, toScene = SceneKey.Shade, progress = MutableStateFlow(0.5f), - isInitiatedByUserInput = false, - isUserInputOngoing = flowOf(false), + isUserInputDriven = false, ) ) ) 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/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt index 3327e42b930f..1b26e19aa4a7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt @@ -23,9 +23,33 @@ import androidx.test.filters.SmallTest import com.android.internal.jank.InteractionJankMonitor import com.android.internal.logging.testing.UiEventLoggerFake import com.android.systemui.SysuiTestCase +import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository +import com.android.systemui.classifier.FalsingCollectorFake +import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository +import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository import com.android.systemui.dump.DumpManager +import com.android.systemui.flags.FakeFeatureFlagsClassic +import com.android.systemui.keyguard.data.repository.FakeCommandQueue +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.plugins.statusbar.StatusBarStateController -import com.android.systemui.shade.ShadeExpansionStateManager +import com.android.systemui.power.data.repository.FakePowerRepository +import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.scene.SceneTestUtils +import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags +import com.android.systemui.shade.data.repository.FakeShadeRepository +import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository +import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor +import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository +import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController +import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository +import com.android.systemui.user.data.repository.FakeUserRepository +import com.android.systemui.util.mockito.mock import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue @@ -40,17 +64,22 @@ import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.mock import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations +import org.mockito.Mockito.`when` as whenever @SmallTest @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper class StatusBarStateControllerImplTest : SysuiTestCase() { + private val utils = SceneTestUtils(this) + private val testScope = utils.testScope + private lateinit var shadeInteractor: ShadeInteractor + private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor + private lateinit var fromPrimaryBouncerTransitionInteractor: + FromPrimaryBouncerTransitionInteractor @Mock lateinit var interactionJankMonitor: InteractionJankMonitor - @Mock private lateinit var mockDarkAnimator: ObjectAnimator - @Mock private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager + @Mock lateinit var mockDarkAnimator: ObjectAnimator private lateinit var controller: StatusBarStateControllerImpl private lateinit var uiEventLogger: UiEventLoggerFake @@ -65,10 +94,74 @@ class StatusBarStateControllerImplTest : SysuiTestCase() { controller = object : StatusBarStateControllerImpl( uiEventLogger, mock(DumpManager::class.java), - interactionJankMonitor, shadeExpansionStateManager + interactionJankMonitor, + { shadeInteractor } ) { override fun createDarkAnimator(): ObjectAnimator { return mockDarkAnimator } } + + val powerInteractor = PowerInteractor( + FakePowerRepository(), + FalsingCollectorFake(), + mock(), + controller) + val keyguardRepository = FakeKeyguardRepository() + val keyguardTransitionRepository = FakeKeyguardTransitionRepository() + val featureFlags = FakeFeatureFlagsClassic() + val shadeRepository = FakeShadeRepository() + val sceneContainerFlags = FakeSceneContainerFlags() + val configurationRepository = FakeConfigurationRepository() + val keyguardInteractor = KeyguardInteractor( + keyguardRepository, + FakeCommandQueue(), + powerInteractor, + featureFlags, + sceneContainerFlags, + FakeDeviceEntryRepository(), + FakeKeyguardBouncerRepository(), + configurationRepository, + shadeRepository, + utils::sceneInteractor) + val keyguardTransitionInteractor = KeyguardTransitionInteractor( + testScope.backgroundScope, + keyguardTransitionRepository, + { keyguardInteractor }, + { fromLockscreenTransitionInteractor }, + { fromPrimaryBouncerTransitionInteractor }) + fromLockscreenTransitionInteractor = FromLockscreenTransitionInteractor( + keyguardTransitionRepository, + keyguardTransitionInteractor, + testScope.backgroundScope, + keyguardInteractor, + featureFlags, + shadeRepository, + powerInteractor) + fromPrimaryBouncerTransitionInteractor = FromPrimaryBouncerTransitionInteractor( + keyguardTransitionRepository, + keyguardTransitionInteractor, + testScope.backgroundScope, + keyguardInteractor, + featureFlags, + mock(), + powerInteractor) + shadeInteractor = ShadeInteractor( + testScope.backgroundScope, + FakeDeviceProvisioningRepository(), + FakeDisableFlagsRepository(), + mock(), + sceneContainerFlags, + utils::sceneInteractor, + keyguardRepository, + keyguardTransitionInteractor, + powerInteractor, + FakeUserSetupRepository(), + FakeUserRepository(), + SharedNotificationContainerInteractor( + configurationRepository, + mContext, + ResourcesSplitShadeStateController()), + shadeRepository, + ) } @Test 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 8f39ee6a6e9f..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; @@ -62,11 +67,15 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.util.settings.FakeGlobalSettings; import com.android.systemui.util.settings.FakeSettings; import com.android.systemui.util.settings.GlobalSettings; import com.android.systemui.util.settings.SecureSettings; import com.android.systemui.utils.os.FakeHandler; +import dagger.BindsInstance; +import dagger.Component; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -77,9 +86,6 @@ import org.mockito.MockitoAnnotations; import java.util.Map; import java.util.function.Consumer; -import dagger.BindsInstance; -import dagger.Component; - @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper @@ -93,7 +99,9 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { @Mock private HighPriorityProvider mHighPriorityProvider; @Mock private SysuiStatusBarStateController mStatusBarStateController; @Mock private UserTracker mUserTracker; - private final FakeSettings mFakeSettings = new FakeSettings(); + private final FakeSettings mSecureSettings = new FakeSettings(); + private final FakeGlobalSettings mGlobalSettings = new FakeGlobalSettings(); + private final FakeFeatureFlagsClassic mFeatureFlags = new FakeFeatureFlagsClassic(); private KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider; private NotificationEntry mEntry; @@ -113,8 +121,9 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { mHighPriorityProvider, mStatusBarStateController, mUserTracker, - mFakeSettings, - mFakeSettings); + mSecureSettings, + mGlobalSettings, + mFeatureFlags); mKeyguardNotificationVisibilityProvider = component.getProvider(); for (CoreStartable startable : component.getCoreStartables().values()) { startable.start(); @@ -223,7 +232,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { Consumer<String> listener = mock(Consumer.class); mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener); - mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true); + mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true); verify(listener).accept(anyString()); } @@ -234,7 +243,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { Consumer<String> listener = mock(Consumer.class); mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener); - mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, true); + mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, true); verify(listener).accept(anyString()); } @@ -242,8 +251,8 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { @Test public void hideSilentNotificationsPerUserSettingWithHighPriorityParent() { when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD); - mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true); - mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false); + mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true); + mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false); GroupEntry parent = new GroupEntryBuilder() .setKey("parent") .addChild(mEntry) @@ -264,8 +273,8 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { @Test public void keyguardShowing_hideSilentNotifications_perUserSetting() { when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD); - mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true); - mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false); + mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true); + mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false); mEntry = new NotificationEntryBuilder() .setUser(new UserHandle(NOTIF_USER_ID)) .setImportance(IMPORTANCE_LOW) @@ -277,8 +286,8 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { @Test public void keyguardShowing_hideSilentNotifications_perUserSetting_withHighPriorityParent() { when(mKeyguardStateController.isShowing()).thenReturn(true); - mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true); - mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false); + mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true); + mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false); GroupEntry parent = new GroupEntryBuilder() .setKey("parent") .addChild(mEntry) @@ -300,10 +309,10 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { public void hideSilentOnLockscreenSetting() { // GIVEN an 'unfiltered-keyguard-showing' state and notifications shown on lockscreen setupUnfilteredState(mEntry); - mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true); + mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true); // WHEN the show silent notifs on lockscreen setting is false - mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false); + mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false); // WHEN the notification is not high priority and not ambient mEntry = new NotificationEntryBuilder() @@ -319,10 +328,10 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { public void showSilentOnLockscreenSetting() { // GIVEN an 'unfiltered-keyguard-showing' state and notifications shown on lockscreen setupUnfilteredState(mEntry); - mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true); + mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true); // WHEN the show silent notifs on lockscreen setting is true - mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, true); + mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, true); // WHEN the notification is not high priority and not ambient mEntry = new NotificationEntryBuilder() @@ -338,7 +347,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { public void defaultSilentOnLockscreenSettingIsHide() { // GIVEN an 'unfiltered-keyguard-showing' state and notifications shown on lockscreen setupUnfilteredState(mEntry); - mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true); + mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true); // WHEN the notification is not high priority and not ambient mEntry = new NotificationEntryBuilder() @@ -348,7 +357,8 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { when(mHighPriorityProvider.isExplicitlyHighPriority(any())).thenReturn(false); // WhHEN the show silent notifs on lockscreen setting is unset - assertNull(mFakeSettings.getString(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS)); + assertNull( + mSecureSettings.getString(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS)); assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry)); } @@ -359,7 +369,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { Consumer<String> listener = mock(Consumer.class); mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener); - mFakeSettings.putBool(Settings.Global.ZEN_MODE, true); + mGlobalSettings.putBool(Settings.Global.ZEN_MODE, true); verify(listener).accept(anyString()); } @@ -370,7 +380,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { Consumer<String> listener = mock(Consumer.class); mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener); - mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, true); + mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, true); verify(listener).accept(anyString()); } @@ -421,6 +431,28 @@ 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); + + // 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_settingsDisallow_mainThread() { + mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, false); // GIVEN an 'unfiltered-keyguard-showing' state setupUnfilteredState(mEntry); @@ -430,12 +462,38 @@ 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_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); @@ -470,8 +528,8 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { public void highPriorityCharacteristicsIgnored() { // GIVEN an 'unfiltered-keyguard-showing' state with silent notifications hidden setupUnfilteredState(mEntry); - mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true); - mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false); + mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true); + mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false); // WHEN the notification doesn't exceed the threshold to show on the lockscreen, but does // have the "high priority characteristics" that would promote it to high priority @@ -503,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() @@ -557,7 +663,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { .build()); // WHEN its parent does exceed threshold tot show on the lockscreen - mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false); + mSecureSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false); when(mHighPriorityProvider.isExplicitlyHighPriority(parent)).thenReturn(true); // THEN filter out the entry regardless of parent @@ -632,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/logging/StackStateLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/StackStateLoggerTest.kt deleted file mode 100644 index 47c5e5b021ae..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/StackStateLoggerTest.kt +++ /dev/null @@ -1,77 +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.statusbar.notification.logging - -import android.testing.AndroidTestingRunner -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogcatEchoTracker -import com.android.systemui.log.core.LogLevel -import com.android.systemui.statusbar.notification.stack.StackStateLogger -import com.google.common.truth.Truth -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidTestingRunner::class) -@SmallTest -class StackStateLoggerTest : SysuiTestCase() { - private val logBufferCounter = LogBufferCounter() - private lateinit var logger: StackStateLogger - - @Before - fun setup() { - logger = StackStateLogger(logBufferCounter.logBuffer, logBufferCounter.logBuffer) - } - - @Test - fun groupChildRemovalEvent() { - logger.groupChildRemovalEventProcessed(KEY) - verifyDidLog(1) - logger.groupChildRemovalAnimationEnded(KEY) - verifyDidLog(1) - } - - class LogBufferCounter { - val recentLogs = mutableListOf<Pair<String, LogLevel>>() - val tracker = - object : LogcatEchoTracker { - override val logInBackgroundThread: Boolean = false - override fun isBufferLoggable(bufferName: String, level: LogLevel): Boolean = false - override fun isTagLoggable(tagName: String, level: LogLevel): Boolean { - recentLogs.add(tagName to level) - return true - } - } - val logBuffer = - LogBuffer(name = "test", maxSize = 1, logcatEchoTracker = tracker, systrace = false) - - fun verifyDidLog(times: Int) { - Truth.assertThat(recentLogs).hasSize(times) - recentLogs.clear() - } - } - - private fun verifyDidLog(times: Int) { - logBufferCounter.verifyDidLog(times) - } - - companion object { - private val KEY = "PACKAGE_NAME" - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt index 8c3bfd55ecf1..f7632aa37d4b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt @@ -30,9 +30,12 @@ import android.widget.FrameLayout import androidx.test.filters.SmallTest import com.android.internal.R import com.android.systemui.SysuiTestCase +import com.android.systemui.TestUiOffloadThread +import com.android.systemui.UiOffloadThread import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.NotificationTestHelper import com.android.systemui.statusbar.notification.row.wrapper.NotificationTemplateViewWrapper.ActionPendingIntentCancellationHandler +import com.android.systemui.util.leak.ReferenceTestUtils.waitForCondition import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test @@ -60,6 +63,12 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { fun setUp() { looper = TestableLooper.get(this) allowTestableLooperAsMainThread() + // Use main thread instead of UI offload thread to fix flakes. + mDependency.injectTestDependency( + UiOffloadThread::class.java, + TestUiOffloadThread(looper.looper) + ) + helper = NotificationTestHelper(mContext, mDependency, looper) row = helper.createRow() // Some code in the view iterates through parents so we need some extra containers around @@ -88,12 +97,11 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { val action2 = createActionWithPendingIntent() val action3 = createActionWithPendingIntent() wrapper.onContentUpdated(row) - waitForUiOffloadThread() // Wait for cancellation registration to execute. val pi3 = getPendingIntent(action3) pi3.cancel() - looper.processAllMessages() // Wait for listener callbacks to execute + waitForActionDisabled(action3) assertThat(action1.isEnabled).isTrue() assertThat(action2.isEnabled).isTrue() assertThat(action3.isEnabled).isFalse() @@ -109,12 +117,12 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { val wrapper = NotificationTemplateViewWrapper(mContext, view, row) val action = createActionWithPendingIntent() wrapper.onContentUpdated(row) - waitForUiOffloadThread() // Wait for cancellation registration to execute. // Cancel the intent and check action is now false. val pi = getPendingIntent(action) pi.cancel() - looper.processAllMessages() // Wait for listener callbacks to execute + + waitForActionDisabled(action) assertThat(action.isEnabled).isFalse() // Create a NEW action and make sure that one will also be cancelled with same PI. @@ -134,12 +142,13 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { val action2 = createActionWithPendingIntent() val action3 = createActionWithPendingIntent(getPendingIntent(action2)) wrapper.onContentUpdated(row) - waitForUiOffloadThread() // Wait for cancellation registration to execute. + looper.processAllMessages() val pi = getPendingIntent(action2) pi.cancel() - looper.processAllMessages() // Wait for listener callbacks to execute + waitForActionDisabled(action2) + waitForActionDisabled(action3) assertThat(action1.isEnabled).isTrue() assertThat(action2.isEnabled).isFalse() assertThat(action3.isEnabled).isFalse() @@ -152,10 +161,12 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { val action = createActionWithPendingIntent() wrapper.onContentUpdated(row) getPendingIntent(action).cancel() + looper.processAllMessages() + ViewUtils.attachView(root) - waitForUiOffloadThread() looper.processAllMessages() + waitForActionDisabled(action) assertThat(action.isEnabled).isFalse() } @@ -173,7 +184,6 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { val wrapper = NotificationTemplateViewWrapper(mContext, view, row) wrapper.onContentUpdated(row) ViewUtils.detachView(root) - waitForUiOffloadThread() looper.processAllMessages() val captor = ArgumentCaptor.forClass(CancelListener::class.java) @@ -194,7 +204,6 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { val action = createActionWithPendingIntent(spy) val wrapper = NotificationTemplateViewWrapper(mContext, view, row) wrapper.onContentUpdated(row) - waitForUiOffloadThread() looper.processAllMessages() // Grab set attach listener @@ -213,7 +222,6 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { ) action.setTagInternal(R.id.pending_intent_tag, newPi) wrapper.onContentUpdated(row) - waitForUiOffloadThread() looper.processAllMessages() // Listeners for original pending intent need to be cleaned up now. @@ -251,4 +259,11 @@ class NotificationTemplateViewWrapperTest : SysuiTestCase() { assertThat(pendingIntent).isNotNull() return pendingIntent } + + private fun waitForActionDisabled(action: View) { + waitForCondition { + looper.processAllMessages() + !action.isEnabled + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index a2be8b0e0be2..033c96ae84b0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -164,6 +164,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { mFeatureFlags.setDefault(Flags.ANIMATED_NOTIFICATION_SHADE_INSETS); mFeatureFlags.setDefault(Flags.NOTIFICATION_SHELF_REFACTOR); mFeatureFlags.setDefault(Flags.NEW_AOD_TRANSITION); + mFeatureFlags.setDefault(Flags.UNCLEARED_TRANSIENT_HUN_FIX); // Inject dependencies before initializing the layout mDependency.injectTestDependency(FeatureFlags.class, mFeatureFlags); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java index 416694b29f69..1d8a3461e546 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java @@ -50,15 +50,15 @@ import android.testing.TestableLooper.RunWithLooper; import androidx.test.filters.SmallTest; -import com.android.systemui.res.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.dagger.NightDisplayListenerModule; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.qs.AutoAddTracker; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.ReduceBrightColorsController; -import com.android.systemui.qs.SettingObserver; +import com.android.systemui.qs.UserSettingObserver; import com.android.systemui.qs.external.CustomTile; +import com.android.systemui.res.R; import com.android.systemui.statusbar.policy.CastController; import com.android.systemui.statusbar.policy.CastController.CastDevice; import com.android.systemui.statusbar.policy.DataSaverController; @@ -288,7 +288,7 @@ public class AutoTileManagerTest extends SysuiTestCase { inOrderSafety.verify(mSafetyController).removeCallback(any()); inOrderSafety.verify(mSafetyController).addCallback(any()); - SettingObserver setting = mAutoTileManager.getSecureSettingForKey(TEST_SETTING); + UserSettingObserver setting = mAutoTileManager.getSecureSettingForKey(TEST_SETTING); assertEquals(USER + 1, setting.getCurrentUser()); assertTrue(setting.isListening()); } @@ -342,7 +342,7 @@ public class AutoTileManagerTest extends SysuiTestCase { inOrderSafety.verify(mSafetyController).removeCallback(any()); inOrderSafety.verify(mSafetyController).addCallback(any()); - SettingObserver setting = mAutoTileManager.getSecureSettingForKey(TEST_SETTING); + UserSettingObserver setting = mAutoTileManager.getSecureSettingForKey(TEST_SETTING); assertEquals(USER + 1, setting.getCurrentUser()); assertFalse(setting.isListening()); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index f18af61dd314..c8cbe42fb0d5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -18,6 +18,8 @@ package com.android.systemui.statusbar.phone; import static android.app.NotificationManager.IMPORTANCE_HIGH; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; +import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN; +import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.StatusBarState.SHADE; @@ -1110,6 +1112,16 @@ public class CentralSurfacesImplTest extends SysuiTestCase { // THEN no NPE when fingerprintManager is null } + @Test + public void bubbleBarVisibility() { + createCentralSurfaces(); + mCentralSurfaces.onStatusBarWindowStateChanged(WINDOW_STATE_HIDDEN); + verify(mBubbles).onStatusBarVisibilityChanged(false); + + mCentralSurfaces.onStatusBarWindowStateChanged(WINDOW_STATE_SHOWING); + verify(mBubbles).onStatusBarVisibilityChanged(true); + } + /** * Configures the appropriate mocks and then calls {@link CentralSurfacesImpl#updateIsKeyguard} * to reconfigure the keyguard to reflect the requested showing/occluded states. diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java index cda2a74609bd..48b95d407246 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java @@ -34,7 +34,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.logging.UiEventLogger; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.res.R; -import com.android.systemui.shade.ShadeExpansionStateManager; +import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.statusbar.AlertingNotificationManager; import com.android.systemui.statusbar.AlertingNotificationManagerTest; import com.android.systemui.statusbar.NotificationShadeWindowController; @@ -45,6 +45,7 @@ import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.HeadsUpManagerLogger; +import com.android.systemui.util.kotlin.JavaAdapter; import org.junit.After; import org.junit.Before; @@ -56,6 +57,8 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import kotlinx.coroutines.flow.StateFlowKt; + @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper @@ -70,8 +73,9 @@ public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest { @Mock private KeyguardBypassController mBypassController; @Mock private ConfigurationControllerImpl mConfigurationController; @Mock private AccessibilityManagerWrapper mAccessibilityManagerWrapper; - @Mock private ShadeExpansionStateManager mShadeExpansionStateManager; @Mock private UiEventLogger mUiEventLogger; + @Mock private JavaAdapter mJavaAdapter; + @Mock private ShadeInteractor mShadeInteractor; private static final class TestableHeadsUpManagerPhone extends HeadsUpManagerPhone { TestableHeadsUpManagerPhone( @@ -85,7 +89,8 @@ public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest { Handler handler, AccessibilityManagerWrapper accessibilityManagerWrapper, UiEventLogger uiEventLogger, - ShadeExpansionStateManager shadeExpansionStateManager + JavaAdapter javaAdapter, + ShadeInteractor shadeInteractor ) { super( context, @@ -98,7 +103,8 @@ public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest { handler, accessibilityManagerWrapper, uiEventLogger, - shadeExpansionStateManager + javaAdapter, + shadeInteractor ); mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME; mAutoDismissNotificationDecay = TEST_AUTO_DISMISS_TIME; @@ -117,7 +123,8 @@ public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest { mTestHandler, mAccessibilityManagerWrapper, mUiEventLogger, - mShadeExpansionStateManager + mJavaAdapter, + mShadeInteractor ); } @@ -129,6 +136,7 @@ public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest { @Before @Override public void setUp() { + when(mShadeInteractor.isAnyExpanded()).thenReturn(StateFlowKt.MutableStateFlow(false)); final AccessibilityManagerWrapper accessibilityMgr = mDependency.injectMockDependency(AccessibilityManagerWrapper.class); when(accessibilityMgr.getRecommendedTimeoutMillis(anyInt(), anyInt())) 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/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index b36d09df5929..45e9224aa253 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -19,8 +19,6 @@ package com.android.systemui.statusbar.phone; import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN; import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE; -import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher; - import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -37,6 +35,8 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher; + import android.service.trust.TrustAgentService; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -175,7 +175,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { mFeatureFlags = new FakeFeatureFlags(); mFeatureFlags.set(Flags.WM_ENABLE_PREDICTIVE_BACK_BOUNCER_ANIM, true); mFeatureFlags.set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false); - mFeatureFlags.set(Flags.UDFPS_NEW_TOUCH_DETECTION, true); mFeatureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false); mFeatureFlags.set(Flags.ALTERNATE_BOUNCER_VIEW, false); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt index d35ce76d7a9a..8ecf6f82806a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/AirplaneModeRepositoryImplTest.kt @@ -18,12 +18,11 @@ package com.android.systemui.statusbar.pipeline.airplane.data.repository import android.os.Handler import android.os.Looper -import android.os.UserHandle import android.provider.Settings.Global import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.log.table.TableLogBuffer -import com.android.systemui.util.settings.FakeSettings +import com.android.systemui.util.settings.FakeGlobalSettings import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -48,15 +47,14 @@ class AirplaneModeRepositoryImplTest : SysuiTestCase() { @Mock private lateinit var logger: TableLogBuffer private lateinit var bgHandler: Handler private lateinit var scope: CoroutineScope - private lateinit var settings: FakeSettings + private lateinit var settings: FakeGlobalSettings @Before fun setUp() { MockitoAnnotations.initMocks(this) bgHandler = Handler(Looper.getMainLooper()) scope = CoroutineScope(IMMEDIATE) - settings = FakeSettings() - settings.userId = UserHandle.USER_ALL + settings = FakeGlobalSettings() underTest = AirplaneModeRepositoryImpl( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java index e76163575738..a1da16737aa4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java @@ -75,6 +75,8 @@ public class BluetoothControllerImplTest extends SysuiTestCase { private BluetoothAdapter mMockAdapter; private List<CachedBluetoothDevice> mDevices; + private FakeExecutor mBackgroundExecutor; + @Before public void setup() throws Exception { mTestableLooper = TestableLooper.get(this); @@ -91,6 +93,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase { when(mMockBluetoothManager.getProfileManager()) .thenReturn(mock(LocalBluetoothProfileManager.class)); mMockDumpManager = mock(DumpManager.class); + mBackgroundExecutor = new FakeExecutor(new FakeSystemClock()); BluetoothRepository bluetoothRepository = new FakeBluetoothRepository(mMockBluetoothManager); @@ -101,6 +104,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase { mMockDumpManager, mock(BluetoothLogger.class), bluetoothRepository, + mBackgroundExecutor, mTestableLooper.getLooper(), mMockBluetoothManager, mMockAdapter); @@ -205,6 +209,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase { mBluetoothControllerImpl.onAclConnectionStateChanged(device, BluetoothProfile.STATE_CONNECTED); mBluetoothControllerImpl.onActiveDeviceChanged(device, BluetoothProfile.HEADSET); + mBackgroundExecutor.runAllReady(); assertTrue(mBluetoothControllerImpl.isBluetoothAudioActive()); assertTrue(mBluetoothControllerImpl.isBluetoothAudioProfileOnly()); @@ -290,6 +295,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase { BluetoothProfile.LE_AUDIO, /* isConnected= */ true, /* isActive= */ false); mBluetoothControllerImpl.onDeviceAdded(device); + mBackgroundExecutor.runAllReady(); assertThat(mBluetoothControllerImpl.isBluetoothAudioProfileOnly()).isTrue(); } @@ -300,6 +306,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase { BluetoothProfile.HEADSET, /* isConnected= */ true, /* isActive= */ false); mBluetoothControllerImpl.onDeviceAdded(device); + mBackgroundExecutor.runAllReady(); assertThat(mBluetoothControllerImpl.isBluetoothAudioProfileOnly()).isTrue(); } @@ -310,6 +317,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase { BluetoothProfile.A2DP, /* isConnected= */ true, /* isActive= */ false); mBluetoothControllerImpl.onDeviceAdded(device); + mBackgroundExecutor.runAllReady(); assertThat(mBluetoothControllerImpl.isBluetoothAudioProfileOnly()).isTrue(); } @@ -320,6 +328,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase { BluetoothProfile.HEARING_AID, /* isConnected= */ true, /* isActive= */ false); mBluetoothControllerImpl.onDeviceAdded(device); + mBackgroundExecutor.runAllReady(); assertThat(mBluetoothControllerImpl.isBluetoothAudioProfileOnly()).isTrue(); } @@ -337,6 +346,8 @@ public class BluetoothControllerImplTest extends SysuiTestCase { mBluetoothControllerImpl.onDeviceAdded(device2); mBluetoothControllerImpl.onDeviceAdded(device3); + mBackgroundExecutor.runAllReady(); + assertThat(mBluetoothControllerImpl.isBluetoothAudioProfileOnly()).isTrue(); } @@ -349,6 +360,7 @@ public class BluetoothControllerImplTest extends SysuiTestCase { mBluetoothControllerImpl.onDeviceAdded(device1); mBluetoothControllerImpl.onDeviceAdded(device2); + mBackgroundExecutor.runAllReady(); assertThat(mBluetoothControllerImpl.isBluetoothAudioProfileOnly()).isFalse(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt index 6094135c6364..361fa5b169d9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt @@ -28,6 +28,7 @@ import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.settings.FakeGlobalSettings import com.android.systemui.util.settings.FakeSettings import com.android.systemui.util.time.FakeSystemClock import com.android.systemui.util.wrapper.BuildInfo @@ -67,19 +68,22 @@ class DeviceProvisionedControllerImplTest : SysuiTestCase() { private lateinit var mainExecutor: FakeExecutor private lateinit var testableLooper: TestableLooper - private lateinit var settings: FakeSettings + private lateinit var secureSettings: FakeSettings + private lateinit var globalSettings: FakeGlobalSettings + @Before fun setUp() { MockitoAnnotations.initMocks(this) testableLooper = TestableLooper.get(this) mainExecutor = FakeExecutor(FakeSystemClock()) - settings = FakeSettings() + secureSettings = FakeSettings() + globalSettings = FakeGlobalSettings() `when`(userTracker.userId).thenReturn(START_USER) whenever(buildInfo.isDebuggable).thenReturn(false) controller = DeviceProvisionedControllerImpl( - settings, - settings, + secureSettings, + globalSettings, userTracker, dumpManager, buildInfo, @@ -108,7 +112,7 @@ class DeviceProvisionedControllerImplTest : SysuiTestCase() { @Test fun testProvisionedWhenCreated() { - settings.putInt(Settings.Global.DEVICE_PROVISIONED, 1) + globalSettings.putInt(Settings.Global.DEVICE_PROVISIONED, 1) init() assertThat(controller.isDeviceProvisioned).isTrue() @@ -116,7 +120,7 @@ class DeviceProvisionedControllerImplTest : SysuiTestCase() { @Test fun testFrpActiveWhenCreated() { - settings.putInt(Settings.Secure.SECURE_FRP_MODE, 1) + globalSettings.putInt(Settings.Global.SECURE_FRP_MODE, 1) init() assertThat(controller.isFrpActive).isTrue() @@ -124,7 +128,7 @@ class DeviceProvisionedControllerImplTest : SysuiTestCase() { @Test fun testUserSetupWhenCreated() { - settings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER) + secureSettings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER) init() assertThat(controller.isUserSetup(START_USER)) @@ -134,7 +138,7 @@ class DeviceProvisionedControllerImplTest : SysuiTestCase() { fun testDeviceProvisionedChange() { init() - settings.putInt(Settings.Global.DEVICE_PROVISIONED, 1) + globalSettings.putInt(Settings.Global.DEVICE_PROVISIONED, 1) testableLooper.processAllMessages() // background observer assertThat(controller.isDeviceProvisioned).isTrue() @@ -144,7 +148,7 @@ class DeviceProvisionedControllerImplTest : SysuiTestCase() { fun testFrpActiveChange() { init() - settings.putInt(Settings.Secure.SECURE_FRP_MODE, 1) + globalSettings.putInt(Settings.Global.SECURE_FRP_MODE, 1) testableLooper.processAllMessages() // background observer assertThat(controller.isFrpActive).isTrue() @@ -154,7 +158,7 @@ class DeviceProvisionedControllerImplTest : SysuiTestCase() { fun testUserSetupChange() { init() - settings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER) + secureSettings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER) testableLooper.processAllMessages() // background observer assertThat(controller.isUserSetup(START_USER)).isTrue() @@ -165,7 +169,7 @@ class DeviceProvisionedControllerImplTest : SysuiTestCase() { init() val otherUser = 10 - settings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, otherUser) + secureSettings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, otherUser) testableLooper.processAllMessages() // background observer assertThat(controller.isUserSetup(START_USER)).isFalse() @@ -175,7 +179,7 @@ class DeviceProvisionedControllerImplTest : SysuiTestCase() { @Test fun testCurrentUserSetup() { val otherUser = 10 - settings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, otherUser) + secureSettings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, otherUser) init() assertThat(controller.isCurrentUserSetup).isFalse() @@ -219,7 +223,7 @@ class DeviceProvisionedControllerImplTest : SysuiTestCase() { init() controller.addCallback(listener) - settings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER) + secureSettings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER) testableLooper.processAllMessages() mainExecutor.runAllReady() @@ -234,7 +238,7 @@ class DeviceProvisionedControllerImplTest : SysuiTestCase() { init() controller.addCallback(listener) - settings.putInt(Settings.Global.DEVICE_PROVISIONED, 1) + globalSettings.putInt(Settings.Global.DEVICE_PROVISIONED, 1) testableLooper.processAllMessages() mainExecutor.runAllReady() @@ -249,7 +253,7 @@ class DeviceProvisionedControllerImplTest : SysuiTestCase() { init() controller.addCallback(listener) - settings.putInt(Settings.Secure.SECURE_FRP_MODE, 1) + globalSettings.putInt(Settings.Global.SECURE_FRP_MODE, 1) testableLooper.processAllMessages() mainExecutor.runAllReady() @@ -266,9 +270,9 @@ class DeviceProvisionedControllerImplTest : SysuiTestCase() { controller.removeCallback(listener) switchUser(10) - settings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER) - settings.putInt(Settings.Global.DEVICE_PROVISIONED, 1) - settings.putInt(Settings.Secure.SECURE_FRP_MODE, 1) + secureSettings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER) + globalSettings.putInt(Settings.Global.DEVICE_PROVISIONED, 1) + globalSettings.putInt(Settings.Global.SECURE_FRP_MODE, 1) testableLooper.processAllMessages() mainExecutor.runAllReady() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java index 66c5aaa3ed07..6825f650c421 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java @@ -38,7 +38,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dump.DumpManager; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.policy.ZenModeController.Callback; -import com.android.systemui.util.settings.FakeSettings; +import com.android.systemui.util.settings.FakeGlobalSettings; import org.junit.Before; import org.junit.Test; @@ -67,7 +67,7 @@ public class ZenModeControllerImplTest extends SysuiTestCase { UserTracker mUserTracker; private ZenModeControllerImpl mController; - private final FakeSettings mGlobalSettings = new FakeSettings(); + private final FakeGlobalSettings mGlobalSettings = new FakeGlobalSettings(); @Before public void setUp() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt index e249cece5a1e..0d78ae9c7b11 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt @@ -29,7 +29,7 @@ import com.android.systemui.settings.FakeUserTracker import com.android.systemui.user.data.model.SelectedUserModel import com.android.systemui.user.data.model.SelectionStatus import com.android.systemui.user.data.model.UserSwitcherSettingsModel -import com.android.systemui.util.settings.FakeSettings +import com.android.systemui.util.settings.FakeGlobalSettings import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -56,14 +56,14 @@ class UserRepositoryImplTest : SysuiTestCase() { private lateinit var underTest: UserRepositoryImpl - private lateinit var globalSettings: FakeSettings + private lateinit var globalSettings: FakeGlobalSettings private lateinit var tracker: FakeUserTracker @Before fun setUp() { MockitoAnnotations.initMocks(this) - globalSettings = FakeSettings() + globalSettings = FakeGlobalSettings() tracker = FakeUserTracker() } @@ -282,20 +282,17 @@ class UserRepositoryImplTest : SysuiTestCase() { com.android.internal.R.bool.config_expandLockScreenUserSwitcher, true, ) - globalSettings.putIntForUser( + globalSettings.putInt( UserRepositoryImpl.SETTING_SIMPLE_USER_SWITCHER, if (isSimpleUserSwitcher) 1 else 0, - UserHandle.USER_SYSTEM, ) - globalSettings.putIntForUser( + globalSettings.putInt( Settings.Global.ADD_USERS_WHEN_LOCKED, if (isAddUsersFromLockscreen) 1 else 0, - UserHandle.USER_SYSTEM, ) - globalSettings.putIntForUser( + globalSettings.putInt( Settings.Global.USER_SWITCHER_ENABLED, if (isUserSwitcherEnabled) 1 else 0, - UserHandle.USER_SYSTEM, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index c8327029026d..123362ac57d7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -156,7 +156,7 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository; -import com.android.systemui.user.domain.interactor.UserInteractor; +import com.android.systemui.user.data.repository.FakeUserRepository; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.bubbles.Bubble; @@ -450,7 +450,7 @@ public class BubblesTest extends SysuiTestCase { keyguardTransitionInteractor, powerInteractor, new FakeUserSetupRepository(), - mock(UserInteractor.class), + new FakeUserRepository(), new SharedNotificationContainerInteractor( configurationRepository, mContext, diff --git a/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java b/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java index 41dbc147dfc5..b820ca612bfc 100644 --- a/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java +++ b/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java @@ -68,13 +68,26 @@ public final class AnimatorTestRule implements TestRule { private final Object mLock = new Object(); private final TestHandler mTestHandler = new TestHandler(); + private final long mStartTime; + private long mTotalTimeDelta = 0; + + /** + * Construct an AnimatorTestRule with a custom start time. + * @see #AnimatorTestRule() + */ + public AnimatorTestRule(long startTime) { + mStartTime = startTime; + } + /** - * initializing the start time with {@link SystemClock#uptimeMillis()} reduces the discrepancies - * with various internals of classes like ValueAnimator which can sometimes read that clock via + * Construct an AnimatorTestRule with a start time of {@link SystemClock#uptimeMillis()}. + * Initializing the start time with this clock reduces the discrepancies with various internals + * of classes like ValueAnimator which can sometimes read that clock via * {@link android.view.animation.AnimationUtils#currentAnimationTimeMillis()}. */ - private final long mStartTime = SystemClock.uptimeMillis(); - private long mTotalTimeDelta = 0; + public AnimatorTestRule() { + this(SystemClock.uptimeMillis()); + } @NonNull @Override diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/TestUiOffloadThread.java b/packages/SystemUI/tests/utils/src/com/android/systemui/TestUiOffloadThread.java new file mode 100644 index 000000000000..fdd26ebeab70 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/TestUiOffloadThread.java @@ -0,0 +1,59 @@ +/* + * 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; + +import android.os.Handler; +import android.os.Looper; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; + +/** + * UiOffloadThread that can be used for testing as part of {@link TestableDependency}. + */ +public class TestUiOffloadThread extends UiOffloadThread { + private final Handler mTestHandler; + + public TestUiOffloadThread(Looper looper) { + mTestHandler = new Handler(looper); + } + + @Override + public Future<?> execute(Runnable runnable) { + Looper myLooper = Looper.myLooper(); + if (myLooper != null && myLooper.isCurrentThread()) { + try { + runnable.run(); + return CompletableFuture.completedFuture(null); + } catch (Exception e) { + return CompletableFuture.failedFuture(e); + } + } + + final CompletableFuture<?> future = new CompletableFuture<>(); + mTestHandler.post(() -> { + try { + runnable.run(); + future.complete(null); + } catch (Exception e) { + future.completeExceptionally(e); + } + }); + + return future; + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt index 0ced19e3d5f3..ba9c5eda1b63 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt @@ -28,11 +28,21 @@ import org.junit.runners.model.Statement * advanced together. */ class AnimatorTestRule : TestRule { + // Create the androidx rule, which initializes start time to SystemClock.uptimeMillis(), + // then copy that time to the platform rule so that the two clocks are in sync. private val androidxRule = androidx.core.animation.AnimatorTestRule() - private val platformRule = android.animation.AnimatorTestRule() + private val platformRule = android.animation.AnimatorTestRule(androidxRule.startTime) private val advanceAndroidXTimeBy = Consumer<Long> { timeDelta -> androidxRule.advanceTimeBy(timeDelta) } + /** Access the mStartTime field; bypassing the restriction of being on a looper thread. */ + private val androidx.core.animation.AnimatorTestRule.startTime: Long + get() = + javaClass.getDeclaredField("mStartTime").let { field -> + field.isAccessible = true + field.getLong(this) + } + /** * Chain is for simplicity not to force a particular order; order should not matter, because * each rule affects a different AnimationHandler classes, and no callbacks to code under test @@ -55,4 +65,11 @@ class AnimatorTestRule : TestRule { // animation from one to start later than the other. platformRule.advanceTimeBy(timeDelta, advanceAndroidXTimeBy) } + + /** + * Returns the current time in milliseconds tracked by the AnimationHandlers. Note that this is + * a different time than the time tracked by {@link SystemClock}. + */ + val currentTime: Long + get() = androidxRule.currentTime } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalTutorialRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalTutorialRepository.kt new file mode 100644 index 000000000000..902e8521acd1 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalTutorialRepository.kt @@ -0,0 +1,19 @@ +package com.android.systemui.communal.data.repository + +import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED +import android.provider.Settings.Secure.HubModeTutorialState +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +/** Fake implementation of [CommunalTutorialRepository] */ +class FakeCommunalTutorialRepository() : CommunalTutorialRepository { + private val _tutorialSettingState = MutableStateFlow(HUB_MODE_TUTORIAL_NOT_STARTED) + override val tutorialSettingState: StateFlow<Int> = _tutorialSettingState + override suspend fun setTutorialState(@HubModeTutorialState state: Int) { + setTutorialSettingState(state) + } + + fun setTutorialSettingState(@HubModeTutorialState state: Int) { + _tutorialSettingState.value = state + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt index bf77b1a050cd..911eafae5c27 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt @@ -53,7 +53,7 @@ import com.android.systemui.user.data.repository.UserSwitcherRepository import com.android.systemui.user.data.repository.UserSwitcherRepositoryImpl import com.android.systemui.user.domain.interactor.UserInteractor import com.android.systemui.util.mockito.mock -import com.android.systemui.util.settings.FakeSettings +import com.android.systemui.util.settings.FakeGlobalSettings import com.android.systemui.util.settings.GlobalSettings import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.test.StandardTestDispatcher @@ -69,8 +69,8 @@ class FooterActionsTestUtils( private val scheduler: TestCoroutineScheduler, ) { /** Enable or disable the user switcher in the settings. */ - fun setUserSwitcherEnabled(settings: GlobalSettings, enabled: Boolean, userId: Int) { - settings.putBoolForUser(Settings.Global.USER_SWITCHER_ENABLED, enabled, userId) + fun setUserSwitcherEnabled(settings: GlobalSettings, enabled: Boolean) { + settings.putBool(Settings.Global.USER_SWITCHER_ENABLED, enabled) // The settings listener is processing messages on the bgHandler (usually backed by a // testableLooper in tests), so let's make sure we process the callback before continuing. @@ -152,7 +152,7 @@ class FooterActionsTestUtils( userTracker: UserTracker = FakeUserTracker(), userSwitcherController: UserSwitcherController = mock(), userInfoController: UserInfoController = FakeUserInfoController(), - settings: GlobalSettings = FakeSettings(), + settings: GlobalSettings = FakeGlobalSettings(), ): UserSwitcherRepository { return UserSwitcherRepositoryImpl( context, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java new file mode 100644 index 000000000000..db5eaffee76b --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util.settings; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ContentResolver; +import android.database.ContentObserver; +import android.net.Uri; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class FakeGlobalSettings implements GlobalSettings { + private final Map<String, String> mValues = new HashMap<>(); + private final Map<String, List<ContentObserver>> mContentObserversAllUsers = new HashMap<>(); + + public static final Uri CONTENT_URI = Uri.parse("content://settings/fake_global"); + + public FakeGlobalSettings() { + } + + @Override + public ContentResolver getContentResolver() { + return null; + } + + @Override + public void registerContentObserver(Uri uri, boolean notifyDescendants, + ContentObserver settingsObserver) { + List<ContentObserver> observers; + mContentObserversAllUsers.putIfAbsent(uri.toString(), new ArrayList<>()); + observers = mContentObserversAllUsers.get(uri.toString()); + observers.add(settingsObserver); + } + + @Override + public void unregisterContentObserver(ContentObserver settingsObserver) { + for (Map.Entry<String, List<ContentObserver>> entry : + mContentObserversAllUsers.entrySet()) { + entry.getValue().remove(settingsObserver); + } + } + + @Override + public Uri getUriFor(String name) { + return Uri.withAppendedPath(CONTENT_URI, name); + } + + @Override + public String getString(String name) { + return mValues.get(getUriFor(name).toString()); + } + + @Override + public boolean putString(String name, String value) { + return putString(name, value, null, false); + } + + @Override + public boolean putString(@NonNull String name, @Nullable String value, @Nullable String tag, + boolean makeDefault) { + String key = getUriFor(name).toString(); + mValues.put(key, value); + + Uri uri = getUriFor(name); + for (ContentObserver observer : + mContentObserversAllUsers.getOrDefault(uri.toString(), new ArrayList<>())) { + observer.dispatchChange(false, List.of(uri), 0); + } + return true; + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java index 4b973162e42f..a49188687100 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java @@ -23,6 +23,8 @@ import android.net.Uri; import android.os.UserHandle; import android.util.Pair; +import androidx.annotation.NonNull; + import com.android.systemui.settings.UserTracker; import java.util.ArrayList; @@ -30,7 +32,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -public class FakeSettings implements SecureSettings, GlobalSettings, SystemSettings { +public class FakeSettings implements SecureSettings, SystemSettings { private final Map<SettingsKey, String> mValues = new HashMap<>(); private final Map<SettingsKey, List<ContentObserver>> mContentObservers = new HashMap<>(); @@ -64,7 +66,7 @@ public class FakeSettings implements SecureSettings, GlobalSettings, SystemSetti } @Override - public void registerContentObserverForUser(Uri uri, boolean notifyDescendents, + public void registerContentObserverForUser(Uri uri, boolean notifyDescendants, ContentObserver settingsObserver, int userHandle) { List<ContentObserver> observers; if (userHandle == UserHandle.USER_ALL) { @@ -147,7 +149,7 @@ public class FakeSettings implements SecureSettings, GlobalSettings, SystemSetti } @Override - public boolean putString(String name, String value, String tag, boolean makeDefault) { + public boolean putString(@NonNull String name, String value, String tag, boolean makeDefault) { return putString(name, value); } diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto index ced97cc0b39e..7ac7859f3c83 100644 --- a/proto/src/metrics_constants/metrics_constants.proto +++ b/proto/src/metrics_constants/metrics_constants.proto @@ -90,8 +90,14 @@ message MetricsEvent { // Make sound through the speaker. ALERT_BEEP = 2; - // Flash a notificaiton light. + // Flash a notification light. ALERT_BLINK = 4; + + // Alert was attenuated by polite notif. feature. + ALERT_POLITE = 8; + + // Alert was muted by polite notif. feature. + ALERT_MUTED = 16; } // Reasons that a notification might be dismissed. diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp new file mode 100644 index 000000000000..91acc3d0deb4 --- /dev/null +++ b/ravenwood/Android.bp @@ -0,0 +1,33 @@ +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_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +filegroup { + name: "ravenwood-annotations", + srcs: [ + "annotations-src/**/*.java", + ], + visibility: ["//visibility:public"], +} + +// File that contains the standard command line arguments to hoststubgen. +filegroup { + name: "ravenwood-standard-options", + srcs: [ + "ravenwood-standard-options.txt", + ], + visibility: ["//visibility:public"], +} + +java_library { + name: "ravenwood-annotations-lib", + srcs: [":ravenwood-annotations"], + sdk_version: "core_current", + host_supported: true, + visibility: ["//visibility:public"], +} diff --git a/ravenwood/OWNERS b/ravenwood/OWNERS new file mode 100644 index 000000000000..c06b3b9c2d11 --- /dev/null +++ b/ravenwood/OWNERS @@ -0,0 +1,3 @@ +jsharkey@google.com +omakoto@google.com +jaggies@google.com diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodClassLoadHook.java b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodClassLoadHook.java new file mode 100644 index 000000000000..be7b923244bf --- /dev/null +++ b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodClassLoadHook.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.ravenwood.annotations; + +import static java.lang.annotation.ElementType.TYPE; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY + * QUESTIONS ABOUT IT. + * + * Add this with a fully-specified method name (e.g. {@code "com.package.Class.methodName"}) + * of a callback to get a callback at the class load time. + * + * The method must be {@code public static} with a single argument that takes + * {@link Class}. + */ +@Target({TYPE}) +@Retention(RetentionPolicy.CLASS) +public @interface RavenwoodClassLoadHook { + String value(); +} diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodKeep.java b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodKeep.java new file mode 100644 index 000000000000..1644ffc57dca --- /dev/null +++ b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodKeep.java @@ -0,0 +1,37 @@ +/* + * 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.ravenwood.annotations; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY + * QUESTIONS ABOUT IT. + * + * TODO: Javadoc + * + */ +@Target({TYPE, FIELD, METHOD, CONSTRUCTOR}) +@Retention(RetentionPolicy.CLASS) +public @interface RavenwoodKeep { +} diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodNativeSubstitutionClass.java b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodNativeSubstitutionClass.java new file mode 100644 index 000000000000..eb883e228a40 --- /dev/null +++ b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodNativeSubstitutionClass.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.ravenwood.annotations; + +import static java.lang.annotation.ElementType.TYPE; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY + * QUESTIONS ABOUT IT. + * + * TODO: Javadoc + */ +@Target({TYPE}) +@Retention(RetentionPolicy.CLASS) +public @interface RavenwoodNativeSubstitutionClass { + String value(); +} diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodRemove.java b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodRemove.java new file mode 100644 index 000000000000..ffa1fa50fa4e --- /dev/null +++ b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodRemove.java @@ -0,0 +1,36 @@ +/* + * 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.ravenwood.annotations; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY + * QUESTIONS ABOUT IT. + * + * TODO: Javadoc + */ +@Target({TYPE, FIELD, METHOD, CONSTRUCTOR}) +@Retention(RetentionPolicy.CLASS) +public @interface RavenwoodRemove { +} diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodSubstitute.java b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodSubstitute.java new file mode 100644 index 000000000000..6d747da10207 --- /dev/null +++ b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodSubstitute.java @@ -0,0 +1,36 @@ +/* + * 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.ravenwood.annotations; + +import static java.lang.annotation.ElementType.METHOD; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY + * QUESTIONS ABOUT IT. + * + * TODO: Javadoc + */ +@Target({METHOD}) +@Retention(RetentionPolicy.CLASS) +public @interface RavenwoodSubstitute { + // TODO We should add "_host" as default. We're not doing it yet, because extractign the default + // value with ASM doesn't seem trivial. (? not sure.) + String suffix(); +} diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodThrow.java b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodThrow.java new file mode 100644 index 000000000000..a329d841abbe --- /dev/null +++ b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodThrow.java @@ -0,0 +1,35 @@ +/* + * 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.ravenwood.annotations; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.METHOD; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY + * QUESTIONS ABOUT IT. + * + * TODO: Javadoc + * TODO: Create "whole-class-throw"? + */ +@Target({METHOD, CONSTRUCTOR}) +@Retention(RetentionPolicy.CLASS) +public @interface RavenwoodThrow { +} diff --git a/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodWholeClassKeep.java b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodWholeClassKeep.java new file mode 100644 index 000000000000..ae6f42dbeaa6 --- /dev/null +++ b/ravenwood/annotations-src/android/ravenwood/annotations/RavenwoodWholeClassKeep.java @@ -0,0 +1,37 @@ +/* + * 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.ravenwood.annotations; + +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY + * QUESTIONS ABOUT IT. + * + * TODO: Javadoc + * TODO: Create "whole-class-throw"? + */ +@Target({TYPE, FIELD, METHOD, CONSTRUCTOR}) +@Retention(RetentionPolicy.CLASS) +public @interface RavenwoodWholeClassKeep { +} diff --git a/ravenwood/ravenwood-standard-options.txt b/ravenwood/ravenwood-standard-options.txt new file mode 100644 index 000000000000..6e1384f368b8 --- /dev/null +++ b/ravenwood/ravenwood-standard-options.txt @@ -0,0 +1,37 @@ +# File containing standard options to HostStubGen for Ravenwood + +--debug + +# Keep all classes / methods / fields, but make the methods throw. +--default-throw + +# Uncomment below lines to enable each feature. +# --enable-non-stub-method-check + +#--default-method-call-hook +# com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall +#--default-class-load-hook +# com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded + +# Standard annotations. +# Note, each line is a single argument, so we need newlines after each `--xxx-annotation`. +--keep-annotation + android.ravenwood.annotations.RavenwoodKeep + +--keep-class-annotation + android.ravenwood.annotations.RavenwoodWholeClassKeep + +--throw-annotation + android.ravenwood.annotations.RavenwoodThrow + +--remove-annotation + android.ravenwood.annotations.RavenwoodRemove + +--substitute-annotation + android.ravenwood.annotations.RavenwoodSubstitute + +--native-substitute-annotation + android.ravenwood.annotations.RavenwoodNativeSubstitutionClass + +--class-load-hook-annotation + android.ravenwood.annotations.RavenwoodClassLoadHook diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig index 6fa9c0809f75..10ac2ebc9b2f 100644 --- a/services/accessibility/accessibility.aconfig +++ b/services/accessibility/accessibility.aconfig @@ -40,4 +40,11 @@ flag { namespace: "accessibility" description: "Whether to set min span of ScaleGestureDetector to zero." bug: "295327792" -}
\ No newline at end of file +} + +flag { + name: "deprecate_package_list_observer" + namespace: "accessibility" + description: "Stops using the deprecated PackageListObserver." + bug: "304561459" +} diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 60d4ee61fdd4..aa6d800510e1 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -841,32 +841,32 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub // package changes monitor.register(mContext, null, UserHandle.ALL, true); - // Register an additional observer for new packages using PackageManagerInternal, which - // generally notifies observers much sooner than the BroadcastReceiver-based PackageMonitor. - final PackageManagerInternal pm = LocalServices.getService( - PackageManagerInternal.class); - if (pm != null) { - pm.getPackageList(new PackageManagerInternal.PackageListObserver() { - @Override - public void onPackageAdded(String packageName, int uid) { - final int userId = UserHandle.getUserId(uid); - synchronized (mLock) { - if (userId == mCurrentUserId) { - onSomePackagesChangedLocked(); + if (!Flags.deprecatePackageListObserver()) { + final PackageManagerInternal pm = LocalServices.getService( + PackageManagerInternal.class); + if (pm != null) { + pm.getPackageList(new PackageManagerInternal.PackageListObserver() { + @Override + public void onPackageAdded(String packageName, int uid) { + final int userId = UserHandle.getUserId(uid); + synchronized (mLock) { + if (userId == mCurrentUserId) { + onSomePackagesChangedLocked(); + } } } - } - @Override - public void onPackageRemoved(String packageName, int uid) { - final int userId = UserHandle.getUserId(uid); - synchronized (mLock) { - if (userId == mCurrentUserId) { - onPackageRemovedLocked(packageName); + @Override + public void onPackageRemoved(String packageName, int uid) { + final int userId = UserHandle.getUserId(uid); + synchronized (mLock) { + if (userId == mCurrentUserId) { + onPackageRemovedLocked(packageName); + } } } - } - }); + }); + } } // user change and unlock diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java index 30b9d0b59467..01064ac83fb2 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java @@ -873,7 +873,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH transitionToDelegatingStateAndClear(); - } else if (mDetectTripleTap + } else if (mDetectSingleFingerTripleTap // If activated, delay an ACTION_DOWN for mMultiTapMaxDelay // to ensure reachability of // STATE_PANNING_SCALING(triggerable with ACTION_POINTER_DOWN) @@ -989,7 +989,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH // Shortcut acts as the 2 initial taps if (mShortcutTriggered) return tapCount() + 2 >= numTaps; - final boolean multitapTriggered = mDetectTripleTap + final boolean multitapTriggered = mDetectSingleFingerTripleTap && tapCount() >= numTaps && isMultiTap(mPreLastDown, mLastDown) && isMultiTap(mPreLastUp, mLastUp); @@ -1205,7 +1205,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH * @return true if tap is out of distance slop */ boolean isTapOutOfDistanceSlop() { - if (!mDetectTripleTap) return false; + if (!mDetectSingleFingerTripleTap) return false; if (mPreLastDown == null || mLastDown == null) { return false; } @@ -1282,7 +1282,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH + ", mMagnifiedInteractionState=" + mPanningScalingState + ", mViewportDraggingState=" + mViewportDraggingState + ", mSinglePanningState=" + mSinglePanningState - + ", mDetectTripleTap=" + mDetectTripleTap + + ", mDetectSingleFingerTripleTap=" + mDetectSingleFingerTripleTap + ", mDetectShortcutTrigger=" + mDetectShortcutTrigger + ", mCurrentState=" + State.nameOf(mCurrentState) + ", mPreviousState=" + State.nameOf(mPreviousState) diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java index 28946939e6fd..8476a5e30e27 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java @@ -57,11 +57,11 @@ public abstract class MagnificationGestureHandler extends BaseEventStreamTransfo protected final boolean mDetectShortcutTrigger; /** - * {@code true} if this detector should detect and respond to triple-tap + * {@code true} if this detector should detect and respond to single-finger triple-tap * gestures for engaging and disengaging magnification, * {@code false} if it should ignore such gestures */ - protected final boolean mDetectTripleTap; + protected final boolean mDetectSingleFingerTripleTap; /** Callback interface to report that magnification is interactive with a user. */ public interface Callback { @@ -85,12 +85,12 @@ public abstract class MagnificationGestureHandler extends BaseEventStreamTransfo private final AccessibilityTraceManager mTrace; protected final Callback mCallback; - protected MagnificationGestureHandler(int displayId, boolean detectTripleTap, + protected MagnificationGestureHandler(int displayId, boolean detectSingleFingerTripleTap, boolean detectShortcutTrigger, AccessibilityTraceManager trace, @NonNull Callback callback) { mDisplayId = displayId; - mDetectTripleTap = detectTripleTap; + mDetectSingleFingerTripleTap = detectSingleFingerTripleTap; mDetectShortcutTrigger = detectShortcutTrigger; mTrace = trace; mCallback = callback; @@ -128,7 +128,7 @@ public abstract class MagnificationGestureHandler extends BaseEventStreamTransfo } private boolean shouldDispatchTransformedEvent(MotionEvent event) { - if ((!mDetectTripleTap && !mDetectShortcutTrigger) || !event.isFromSource( + if ((!mDetectSingleFingerTripleTap && !mDetectShortcutTrigger) || !event.isFromSource( SOURCE_TOUCHSCREEN)) { return true; } diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java index c58e9a6d0a2f..2d9dcb96d53d 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java @@ -111,7 +111,7 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl (event, rawEvent, policyFlags) -> dispatchTransformedEvent(event, rawEvent, policyFlags)); mDelegatingState = new DelegatingState(mMotionEventDispatcherDelegate); - mDetectingState = new DetectingState(context, mDetectTripleTap); + mDetectingState = new DetectingState(context); mViewportDraggingState = new ViewportDraggingState(); mObservePanningScalingState = new PanningScalingGestureState( new PanningScalingHandler(context, MAX_SCALE, MIN_SCALE, true, @@ -448,22 +448,14 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl private final MagnificationGesturesObserver mGesturesObserver; - /** - * {@code true} if this detector should detect and respond to triple-tap - * gestures for engaging and disengaging magnification, - * {@code false} if it should ignore such gestures - */ - private final boolean mDetectTripleTap; - - DetectingState(@UiContext Context context, boolean detectTripleTap) { - mDetectTripleTap = detectTripleTap; - final MultiTap multiTap = new MultiTap(context, mDetectTripleTap ? 3 : 1, - mDetectTripleTap + DetectingState(@UiContext Context context) { + final MultiTap multiTap = new MultiTap(context, mDetectSingleFingerTripleTap ? 3 : 1, + mDetectSingleFingerTripleTap ? MagnificationGestureMatcher.GESTURE_TRIPLE_TAP : MagnificationGestureMatcher.GESTURE_SINGLE_TAP, null); final MultiTapAndHold multiTapAndHold = new MultiTapAndHold(context, - mDetectTripleTap ? 3 : 1, - mDetectTripleTap + mDetectSingleFingerTripleTap ? 3 : 1, + mDetectSingleFingerTripleTap ? MagnificationGestureMatcher.GESTURE_TRIPLE_TAP_AND_HOLD : MagnificationGestureMatcher.GESTURE_SINGLE_TAP_AND_HOLD, null); mGesturesObserver = new MagnificationGesturesObserver(this, @@ -488,7 +480,7 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl @Override public boolean shouldStopDetection(MotionEvent motionEvent) { return !mWindowMagnificationMgr.isWindowMagnifierEnabled(mDisplayId) - && !mDetectTripleTap; + && !mDetectSingleFingerTripleTap; } @Override diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index 3e134992c763..72242d265e79 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -228,6 +228,9 @@ public final class AutofillManagerService private int mMaxInputLengthForAutofill; @GuardedBy("mFlagLock") + private boolean mAutofillCredmanIntegrationEnabled; + + @GuardedBy("mFlagLock") private boolean mIsFillFieldsFromCurrentSessionOnly; // Default flag values for Autofill PCC @@ -705,13 +708,16 @@ public final class AutofillManagerService DeviceConfig.NAMESPACE_AUTOFILL, AutofillFeatureFlags.DEVICE_CONFIG_MAX_INPUT_LENGTH_FOR_AUTOFILL, AutofillFeatureFlags.DEFAULT_MAX_INPUT_LENGTH_FOR_AUTOFILL); + mAutofillCredmanIntegrationEnabled = Flags.autofillCredmanIntegration(); mIsFillFieldsFromCurrentSessionOnly = Flags.fillFieldsFromCurrentSessionOnly(); if (verbose) { Slog.v(mTag, "setDeviceConfigProperties() for PCC: " + "mPccClassificationEnabled=" + mPccClassificationEnabled + ", mPccPreferProviderOverPcc=" + mPccPreferProviderOverPcc + ", mPccUseFallbackDetection=" + mPccUseFallbackDetection - + ", mPccProviderHints=" + mPccProviderHints); + + ", mPccProviderHints=" + mPccProviderHints + + ", mAutofillCredmanIntegrationEnabled=" + + mAutofillCredmanIntegrationEnabled); } } } @@ -970,6 +976,15 @@ public final class AutofillManagerService } /** + * Whether the Autofill-Credman integration feature flag is enabled. + */ + public boolean isAutofillCredmanIntegrationEnabled() { + synchronized (mFlagLock) { + return mAutofillCredmanIntegrationEnabled; + } + } + + /** * Whether the Autofill Provider shouldbe preferred over PCC results for selecting datasets. */ public boolean preferProviderOverPcc() { @@ -2110,6 +2125,9 @@ public final class AutofillManagerService pw.print(";"); pw.print("mPccProviderHints="); pw.println(mPccProviderHints); + pw.print(";"); + pw.print("mAutofillCredmanIntegrationEnabled="); + pw.println(mAutofillCredmanIntegrationEnabled); } // Dump per-user services dumpLocked("", pw); diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index 5b8bdd57ccbb..518b81f19467 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -19,6 +19,7 @@ package com.android.server.autofill; import static android.service.autofill.FillEventHistory.Event.NO_SAVE_UI_REASON_NONE; import static android.service.autofill.FillEventHistory.Event.UI_TYPE_INLINE; import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST; +import static android.service.autofill.FillRequest.FLAG_SCREEN_HAS_CREDMAN_FIELD; import static android.view.autofill.AutofillManager.ACTION_START_SESSION; import static android.view.autofill.AutofillManager.FLAG_ADD_CLIENT_ENABLED; import static android.view.autofill.AutofillManager.FLAG_ADD_CLIENT_ENABLED_FOR_AUGMENTED_AUTOFILL_ONLY; @@ -103,6 +104,10 @@ final class AutofillManagerServiceImpl extends AbstractPerUserSystemService<AutofillManagerServiceImpl, AutofillManagerService> { private static final String TAG = "AutofillManagerServiceImpl"; + + private static final ComponentName CREDMAN_SERVICE_COMPONENT_NAME = + new ComponentName("com.android.credentialmanager", + "com.android.credentialmanager.autofill.CredentialAutofillService"); private static final int MAX_SESSION_ID_CREATE_TRIES = 2048; /** Minimum interval to prune abandoned sessions */ @@ -532,9 +537,16 @@ final class AutofillManagerServiceImpl assertCallerLocked(clientActivity, compatMode); - // It's null when the session is just for augmented autofill - final ComponentName serviceComponentName = mInfo == null ? null + ComponentName serviceComponentName = mInfo == null ? null : mInfo.getServiceInfo().getComponentName(); + + if (isAutofillCredmanIntegrationEnabled() + && ((flags & FLAG_SCREEN_HAS_CREDMAN_FIELD) != 0)) { + // Hardcode to credential manager proxy service + Slog.i(TAG, "Routing to CredentialAutofillService"); + serviceComponentName = CREDMAN_SERVICE_COMPONENT_NAME; + } + final Session newSession = new Session(this, mUi, getContext(), mHandler, mUserId, mLock, sessionId, taskId, clientUid, clientActivityToken, clientCallback, hasCallback, mUiLatencyHistory, mWtfHistory, serviceComponentName, @@ -1747,6 +1759,10 @@ final class AutofillManagerServiceImpl } } + public boolean isAutofillCredmanIntegrationEnabled() { + return mMaster.isAutofillCredmanIntegrationEnabled(); + } + /** * Called when the {@link AutofillManagerService#mFieldClassificationResolver} * changed (among other places). diff --git a/services/autofill/java/com/android/server/autofill/ClientSuggestionsSession.java b/services/autofill/java/com/android/server/autofill/ClientSuggestionsSession.java deleted file mode 100644 index 715697d82cad..000000000000 --- a/services/autofill/java/com/android/server/autofill/ClientSuggestionsSession.java +++ /dev/null @@ -1,293 +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.autofill; - -import static android.service.autofill.FillRequest.INVALID_REQUEST_ID; - -import static com.android.server.autofill.Helper.sVerbose; - -import android.annotation.Nullable; -import android.annotation.UserIdInt; -import android.app.AppGlobals; -import android.content.ComponentName; -import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.graphics.drawable.Drawable; -import android.os.Handler; -import android.os.ICancellationSignal; -import android.os.RemoteException; -import android.service.autofill.Dataset; -import android.service.autofill.FillResponse; -import android.service.autofill.IFillCallback; -import android.service.autofill.SaveInfo; -import android.text.TextUtils; -import android.text.format.DateUtils; -import android.util.Slog; -import android.view.autofill.AutofillId; -import android.view.autofill.IAutoFillManagerClient; -import android.view.inputmethod.InlineSuggestionsRequest; - -import com.android.internal.annotations.GuardedBy; -import com.android.internal.infra.AndroidFuture; - -import java.util.List; -import java.util.concurrent.CancellationException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicReference; - -/** - * Maintains a client suggestions session with the - * {@link android.view.autofill.AutofillRequestCallback} through the {@link IAutoFillManagerClient}. - * - */ -final class ClientSuggestionsSession { - - private static final String TAG = "ClientSuggestionsSession"; - private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 15 * DateUtils.SECOND_IN_MILLIS; - - private final int mSessionId; - private final IAutoFillManagerClient mClient; - private final Handler mHandler; - private final ComponentName mComponentName; - - private final RemoteFillService.FillServiceCallbacks mCallbacks; - - private final Object mLock = new Object(); - @GuardedBy("mLock") - private AndroidFuture<FillResponse> mPendingFillRequest; - @GuardedBy("mLock") - private int mPendingFillRequestId = INVALID_REQUEST_ID; - - ClientSuggestionsSession(int sessionId, IAutoFillManagerClient client, Handler handler, - ComponentName componentName, RemoteFillService.FillServiceCallbacks callbacks) { - mSessionId = sessionId; - mClient = client; - mHandler = handler; - mComponentName = componentName; - mCallbacks = callbacks; - } - - void onFillRequest(int requestId, InlineSuggestionsRequest inlineRequest, int flags) { - final AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>(); - final AtomicReference<AndroidFuture<FillResponse>> futureRef = new AtomicReference<>(); - final AndroidFuture<FillResponse> fillRequest = new AndroidFuture<>(); - - mHandler.post(() -> { - if (sVerbose) { - Slog.v(TAG, "calling onFillRequest() for id=" + requestId); - } - - try { - mClient.requestFillFromClient(requestId, inlineRequest, - new FillCallbackImpl(fillRequest, futureRef, cancellationSink)); - } catch (RemoteException e) { - fillRequest.completeExceptionally(e); - } - }); - - fillRequest.orTimeout(TIMEOUT_REMOTE_REQUEST_MILLIS, TimeUnit.MILLISECONDS); - futureRef.set(fillRequest); - - synchronized (mLock) { - mPendingFillRequest = fillRequest; - mPendingFillRequestId = requestId; - } - - fillRequest.whenComplete((res, err) -> mHandler.post(() -> { - synchronized (mLock) { - mPendingFillRequest = null; - mPendingFillRequestId = INVALID_REQUEST_ID; - } - if (err == null) { - processAutofillId(res); - mCallbacks.onFillRequestSuccess(requestId, res, - mComponentName.getPackageName(), flags); - } else { - Slog.e(TAG, "Error calling on client fill request", err); - if (err instanceof TimeoutException) { - dispatchCancellationSignal(cancellationSink.get()); - mCallbacks.onFillRequestTimeout(requestId); - } else if (err instanceof CancellationException) { - dispatchCancellationSignal(cancellationSink.get()); - } else { - mCallbacks.onFillRequestFailure(requestId, err.getMessage()); - } - } - })); - } - - /** - * Gets the application info for the component. - */ - @Nullable - static ApplicationInfo getAppInfo(ComponentName comp, @UserIdInt int userId) { - try { - ApplicationInfo si = AppGlobals.getPackageManager().getApplicationInfo( - comp.getPackageName(), - PackageManager.GET_META_DATA, - userId); - if (si != null) { - return si; - } - } catch (RemoteException e) { - } - return null; - } - - /** - * Gets the user-visible name of the application. - */ - @Nullable - @GuardedBy("mLock") - static CharSequence getAppLabelLocked(Context context, ApplicationInfo appInfo) { - return appInfo == null ? null : appInfo.loadSafeLabel( - context.getPackageManager(), 0 /* do not ellipsize */, - TextUtils.SAFE_STRING_FLAG_FIRST_LINE | TextUtils.SAFE_STRING_FLAG_TRIM); - } - - /** - * Gets the user-visible icon of the application. - */ - @Nullable - @GuardedBy("mLock") - static Drawable getAppIconLocked(Context context, ApplicationInfo appInfo) { - return appInfo == null ? null : appInfo.loadIcon(context.getPackageManager()); - } - - int cancelCurrentRequest() { - synchronized (mLock) { - return mPendingFillRequest != null && mPendingFillRequest.cancel(false) - ? mPendingFillRequestId - : INVALID_REQUEST_ID; - } - } - - /** - * The {@link AutofillId} which the client gets from its view is not contain the session id, - * but Autofill framework is using the {@link AutofillId} with a session id. So before using - * those ids in the Autofill framework, applies the current session id. - * - * @param res which response need to apply for a session id - */ - private void processAutofillId(FillResponse res) { - if (res == null) { - return; - } - - final List<Dataset> datasets = res.getDatasets(); - if (datasets != null && !datasets.isEmpty()) { - for (int i = 0; i < datasets.size(); i++) { - final Dataset dataset = datasets.get(i); - if (dataset != null) { - applySessionId(dataset.getFieldIds()); - } - } - } - - final SaveInfo saveInfo = res.getSaveInfo(); - if (saveInfo != null) { - applySessionId(saveInfo.getOptionalIds()); - applySessionId(saveInfo.getRequiredIds()); - applySessionId(saveInfo.getSanitizerValues()); - applySessionId(saveInfo.getTriggerId()); - } - } - - private void applySessionId(List<AutofillId> ids) { - if (ids == null || ids.isEmpty()) { - return; - } - - for (int i = 0; i < ids.size(); i++) { - applySessionId(ids.get(i)); - } - } - - private void applySessionId(AutofillId[][] ids) { - if (ids == null) { - return; - } - for (int i = 0; i < ids.length; i++) { - applySessionId(ids[i]); - } - } - - private void applySessionId(AutofillId[] ids) { - if (ids == null) { - return; - } - for (int i = 0; i < ids.length; i++) { - applySessionId(ids[i]); - } - } - - private void applySessionId(AutofillId id) { - if (id == null) { - return; - } - id.setSessionId(mSessionId); - } - - private void dispatchCancellationSignal(@Nullable ICancellationSignal signal) { - if (signal == null) { - return; - } - try { - signal.cancel(); - } catch (RemoteException e) { - Slog.e(TAG, "Error requesting a cancellation", e); - } - } - - private class FillCallbackImpl extends IFillCallback.Stub { - final AndroidFuture<FillResponse> mFillRequest; - final AtomicReference<AndroidFuture<FillResponse>> mFutureRef; - final AtomicReference<ICancellationSignal> mCancellationSink; - - FillCallbackImpl(AndroidFuture<FillResponse> fillRequest, - AtomicReference<AndroidFuture<FillResponse>> futureRef, - AtomicReference<ICancellationSignal> cancellationSink) { - mFillRequest = fillRequest; - mFutureRef = futureRef; - mCancellationSink = cancellationSink; - } - - @Override - public void onCancellable(ICancellationSignal cancellation) { - AndroidFuture<FillResponse> future = mFutureRef.get(); - if (future != null && future.isCancelled()) { - dispatchCancellationSignal(cancellation); - } else { - mCancellationSink.set(cancellation); - } - } - - @Override - public void onSuccess(FillResponse response) { - mFillRequest.complete(response); - } - - @Override - public void onFailure(int requestId, CharSequence message) { - String errorMessage = message == null ? "" : String.valueOf(message); - mFillRequest.completeExceptionally( - new RuntimeException(errorMessage)); - } - } -} diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index ae1487775b74..07e9c50e845a 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -16,7 +16,6 @@ package com.android.server.autofill; -import static android.Manifest.permission.PROVIDE_OWN_AUTOFILL_SUGGESTIONS; import static android.service.autofill.AutofillFieldClassificationService.EXTRA_SCORES; import static android.service.autofill.AutofillService.EXTRA_FILL_RESPONSE; import static android.service.autofill.Dataset.PICK_REASON_NO_PCC; @@ -44,7 +43,6 @@ import static android.view.autofill.AutofillManager.ACTION_VIEW_ENTERED; import static android.view.autofill.AutofillManager.ACTION_VIEW_EXITED; import static android.view.autofill.AutofillManager.COMMIT_REASON_SESSION_DESTROYED; import static android.view.autofill.AutofillManager.COMMIT_REASON_UNKNOWN; -import static android.view.autofill.AutofillManager.FLAG_ENABLED_CLIENT_SUGGESTIONS; import static android.view.autofill.AutofillManager.FLAG_SMART_SUGGESTION_SYSTEM; import static android.view.autofill.AutofillManager.getSmartSuggestionModeToString; @@ -110,8 +108,6 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.IntentSender; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; import android.graphics.Bitmap; import android.graphics.Rect; @@ -481,9 +477,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState */ private final PccAssistDataReceiverImpl mPccAssistReceiver = new PccAssistDataReceiverImpl(); - @Nullable - private ClientSuggestionsSession mClientSuggestionsSession; - private final ClassificationState mClassificationState = new ClassificationState(); // TODO(b/216576510): Share one BroadcastReceiver between all Sessions instead of creating a @@ -625,9 +618,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState /** Whether the current {@link FillResponse} is expired. */ private boolean mExpiredResponse; - /** Whether the client is using {@link android.view.autofill.AutofillRequestCallback}. */ - private boolean mClientSuggestionsEnabled; - /** Whether the fill dialog UI is disabled. */ private boolean mFillDialogDisabled; @@ -673,19 +663,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } mWaitForInlineRequest = inlineSuggestionsRequest != null; mPendingInlineSuggestionsRequest = inlineSuggestionsRequest; - maybeRequestFillFromServiceLocked(); + maybeRequestFillLocked(); viewState.resetState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST); } } - void newAutofillRequestLocked(@Nullable InlineSuggestionsRequest inlineRequest) { - mPendingFillRequest = null; - mWaitForInlineRequest = inlineRequest != null; - mPendingInlineSuggestionsRequest = inlineRequest; - } - @GuardedBy("mLock") - void maybeRequestFillFromServiceLocked() { + void maybeRequestFillLocked() { if (mPendingFillRequest == null) { return; } @@ -696,15 +680,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return; } - if (mPendingInlineSuggestionsRequest.isServiceSupported()) { - mPendingFillRequest = new FillRequest(mPendingFillRequest.getId(), - mPendingFillRequest.getFillContexts(), - mPendingFillRequest.getHints(), - mPendingFillRequest.getClientState(), - mPendingFillRequest.getFlags(), - mPendingInlineSuggestionsRequest, - mPendingFillRequest.getDelayedFillIntentSender()); - } + mPendingFillRequest = new FillRequest(mPendingFillRequest.getId(), + mPendingFillRequest.getFillContexts(), + mPendingFillRequest.getHints(), + mPendingFillRequest.getClientState(), + mPendingFillRequest.getFlags(), + mPendingInlineSuggestionsRequest, + mPendingFillRequest.getDelayedFillIntentSender()); } mLastFillRequest = mPendingFillRequest; @@ -826,7 +808,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState : mDelayedFillPendingIntent.getIntentSender()); mPendingFillRequest = request; - maybeRequestFillFromServiceLocked(); + maybeRequestFillLocked(); } if (mActivityToken != null) { @@ -1152,39 +1134,30 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } /** - * Cancels the last request sent to the {@link #mRemoteFillService} or the - * {@link #mClientSuggestionsSession}. + * Cancels the last request sent to the {@link #mRemoteFillService}. */ @GuardedBy("mLock") private void cancelCurrentRequestLocked() { - if (mRemoteFillService == null && mClientSuggestionsSession == null) { - wtf(null, "cancelCurrentRequestLocked() called without a remote service or a " - + "client suggestions session. mForAugmentedAutofillOnly: %s", - mSessionFlags.mAugmentedAutofillOnly); + if (mRemoteFillService == null) { + wtf(null, "cancelCurrentRequestLocked() called without a remote service. " + + "mForAugmentedAutofillOnly: %s", mSessionFlags.mAugmentedAutofillOnly); return; } + final int canceledRequest = mRemoteFillService.cancelCurrentRequest(); - if (mRemoteFillService != null) { - final int canceledRequest = mRemoteFillService.cancelCurrentRequest(); - - // Remove the FillContext as there will never be a response for the service - if (canceledRequest != INVALID_REQUEST_ID && mContexts != null) { - final int numContexts = mContexts.size(); + // Remove the FillContext as there will never be a response for the service + if (canceledRequest != INVALID_REQUEST_ID && mContexts != null) { + final int numContexts = mContexts.size(); - // It is most likely the last context, hence search backwards - for (int i = numContexts - 1; i >= 0; i--) { - if (mContexts.get(i).getRequestId() == canceledRequest) { - if (sDebug) Slog.d(TAG, "cancelCurrentRequest(): id = " + canceledRequest); - mContexts.remove(i); - break; - } + // It is most likely the last context, hence search backwards + for (int i = numContexts - 1; i >= 0; i--) { + if (mContexts.get(i).getRequestId() == canceledRequest) { + if (sDebug) Slog.d(TAG, "cancelCurrentRequest(): id = " + canceledRequest); + mContexts.remove(i); + break; } } } - - if (mClientSuggestionsSession != null) { - mClientSuggestionsSession.cancelCurrentRequest(); - } } private boolean isViewFocusedLocked(int flags) { @@ -1280,30 +1253,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState requestAssistStructureForPccLocked(flags | FLAG_PCC_DETECTION); } - // Only ask IME to create inline suggestions request when - // 1. Autofill provider supports it or client enabled client suggestions. - // 2. The render service is available. - // 3. The view is focused. (The view may not be focused if the autofill is triggered - // manually.) + // Only ask IME to create inline suggestions request if Autofill provider supports it and + // the render service is available except the autofill is triggered manually and the view + // is also not focused. final RemoteInlineSuggestionRenderService remoteRenderService = mService.getRemoteInlineSuggestionRenderServiceLocked(); - if ((mSessionFlags.mInlineSupportedByService || mSessionFlags.mClientSuggestionsEnabled) - && remoteRenderService != null - && (isViewFocusedLocked(flags) || (isRequestSupportFillDialog(flags)))) { - final Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer; - if (mSessionFlags.mClientSuggestionsEnabled) { - final int finalRequestId = requestId; - inlineSuggestionsRequestConsumer = (inlineSuggestionsRequest) -> { - // Using client suggestions - synchronized (mLock) { - onClientFillRequestLocked(finalRequestId, inlineSuggestionsRequest); - } - viewState.resetState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST); - }; - } else { - inlineSuggestionsRequestConsumer = mAssistReceiver.newAutofillRequestLocked( - viewState, /* isInlineRequest= */ true); - } + if (mSessionFlags.mInlineSupportedByService && remoteRenderService != null + && (isViewFocusedLocked(flags) || isRequestSupportFillDialog(flags))) { + Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer = + mAssistReceiver.newAutofillRequestLocked(viewState, + /* isInlineRequest= */ true); if (inlineSuggestionsRequestConsumer != null) { final int requestIdCopy = requestId; final AutofillId focusedId = mCurrentViewId; @@ -1323,18 +1282,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState inlineSuggestionRendorInfoCallback); viewState.setState(ViewState.STATE_PENDING_CREATE_INLINE_REQUEST); } - } else if (mSessionFlags.mClientSuggestionsEnabled) { - // Request client suggestions for the dropdown mode - onClientFillRequestLocked(requestId, null); } else { mAssistReceiver.newAutofillRequestLocked(viewState, /* isInlineRequest= */ false); } - if (mSessionFlags.mClientSuggestionsEnabled) { - // Using client suggestions, unnecessary request AssistStructure - return; - } - // Now request the assist structure data. requestAssistStructureLocked(requestId, flags); } @@ -1443,11 +1394,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mSessionFlags = new SessionFlags(); mSessionFlags.mAugmentedAutofillOnly = forAugmentedAutofillOnly; mSessionFlags.mInlineSupportedByService = mService.isInlineSuggestionsEnabledLocked(); - if (mContext.checkCallingPermission(PROVIDE_OWN_AUTOFILL_SUGGESTIONS) - == PackageManager.PERMISSION_GRANTED) { - mSessionFlags.mClientSuggestionsEnabled = - (mFlags & FLAG_ENABLED_CLIENT_SUGGESTIONS) != 0; - } setClientLocked(client); } @@ -1599,15 +1545,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (requestLog != null) { requestLog.addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS, -1); } - processNullResponseOrFallbackLocked(requestId, requestFlags); + processNullResponseLocked(requestId, requestFlags); return; } // TODO: Check if this is required. We can still present datasets to the user even if // traditional field classification is disabled. fieldClassificationIds = response.getFieldClassificationIds(); - if (!mSessionFlags.mClientSuggestionsEnabled && fieldClassificationIds != null - && !mService.isFieldClassificationEnabledLocked()) { + if (fieldClassificationIds != null && !mService.isFieldClassificationEnabledLocked()) { Slog.w(TAG, "Ignoring " + response + " because field detection is disabled"); processNullResponseLocked(requestId, requestFlags); return; @@ -1741,9 +1686,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState || (ArrayUtils.isEmpty(saveInfo.getOptionalIds()) && ArrayUtils.isEmpty(saveInfo.getRequiredIds()) && ((saveInfo.getFlags() & SaveInfo.FLAG_DELAY_SAVE) == 0))) - && (ArrayUtils.isEmpty(response.getFieldClassificationIds()) - || (!mSessionFlags.mClientSuggestionsEnabled - && !mService.isFieldClassificationEnabledLocked()))); + && (ArrayUtils.isEmpty(response.getFieldClassificationIds()))); } } @@ -2190,40 +2133,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState fieldFilters.add(dataset.getFilter(index)); } - @GuardedBy("mLock") - private void processNullResponseOrFallbackLocked(int requestId, int flags) { - if (!mSessionFlags.mClientSuggestionsEnabled) { - processNullResponseLocked(requestId, flags); - return; - } - - // fallback to the default platform password manager - mSessionFlags.mClientSuggestionsEnabled = false; - mLastFillDialogTriggerIds = null; - // Log the existing FillResponse event. - mFillResponseEventLogger.logAndEndEvent(); - - final InlineSuggestionsRequest inlineRequest = - (mLastInlineSuggestionsRequest != null - && mLastInlineSuggestionsRequest.first == requestId) - ? mLastInlineSuggestionsRequest.second : null; - - // Start a new FillRequest logger for client suggestion fallback. - mFillRequestEventLogger.startLogForNewRequest(); - mRequestCount++; - mFillRequestEventLogger.maybeSetAppPackageUid(uid); - mFillRequestEventLogger.maybeSetFlags( - flags & ~FLAG_ENABLED_CLIENT_SUGGESTIONS); - mFillRequestEventLogger.maybeSetRequestTriggerReason( - TRIGGER_REASON_NORMAL_TRIGGER); - mFillRequestEventLogger.maybeSetIsClientSuggestionFallback(true); - - mAssistReceiver.newAutofillRequestLocked(inlineRequest); - requestAssistStructureLocked(requestId, - flags & ~FLAG_ENABLED_CLIENT_SUGGESTIONS); - return; - } - // FillServiceCallbacks @Override @SuppressWarnings("GuardedBy") @@ -4520,22 +4429,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState filterText = value.getTextValue().toString(); } - final CharSequence targetLabel; - final Drawable targetIcon; - synchronized (mLock) { - if (mSessionFlags.mClientSuggestionsEnabled) { - final ApplicationInfo appInfo = ClientSuggestionsSession.getAppInfo(mComponentName, - mService.getUserId()); - targetLabel = ClientSuggestionsSession.getAppLabelLocked( - mService.getMaster().getContext(), appInfo); - targetIcon = ClientSuggestionsSession.getAppIconLocked( - mService.getMaster().getContext(), appInfo); - } else { - targetLabel = mService.getServiceLabelLocked(); - targetIcon = mService.getServiceIconLocked(); - } + final CharSequence serviceLabel; + final Drawable serviceIcon; + synchronized (this.mService.mLock) { + serviceLabel = mService.getServiceLabelLocked(); + serviceIcon = mService.getServiceIconLocked(); } - if (targetLabel == null || targetIcon == null) { + if (serviceLabel == null || serviceIcon == null) { wtf(null, "onFillReady(): no service label or icon"); return; } @@ -4596,7 +4496,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState getUiForShowing().showFillUi(filledId, response, filterText, mService.getServicePackageName(), mComponentName, - targetLabel, targetIcon, this, mContext, id, mCompatMode, + serviceLabel, serviceIcon, this, mContext, id, mCompatMode, mService.getMaster().getMaxInputLengthForAutofill()); synchronized (mLock) { @@ -4799,17 +4699,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return false; } - final InlineSuggestionsRequest request = inlineSuggestionsRequest.get(); - if (mSessionFlags.mClientSuggestionsEnabled && !request.isClientSupported() - || !mSessionFlags.mClientSuggestionsEnabled && !request.isServiceSupported()) { - if (sDebug) { - Slog.d(TAG, "Inline suggestions not supported for " - + (mSessionFlags.mClientSuggestionsEnabled ? "client" : "service") - + ". Falling back to dropdown."); - } - return false; - } - final RemoteInlineSuggestionRenderService remoteRenderService = mService.getRemoteInlineSuggestionRenderServiceLocked(); if (remoteRenderService == null) { @@ -4824,7 +4713,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } final InlineFillUi.InlineFillUiInfo inlineFillUiInfo = - new InlineFillUi.InlineFillUiInfo(request, focusedId, + new InlineFillUi.InlineFillUiInfo(inlineSuggestionsRequest.get(), focusedId, filterText, remoteRenderService, userId, id); InlineFillUi inlineFillUi = InlineFillUi.forAutofill(inlineFillUiInfo, response, new InlineFillUi.InlineSuggestionUiCallback() { @@ -5641,26 +5530,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } - @GuardedBy("mLock") - private void onClientFillRequestLocked(int requestId, - InlineSuggestionsRequest inlineSuggestionsRequest) { - if (mClientSuggestionsSession == null) { - mClientSuggestionsSession = new ClientSuggestionsSession(id, mClient, mHandler, - mComponentName, this); - } - - if (mContexts == null) { - mContexts = new ArrayList<>(1); - } - mContexts.add(new FillContext(requestId, new AssistStructure(), mCurrentViewId)); - - if (inlineSuggestionsRequest != null && !inlineSuggestionsRequest.isClientSupported()) { - inlineSuggestionsRequest = null; - } - - mClientSuggestionsSession.onFillRequest(requestId, inlineSuggestionsRequest, mFlags); - } - /** * The result of checking whether to show the save dialog, when session can be saved. * 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 8a2aa616f8e6..f5562d27f4e8 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -122,9 +122,9 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub private static final String TAG = "VirtualDeviceImpl"; /** - * Virtual displays created by a {@link VirtualDeviceManager.VirtualDevice} are more consistent - * with virtual displays created via {@link DisplayManager} and allow for the creation of - * private, auto-mirror, and fixed orientation displays since + * Virtual displays created by a {@code VirtualDeviceManager.VirtualDevice} are more consistent + * with virtual displays created via {@link android.hardware.display.DisplayManager} and allow + * for the creation of private, auto-mirror, and fixed orientation displays since * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}. * * @see DisplayManager#VIRTUAL_DISPLAY_FLAG_PUBLIC diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java index f59417046c85..1a8dd3a7316e 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java @@ -20,6 +20,9 @@ import static android.Manifest.permission.MANAGE_CONTENT_CAPTURE; import static android.content.Context.CONTENT_CAPTURE_MANAGER_SERVICE; import static android.service.contentcapture.ContentCaptureService.setClientState; import static android.view.contentcapture.ContentCaptureHelper.toList; +import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG; +import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD; +import static android.view.contentcapture.ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG; import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_FALSE; import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_OK; import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_SECURITY_EXCEPTION; @@ -112,6 +115,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Objects; @@ -203,6 +207,17 @@ public class ContentCaptureManagerService extends @GuardedBy("mLock") int mDevCfgContentProtectionBufferSize; + @GuardedBy("mLock") + @NonNull + List<List<String>> mDevCfgContentProtectionRequiredGroups; + + @GuardedBy("mLock") + @NonNull + List<List<String>> mDevCfgContentProtectionOptionalGroups; + + @GuardedBy("mLock") + int mDevCfgContentProtectionOptionalGroupsThreshold; + private final Executor mDataShareExecutor = Executors.newCachedThreadPool(); private final Handler mHandler = new Handler(Looper.getMainLooper()); @@ -226,6 +241,11 @@ public class ContentCaptureManagerService extends com.android.internal.R.string.config_defaultContentCaptureService), UserManager.DISALLOW_CONTENT_CAPTURE, /*packageUpdatePolicy=*/ PACKAGE_UPDATE_POLICY_NO_REFRESH); + + mDevCfgContentProtectionRequiredGroups = + ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_REQUIRED_GROUPS; + mDevCfgContentProtectionOptionalGroups = + ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS; DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_CONTENT_CAPTURE, ActivityThread.currentApplication().getMainExecutor(), (properties) -> onDeviceConfigChange(properties)); @@ -422,6 +442,9 @@ public class ContentCaptureManagerService extends case ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_BUFFER_SIZE: case ContentCaptureManager .DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE: + case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG: + case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG: + case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD: setFineTuneParamsFromDeviceConfig(); return; default: @@ -433,6 +456,8 @@ public class ContentCaptureManagerService extends /** @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) protected void setFineTuneParamsFromDeviceConfig() { + String contentProtectionRequiredGroupsConfig; + String contentProtectionOptionalGroupsConfig; synchronized (mLock) { mDevCfgMaxBufferSize = DeviceConfig.getInt( @@ -486,6 +511,24 @@ public class ContentCaptureManagerService extends ContentCaptureManager .DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_BUFFER_SIZE, ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE); + contentProtectionRequiredGroupsConfig = + DeviceConfig.getString( + DeviceConfig.NAMESPACE_CONTENT_CAPTURE, + DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG, + ContentCaptureManager + .DEFAULT_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG); + contentProtectionOptionalGroupsConfig = + DeviceConfig.getString( + DeviceConfig.NAMESPACE_CONTENT_CAPTURE, + DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG, + ContentCaptureManager + .DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG); + mDevCfgContentProtectionOptionalGroupsThreshold = + DeviceConfig.getInt( + DeviceConfig.NAMESPACE_CONTENT_CAPTURE, + DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD, + ContentCaptureManager + .DEFAULT_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD); if (verbose) { Slog.v( TAG, @@ -507,9 +550,24 @@ public class ContentCaptureManagerService extends + ", contentProtectionAppsBlocklistSize=" + mDevCfgContentProtectionAppsBlocklistSize + ", contentProtectionBufferSize=" - + mDevCfgContentProtectionBufferSize); + + mDevCfgContentProtectionBufferSize + + ", contentProtectionRequiredGroupsConfig=" + + contentProtectionRequiredGroupsConfig + + ", contentProtectionOptionalGroupsConfig=" + + contentProtectionOptionalGroupsConfig + + ", contentProtectionOptionalGroupsThreshold=" + + mDevCfgContentProtectionOptionalGroupsThreshold); } } + + List<List<String>> contentProtectionRequiredGroups = + parseContentProtectionGroupsConfig(contentProtectionRequiredGroupsConfig); + List<List<String>> contentProtectionOptionalGroups = + parseContentProtectionGroupsConfig(contentProtectionOptionalGroupsConfig); + synchronized (mLock) { + mDevCfgContentProtectionRequiredGroups = contentProtectionRequiredGroups; + mDevCfgContentProtectionOptionalGroups = contentProtectionOptionalGroups; + } } private void setLoggingLevelFromDeviceConfig() { @@ -786,6 +844,15 @@ public class ContentCaptureManagerService extends pw.print(prefix2); pw.print("contentProtectionBufferSize: "); pw.println(mDevCfgContentProtectionBufferSize); + pw.print(prefix2); + pw.print("contentProtectionRequiredGroupsSize: "); + pw.println(mDevCfgContentProtectionRequiredGroups.size()); + pw.print(prefix2); + pw.print("contentProtectionOptionalGroupsSize: "); + pw.println(mDevCfgContentProtectionOptionalGroups.size()); + pw.print(prefix2); + pw.print("contentProtectionOptionalGroupsThreshold: "); + pw.println(mDevCfgContentProtectionOptionalGroupsThreshold); pw.print(prefix); pw.println("Global Options:"); mGlobalContentCaptureOptions.dump(prefix2, pw); @@ -890,6 +957,16 @@ public class ContentCaptureManagerService extends return mContentCaptureManagerServiceStub; } + /** @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(); + } + final class ContentCaptureManagerServiceStub extends IContentCaptureManager.Stub { @Override @@ -1277,7 +1354,10 @@ public class ContentCaptureManagerService extends isContentCaptureReceiverEnabled || whitelistedComponents != null, new ContentCaptureOptions.ContentProtectionOptions( isContentProtectionReceiverEnabled, - mDevCfgContentProtectionBufferSize), + mDevCfgContentProtectionBufferSize, + mDevCfgContentProtectionRequiredGroups, + mDevCfgContentProtectionOptionalGroups, + mDevCfgContentProtectionOptionalGroupsThreshold), whitelistedComponents); if (verbose) Slog.v(TAG, "getOptionsForPackage(" + packageName + "): " + options); return options; diff --git a/services/core/Android.bp b/services/core/Android.bp index 6521fabe5b7c..e5225f65f22a 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -183,6 +183,7 @@ java_library_static { "cbor-java", "display_flags_lib", "icu4j_calendar_astronomer", + "android.security.aaid_aidl-java", "netd-client", "overlayable_policy_aidl-java", "SurfaceFlingerProperties", diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java index 556eba6ced76..e9d4d76c6131 100644 --- a/services/core/java/com/android/server/BatteryService.java +++ b/services/core/java/com/android/server/BatteryService.java @@ -16,6 +16,7 @@ package com.android.server; +import static android.os.Flags.stateOfHealthPublic; import static com.android.internal.logging.nano.MetricsProto.MetricsEvent; import static com.android.server.health.Utils.copyV1Battery; @@ -27,7 +28,6 @@ import android.app.BroadcastOptions; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; import android.database.ContentObserver; import android.hardware.health.HealthInfo; import android.hardware.health.V2_1.BatteryCapacityLevel; @@ -1333,10 +1333,14 @@ public final class BatteryService extends SystemService { @Override public int getProperty(int id, final BatteryProperty prop) throws RemoteException { switch (id) { + case BatteryManager.BATTERY_PROPERTY_STATE_OF_HEALTH: + if (stateOfHealthPublic()) { + break; + } + case BatteryManager.BATTERY_PROPERTY_MANUFACTURING_DATE: case BatteryManager.BATTERY_PROPERTY_FIRST_USAGE_DATE: case BatteryManager.BATTERY_PROPERTY_CHARGING_POLICY: - case BatteryManager.BATTERY_PROPERTY_STATE_OF_HEALTH: mContext.enforceCallingPermission( android.Manifest.permission.BATTERY_STATS, null); break; diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags index d47a3991a966..db89cac2a943 100644 --- a/services/core/java/com/android/server/EventLogTags.logtags +++ b/services/core/java/com/android/server/EventLogTags.logtags @@ -87,7 +87,7 @@ option java_package com.android.server # replaces 27510 with a row per notification 27531 notification_visibility (key|3),(visibile|1),(lifespan|1),(freshness|1),(exposure|1),(rank|1) # a notification emited noise, vibration, or light -27532 notification_alert (key|3),(buzz|1),(beep|1),(blink|1) +27532 notification_alert (key|3),(buzz|1),(beep|1),(blink|1),(politeness|1) # a notification was added to a autogroup 27533 notification_autogrouped (key|3) # notification was removed from an autogroup 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/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 553b08501925..0956c6ded013 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -409,6 +409,13 @@ public final class ActiveServices { AppWidgetManagerInternal mAppWidgetManagerInternal; + /** + * The available ANR timers. + */ + private final ProcessAnrTimer mActiveServiceAnrTimer; + private final ServiceAnrTimer mShortFGSAnrTimer; + private final ServiceAnrTimer mServiceFGAnrTimer; + // allowlisted packageName. ArraySet<String> mAllowListWhileInUsePermissionInFgs = new ArraySet<>(); @@ -663,6 +670,15 @@ public final class ActiveServices { final IBinder b = ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE); this.mFGSLogger = new ForegroundServiceTypeLoggerModule(); + this.mActiveServiceAnrTimer = new ProcessAnrTimer(service, + ActivityManagerService.SERVICE_TIMEOUT_MSG, + "SERVICE_TIMEOUT"); + this.mShortFGSAnrTimer = new ServiceAnrTimer(service, + ActivityManagerService.SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG, + "FGS_TIMEOUT"); + this.mServiceFGAnrTimer = new ServiceAnrTimer(service, + ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, + "SERVICE_FOREGROUND_TIMEOUT"); } void systemServicesReady() { @@ -2083,8 +2099,7 @@ public final class ActiveServices { r.fgRequired = false; r.fgWaiting = false; alreadyStartedOp = stopProcStatsOp = true; - mAm.mHandler.removeMessages( - ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r); + mServiceFGAnrTimer.cancel(r); } final ProcessServiceRecord psr = r.app.mServices; @@ -3313,7 +3328,7 @@ public final class ActiveServices { } void unscheduleShortFgsTimeoutLocked(ServiceRecord sr) { - mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG, sr); + mShortFGSAnrTimer.cancel(sr); mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_SHORT_FGS_PROCSTATE_TIMEOUT_MSG, sr); mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_SHORT_FGS_TIMEOUT_MSG, sr); @@ -3387,9 +3402,11 @@ public final class ActiveServices { Slog.d(TAG_SERVICE, "[STALE] Short FGS timed out: " + sr + " " + sr.getShortFgsTimedEventDescription(nowUptime)); } + mShortFGSAnrTimer.discard(sr); return; } Slog.e(TAG_SERVICE, "Short FGS timed out: " + sr); + mShortFGSAnrTimer.accept(sr); traceInstant("short FGS timeout: ", sr); logFGSStateChangeLocked(sr, @@ -3413,11 +3430,10 @@ public final class ActiveServices { msg, sr.getShortFgsInfo().getProcStateDemoteTime()); } - { - final Message msg = mAm.mHandler.obtainMessage( - ActivityManagerService.SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG, sr); - mAm.mHandler.sendMessageAtTime(msg, sr.getShortFgsInfo().getAnrTime()); - } + // ServiceRecord.getAnrTime() is an absolute time with a reference that is not "now". + // Compute the time from "now" when starting the anr timer. + mShortFGSAnrTimer.start(sr, + sr.getShortFgsInfo().getAnrTime() - SystemClock.uptimeMillis()); } } @@ -4847,8 +4863,7 @@ public final class ActiveServices { // a new SERVICE_FOREGROUND_TIMEOUT_MSG is scheduled in SERVICE_START_FOREGROUND_TIMEOUT // again. if (r.fgRequired && r.fgWaiting) { - mAm.mHandler.removeMessages( - ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r); + mServiceFGAnrTimer.cancel(r); r.fgWaiting = false; } @@ -5691,8 +5706,7 @@ public final class ActiveServices { } mAm.mAppOpsService.finishOperation(AppOpsManager.getToken(mAm.mAppOpsService), AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName, null); - mAm.mHandler.removeMessages( - ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r); + mServiceFGAnrTimer.cancel(r); if (r.app != null) { Message msg = mAm.mHandler.obtainMessage( ActivityManagerService.SERVICE_FOREGROUND_CRASH_MSG); @@ -6128,7 +6142,7 @@ public final class ActiveServices { if (psr.numberOfExecutingServices() == 0) { if (DEBUG_SERVICE || DEBUG_SERVICE_EXECUTING) Slog.v(TAG_SERVICE_EXECUTING, "No more executingServices of " + r.shortInstanceName); - mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app); + if (r.app.mPid != 0) mActiveServiceAnrTimer.cancel(r.app); } else if (r.executeFg) { // Need to re-evaluate whether the app still needs to be in the foreground. for (int i = psr.numberOfExecutingServices() - 1; i >= 0; i--) { @@ -6816,13 +6830,16 @@ public final class ActiveServices { synchronized (mAm) { if (proc.isDebugging()) { // The app's being debugged, ignore timeout. + mActiveServiceAnrTimer.discard(proc); return; } final ProcessServiceRecord psr = proc.mServices; if (psr.numberOfExecutingServices() == 0 || proc.getThread() == null || proc.isKilled()) { + mActiveServiceAnrTimer.discard(proc); return; } + mActiveServiceAnrTimer.accept(proc); final long now = SystemClock.uptimeMillis(); final long maxTime = now - (psr.shouldExecServicesFg() @@ -6855,12 +6872,11 @@ public final class ActiveServices { timeoutRecord = TimeoutRecord.forServiceExec(timeout.shortInstanceName, waitedMillis); } else { - Message msg = mAm.mHandler.obtainMessage( - ActivityManagerService.SERVICE_TIMEOUT_MSG); - msg.obj = proc; - mAm.mHandler.sendMessageAtTime(msg, psr.shouldExecServicesFg() - ? (nextTime + mAm.mConstants.SERVICE_TIMEOUT) : - (nextTime + mAm.mConstants.SERVICE_BACKGROUND_TIMEOUT)); + final long delay = psr.shouldExecServicesFg() + ? (nextTime + mAm.mConstants.SERVICE_TIMEOUT) : + (nextTime + mAm.mConstants.SERVICE_BACKGROUND_TIMEOUT) + - SystemClock.uptimeMillis(); + mActiveServiceAnrTimer.start(proc, delay); } } @@ -6886,12 +6902,15 @@ public final class ActiveServices { synchronized (mAm) { timeoutRecord.mLatencyTracker.waitingOnAMSLockEnded(); if (!r.fgRequired || !r.fgWaiting || r.destroying) { + mServiceFGAnrTimer.discard(r); return; } + mServiceFGAnrTimer.accept(r); app = r.app; if (app != null && app.isDebugging()) { // The app's being debugged; let it ride + mServiceFGAnrTimer.discard(r); return; } @@ -6948,26 +6967,46 @@ public final class ActiveServices { ForegroundServiceDidNotStartInTimeException.createExtrasForService(service)); } + private static class ProcessAnrTimer extends AnrTimer<ProcessRecord> { + + ProcessAnrTimer(ActivityManagerService am, int msg, String label) { + super(Objects.requireNonNull(am).mHandler, msg, label); + } + + void start(@NonNull ProcessRecord proc, long millis) { + start(proc, proc.getPid(), proc.uid, millis); + } + } + + private static class ServiceAnrTimer extends AnrTimer<ServiceRecord> { + + ServiceAnrTimer(ActivityManagerService am, int msg, String label) { + super(Objects.requireNonNull(am).mHandler, msg, label); + } + + void start(@NonNull ServiceRecord service, long millis) { + start(service, + (service.app != null) ? service.app.getPid() : 0, + service.appInfo.uid, + millis); + } + } + void scheduleServiceTimeoutLocked(ProcessRecord proc) { if (proc.mServices.numberOfExecutingServices() == 0 || proc.getThread() == null) { return; } - Message msg = mAm.mHandler.obtainMessage( - ActivityManagerService.SERVICE_TIMEOUT_MSG); - msg.obj = proc; - mAm.mHandler.sendMessageDelayed(msg, proc.mServices.shouldExecServicesFg() - ? mAm.mConstants.SERVICE_TIMEOUT : mAm.mConstants.SERVICE_BACKGROUND_TIMEOUT); + final long delay = proc.mServices.shouldExecServicesFg() + ? mAm.mConstants.SERVICE_TIMEOUT : mAm.mConstants.SERVICE_BACKGROUND_TIMEOUT; + mActiveServiceAnrTimer.start(proc, delay); } void scheduleServiceForegroundTransitionTimeoutLocked(ServiceRecord r) { if (r.app.mServices.numberOfExecutingServices() == 0 || r.app.getThread() == null) { return; } - Message msg = mAm.mHandler.obtainMessage( - ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG); - msg.obj = r; r.fgWaiting = true; - mAm.mHandler.sendMessageDelayed(msg, mAm.mConstants.mServiceStartForegroundTimeoutMs); + mServiceFGAnrTimer.start(r, mAm.mConstants.mServiceStartForegroundTimeoutMs); } final class ServiceDumper { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 19879db1d22e..b43b986064fe 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -377,6 +377,7 @@ import android.util.FeatureFlagUtils; import android.util.IndentingPrintWriter; import android.util.IntArray; import android.util.Log; +import android.util.MathUtils; import android.util.Pair; import android.util.PrintWriterPrinter; import android.util.Slog; @@ -562,7 +563,7 @@ public class ActivityManagerService extends IActivityManager.Stub static final int PROC_START_TIMEOUT = 10 * 1000 * Build.HW_TIMEOUT_MULTIPLIER; // How long we wait for a launched process to complete its app startup before we ANR. - static final int BIND_APPLICATION_TIMEOUT = 10 * 1000 * Build.HW_TIMEOUT_MULTIPLIER; + static final int BIND_APPLICATION_TIMEOUT = 15 * 1000 * Build.HW_TIMEOUT_MULTIPLIER; // How long we wait to kill an application zygote, after the last process using // it has gone away. @@ -1531,6 +1532,11 @@ public class ActivityManagerService extends IActivityManager.Stub */ int mBootPhase; + /** + * The time stamp that all apps have received BOOT_COMPLETED. + */ + volatile long mBootCompletedTimestamp; + @GuardedBy("this") boolean mDeterministicUidIdle = false; @@ -1630,7 +1636,8 @@ public class ActivityManagerService extends IActivityManager.Stub static final int UPDATE_CACHED_APP_HIGH_WATERMARK = 79; static final int ADD_UID_TO_OBSERVER_MSG = 80; static final int REMOVE_UID_FROM_OBSERVER_MSG = 81; - static final int BIND_APPLICATION_TIMEOUT_MSG = 82; + static final int BIND_APPLICATION_TIMEOUT_SOFT_MSG = 82; + static final int BIND_APPLICATION_TIMEOUT_HARD_MSG = 83; static final int FIRST_BROADCAST_QUEUE_MSG = 200; @@ -1983,15 +1990,11 @@ public class ActivityManagerService extends IActivityManager.Stub case UPDATE_CACHED_APP_HIGH_WATERMARK: { mAppProfiler.mCachedAppsWatermarkData.updateCachedAppsSnapshot((long) msg.obj); } break; - case BIND_APPLICATION_TIMEOUT_MSG: { - ProcessRecord app = (ProcessRecord) msg.obj; - - final String anrMessage; - synchronized (app) { - anrMessage = "Process " + app + " failed to complete startup"; - } - - mAnrHelper.appNotResponding(app, TimeoutRecord.forAppStart(anrMessage)); + case BIND_APPLICATION_TIMEOUT_SOFT_MSG: { + handleBindApplicationTimeoutSoft((ProcessRecord) msg.obj, msg.arg1); + } break; + case BIND_APPLICATION_TIMEOUT_HARD_MSG: { + handleBindApplicationTimeoutHard((ProcessRecord) msg.obj); } break; } } @@ -4757,6 +4760,7 @@ public class ActivityManagerService extends IActivityManager.Stub mPlatformCompat.resetReporting(app.info); } final ProviderInfoList providerList = ProviderInfoList.fromList(providers); + app.mProfile.mLastCpuDelayTime.set(app.getCpuDelayTime()); if (app.getIsolatedEntryPoint() != null) { // This is an isolated process which should just call an entry point instead of // being bound to an application. @@ -4794,9 +4798,10 @@ public class ActivityManagerService extends IActivityManager.Stub app.getStartElapsedTime(), app.getStartUptime()); } - Message msg = mHandler.obtainMessage(BIND_APPLICATION_TIMEOUT_MSG); + Message msg = mHandler.obtainMessage(BIND_APPLICATION_TIMEOUT_SOFT_MSG); msg.obj = app; - mHandler.sendMessageDelayed(msg, BIND_APPLICATION_TIMEOUT); + msg.arg1 = BIND_APPLICATION_TIMEOUT; + mHandler.sendMessageDelayed(msg, msg.arg1 /* BIND_APPLICATION_TIMEOUT */); mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app); if (profilerInfo != null) { @@ -4873,7 +4878,8 @@ public class ActivityManagerService extends IActivityManager.Stub } if (app != null && app.getStartUid() == uid && app.getStartSeq() == startSeq) { - mHandler.removeMessages(BIND_APPLICATION_TIMEOUT_MSG, app); + mHandler.removeMessages(BIND_APPLICATION_TIMEOUT_SOFT_MSG, app); + mHandler.removeMessages(BIND_APPLICATION_TIMEOUT_HARD_MSG, app); } else { Slog.wtf(TAG, "Mismatched or missing ProcessRecord: " + app + ". Pid: " + pid + ". Uid: " + uid); @@ -5010,6 +5016,35 @@ public class ActivityManagerService extends IActivityManager.Stub } } + private void handleBindApplicationTimeoutSoft(ProcessRecord app, int softTimeoutMillis) { + // Similar logic as the broadcast delivery timeout: + // instead of immediately triggering an ANR, extend the timeout by + // the amount of time the process was runnable-but-waiting; we're + // only willing to do this once before triggering an hard ANR. + final long cpuDelayTime = app.getCpuDelayTime() - app.mProfile.mLastCpuDelayTime.get(); + final long hardTimeoutMillis = MathUtils.constrain(cpuDelayTime, 0, softTimeoutMillis); + + if (hardTimeoutMillis == 0) { + handleBindApplicationTimeoutHard(app); + return; + } + + Slog.i(TAG, "Extending process start timeout by " + hardTimeoutMillis + "ms for " + app); + Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplicationTimeSoft " + + app.processName + "(" + app.getPid() + ")"); + final Message msg = mHandler.obtainMessage(BIND_APPLICATION_TIMEOUT_HARD_MSG, app); + mHandler.sendMessageDelayed(msg, hardTimeoutMillis); + } + + private void handleBindApplicationTimeoutHard(ProcessRecord app) { + final String anrMessage; + synchronized (app) { + anrMessage = "Process " + app + " failed to complete startup"; + } + + mAnrHelper.appNotResponding(app, TimeoutRecord.forAppStart(anrMessage)); + } + /** * @return The last part of the string of an intent's action. */ @@ -5134,10 +5169,14 @@ public class ActivityManagerService extends IActivityManager.Stub public void performReceive(Intent intent, int resultCode, String data, Bundle extras, boolean ordered, boolean sticky, int sendingUser) { - synchronized (mProcLock) { - mAppProfiler.requestPssAllProcsLPr( - SystemClock.uptimeMillis(), true, false); - } + mBootCompletedTimestamp = SystemClock.uptimeMillis(); + // Defer the full Pss collection as the system is really busy now. + mHandler.postDelayed(() -> { + synchronized (mProcLock) { + mAppProfiler.requestPssAllProcsLPr( + SystemClock.uptimeMillis(), true, false); + } + }, mConstants.FULL_PSS_MIN_INTERVAL); } }); maybeLogUserspaceRebootEvent(); diff --git a/services/core/java/com/android/server/am/AnrTimer.java b/services/core/java/com/android/server/am/AnrTimer.java index 9ba49ce35dad..3e17930e3cb9 100644 --- a/services/core/java/com/android/server/am/AnrTimer.java +++ b/services/core/java/com/android/server/am/AnrTimer.java @@ -28,6 +28,7 @@ import android.os.Process; import android.os.SystemClock; import android.os.Trace; import android.text.TextUtils; +import android.text.format.TimeMigrationUtils; import android.util.ArrayMap; import android.util.IndentingPrintWriter; import android.util.Log; @@ -44,7 +45,6 @@ import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; -import java.util.Date; import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; @@ -150,7 +150,7 @@ class AnrTimer<V> { /** A partial stack that localizes the caller of the operation. */ final StackTraceElement[] stack; /** The date, in local time, the error was created. */ - final String date; + final long timestamp; Error(@NonNull String issue, @NonNull String operation, @NonNull String tag, @NonNull StackTraceElement[] stack, @NonNull String arg) { @@ -159,7 +159,7 @@ class AnrTimer<V> { this.tag = tag; this.stack = stack; this.arg = arg; - this.date = new Date().toString(); + this.timestamp = SystemClock.elapsedRealtime(); } } @@ -347,20 +347,23 @@ class AnrTimer<V> { * main Looper. */ @NonNull - Handler getHandler(@NonNull Handler.Callback callback) { + Handler newHandler(@NonNull Handler.Callback callback) { Looper looper = mReferenceHandler.getLooper(); if (looper == null) looper = Looper.getMainLooper(); return new Handler(looper, callback); - }; + } - /** Return a CpuTracker. */ + /** + * Return a CpuTracker. The default behavior is to create a new CpuTracker but this changes + * for unit tests. + **/ @NonNull - CpuTracker getTracker() { + CpuTracker newTracker() { return new CpuTracker(); } /** Return true if the feature is enabled. */ - boolean getFeatureEnabled() { + boolean isFeatureEnabled() { return anrTimerServiceEnabled(); } } @@ -401,8 +404,8 @@ class AnrTimer<V> { /** Create a HandlerTimerService that directly uses the supplied handler and tracker. */ @VisibleForTesting HandlerTimerService(@NonNull Injector injector) { - mHandler = injector.getHandler(this::expires); - mCpu = injector.getTracker(); + mHandler = injector.newHandler(this::expires); + mCpu = injector.newTracker(); } /** Post a message with the specified timeout. The timer is not modified. */ @@ -513,7 +516,26 @@ class AnrTimer<V> { private final FeatureSwitch mFeature; /** - * The common constructor. A null injector results in a normal, production timer. + * Create one AnrTimer instance. The instance is given a handler and a "what". Individual + * timers are started with {@link #start}. If a timer expires, then a {@link Message} is sent + * immediately to the handler with {@link Message.what} set to what and {@link Message.obj} set + * to the timer key. + * + * AnrTimer instances have a label, which must be unique. The label is used for reporting and + * debug. + * + * If an individual timer expires internally, and the "extend" parameter is true, then the + * AnrTimer may extend the individual timer rather than immediately delivering the timeout to + * the client. The extension policy is not part of the instance. + * + * This method accepts an {@link #Injector} to tune behavior for testing. This method should + * not be called directly by regular clients. + * + * @param handler The handler to which the expiration message will be delivered. + * @param what The "what" parameter for the expiration message. + * @param label A name for this instance. + * @param extend A flag to indicate if expired timers can be granted extensions. + * @param injector An {@link #Injector} to tune behavior for testing. */ @VisibleForTesting AnrTimer(@NonNull Handler handler, int what, @NonNull String label, boolean extend, @@ -522,7 +544,7 @@ class AnrTimer<V> { mWhat = what; mLabel = label; mExtend = extend; - boolean enabled = injector.getFeatureEnabled(); + boolean enabled = injector.isFeatureEnabled(); if (!enabled) { mFeature = new FeatureDisabled(); mTimerService = null; @@ -538,14 +560,25 @@ class AnrTimer<V> { } /** - * Create one timer instance for production. The client can ask for extensible timeouts. + * Create an AnrTimer instance with the default {@link #Injector}. See {@link AnrTimer(Handler, + * int, String, boolean, Injector} for a functional description. + * + * @param handler The handler to which the expiration message will be delivered. + * @param what The "what" parameter for the expiration message. + * @param label A name for this instance. + * @param extend A flag to indicate if expired timers can be granted extensions. */ AnrTimer(@NonNull Handler handler, int what, @NonNull String label, boolean extend) { this(handler, what, label, extend, new Injector(handler)); } /** - * Create one timer instance for production. There are no extensible timeouts. + * Create an AnrTimer instance with the default {@link #Injector} and with extensions disabled. + * See {@link AnrTimer(Handler, int, String, boolean, Injector} for a functional description. + * + * @param handler The handler to which the expiration message will be delivered. + * @param what The "what" parameter for the expiration message. + * @param label A name for this instance. */ AnrTimer(@NonNull Handler handler, int what, @NonNull String label) { this(handler, what, label, false); @@ -555,6 +588,8 @@ class AnrTimer<V> { * Return true if the service is enabled on this instance. Clients should use this method to * decide if the feature is enabled, and not read the flags directly. This method should be * deleted if and when the feature is enabled permanently. + * + * @return true if the service is flag-enabled. */ boolean serviceEnabled() { return mFeature.enabled(); @@ -642,7 +677,7 @@ class AnrTimer<V> { } /** - * Report something about a timer. + * Generate a log message for a timer. */ private void report(@NonNull Timer timer, @NonNull String msg) { Log.i(TAG, msg + " " + timer + " " + Objects.toString(timer.arg)); @@ -654,9 +689,13 @@ class AnrTimer<V> { */ private abstract class FeatureSwitch { abstract boolean start(@NonNull V arg, int pid, int uid, long timeoutMs); + abstract boolean cancel(@NonNull V arg); + abstract boolean accept(@NonNull V arg); + abstract boolean discard(@NonNull V arg); + abstract boolean enabled(); } @@ -666,6 +705,7 @@ class AnrTimer<V> { */ private class FeatureDisabled extends FeatureSwitch { /** Start a timer by sending a message to the client's handler. */ + @Override boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) { final Message msg = mHandler.obtainMessage(mWhat, arg); mHandler.sendMessageDelayed(msg, timeoutMs); @@ -673,22 +713,26 @@ class AnrTimer<V> { } /** Cancel a timer by removing the message from the client's handler. */ + @Override boolean cancel(@NonNull V arg) { mHandler.removeMessages(mWhat, arg); return true; } /** accept() is a no-op when the feature is disabled. */ + @Override boolean accept(@NonNull V arg) { return true; } /** discard() is a no-op when the feature is disabled. */ + @Override boolean discard(@NonNull V arg) { return true; } /** The feature is not enabled. */ + @Override boolean enabled() { return false; } @@ -703,16 +747,17 @@ class AnrTimer<V> { /** * Start a timer. */ + @Override boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) { final Timer timer = Timer.obtain(pid, uid, arg, timeoutMs, AnrTimer.this); synchronized (mLock) { Timer old = mTimerMap.get(arg); + // There is an existing timer. If the timer was running, then cancel the running + // timer and restart it. If the timer was expired record a protocol error and + // discard the expired timer. if (old != null) { - // There is an existing timer. This is a protocol error in the client. - // Record the error and then clean up by canceling running timers and - // discarding expired timers. - restartedLocked(old.status, arg); if (old.status == TIMER_EXPIRED) { + restartedLocked(old.status, arg); discard(arg); } else { cancel(arg); @@ -735,6 +780,7 @@ class AnrTimer<V> { /** * Cancel a timer. Return false if the timer was not found. */ + @Override boolean cancel(@NonNull V arg) { synchronized (mLock) { Timer timer = removeLocked(arg); @@ -755,6 +801,7 @@ class AnrTimer<V> { * Accept a timer in the framework-level handler. The timeout has been accepted and the * timeout handler is executing. Return false if the timer was not found. */ + @Override boolean accept(@NonNull V arg) { synchronized (mLock) { Timer timer = removeLocked(arg); @@ -775,6 +822,7 @@ class AnrTimer<V> { * longer interesting. No statistics are collected. Return false if the time was not * found. */ + @Override boolean discard(@NonNull V arg) { synchronized (mLock) { Timer timer = removeLocked(arg); @@ -791,40 +839,58 @@ class AnrTimer<V> { } /** The feature is enabled. */ + @Override boolean enabled() { return true; } } /** - * Start a timer associated with arg. If a timer already exists with the same arg, then that - * timer is canceled and a new timer is created. This returns false if the timer cannot be - * created. + * Start a timer associated with arg. The same object must be used to cancel, accept, or + * discard a timer later. If a timer already exists with the same arg, then the existing timer + * is canceled and a new timer is created. + * + * @param arg The key by which the timer is known. This is never examined or modified. + * @param pid The Linux process ID of the target being timed. + * @param uid The Linux user ID of the target being timed. + * @param timeoutMs The timer timeout, in milliseconds. + * @return true if the timer was successfully created. */ boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) { return mFeature.start(arg, pid, uid, timeoutMs); } /** - * Cancel a running timer and remove it from any list. This returns true if the timer was - * found and false otherwise. It is not an error to cancel a non-existent timer. It is also - * not an error to cancel an expired timer. + * Cancel the running timer associated with arg. The timer is forgotten. If the timer has + * expired, the call is treated as a discard. No errors are reported if the timer does not + * exist or if the timer has expired. + * + * @return true if the timer was found and was running. */ boolean cancel(@NonNull V arg) { return mFeature.cancel(arg); } /** - * Accept an expired timer. This returns false if the timer was not found or if the timer was - * not expired. + * Accept the expired timer associated with arg. This indicates that the caller considers the + * timer expiration to be a true ANR. (See {@link #discard} for an alternate response.) It is + * an error to accept a running timer, however the running timer will be canceled. + * + * @return true if the timer was found and was expired. */ boolean accept(@NonNull V arg) { return mFeature.accept(arg); } /** - * Discard an expired timer. This returns false if the timer was not found or if the timer was - * not expired. + * Discard the expired timer associated with arg. This indicates that the caller considers the + * timer expiration to be a false ANR. ((See {@link #accept} for an alternate response.) One + * reason to discard an expired timer is if the process being timed was also being debugged: + * such a process could be stopped at a breakpoint and its failure to respond would not be an + * error. It is an error to discard a running timer, however the running timer will be + * canceled. + * + * @return true if the timer was found and was expired. */ boolean discard(@NonNull V arg) { return mFeature.discard(arg); @@ -913,7 +979,10 @@ class AnrTimer<V> { private static void dump(IndentingPrintWriter ipw, int seq, Error err) { ipw.format("%2d: op:%s tag:%s issue:%s arg:%s\n", seq, err.operation, err.tag, err.issue, err.arg); - ipw.format(" date:%s\n", err.date); + + final long offset = System.currentTimeMillis() - SystemClock.elapsedRealtime(); + final long etime = offset + err.timestamp; + ipw.println(" date:" + TimeMigrationUtils.formatMillisWithFixedFormat(etime)); ipw.increaseIndent(); for (int i = 0; i < err.stack.length; i++) { ipw.println(" " + err.stack[i].toString()); diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index a42890707368..d19eae5b0709 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -258,7 +258,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue { private static final int MSG_PROCESS_FREEZABLE_CHANGED = 6; private static final int MSG_UID_STATE_CHANGED = 7; - // Required when Flags.anrTimerServiceEnabled is false. + // Required when Flags.anrTimerServiceEnabled is false. This constant should be deleted if and + // when the flag is fused on. private static final int MSG_DELIVERY_TIMEOUT_SOFT = 8; private void enqueueUpdateRunningList() { @@ -274,7 +275,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue { updateRunningList(); return true; } - // Required when Flags.anrTimerServiceEnabled is false. + // Required when Flags.anrTimerServiceEnabled is false. This case should be deleted if + // and when the flag is fused on. case MSG_DELIVERY_TIMEOUT_SOFT: { synchronized (mService) { deliveryTimeoutSoftLocked((BroadcastProcessQueue) msg.obj, msg.arg1); @@ -1169,7 +1171,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue { r.resultTo = null; } - // Required when Flags.anrTimerServiceEnabled is false. + // Required when Flags.anrTimerServiceEnabled is false. This function can be replaced with a + // single call to {@code mAnrTimer.start()} if and when the flag is fused on. private void startDeliveryTimeoutLocked(@NonNull BroadcastProcessQueue queue, int softTimeoutMillis) { if (mAnrTimer.serviceEnabled()) { @@ -1181,7 +1184,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } } - // Required when Flags.anrTimerServiceEnabled is false. + // Required when Flags.anrTimerServiceEnabled is false. This function can be replaced with a + // single call to {@code mAnrTimer.cancel()} if and when the flag is fused on. private void cancelDeliveryTimeoutLocked(@NonNull BroadcastProcessQueue queue) { mAnrTimer.cancel(queue); if (!mAnrTimer.serviceEnabled()) { @@ -1189,7 +1193,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } } - // Required when Flags.anrTimerServiceEnabled is false. + // Required when Flags.anrTimerServiceEnabled is false. This function can be deleted entirely + // if and when the flag is fused on. private void deliveryTimeoutSoftLocked(@NonNull BroadcastProcessQueue queue, int softTimeoutMillis) { if (queue.app != null) { diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java index 4b622f589adb..903cb7bcfaed 100644 --- a/services/core/java/com/android/server/am/CoreSettingsObserver.java +++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java @@ -175,13 +175,15 @@ final class CoreSettingsObserver extends ContentObserver { TextFlags.ENABLE_NEW_CONTEXT_MENU_DEFAULT)); // Register all text aconfig flags. - for (String flag : TextFlags.TEXT_ACONFIGS_FLAGS) { + for (int i = 0; i < TextFlags.TEXT_ACONFIGS_FLAGS.length; i++) { + final String flag = TextFlags.TEXT_ACONFIGS_FLAGS[i]; + final boolean defaultValue = TextFlags.TEXT_ACONFIG_DEFAULT_VALUE[i]; sDeviceConfigEntries.add(new DeviceConfigEntry<Boolean>( TextFlags.NAMESPACE, flag, TextFlags.getKeyForFlag(flag), boolean.class, - false)); // All aconfig flags are false by default. + defaultValue)); } // add other device configs here... } diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 4572766371ec..e0e6cade5f27 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -1439,7 +1439,7 @@ public final class ProcessList { } public static long computeNextPssTime(int procState, ProcStateMemTracker tracker, boolean test, - boolean sleeping, long now) { + boolean sleeping, long now, long earliest) { boolean first; float scalingFactor; final int memState = sProcStateToProcMem[procState]; @@ -1470,7 +1470,7 @@ public final class ProcessList { if (delay > PSS_MAX_INTERVAL) { delay = PSS_MAX_INTERVAL; } - return now + delay; + return Math.max(now + delay, earliest); } long getMemLevel(int adjustment) { diff --git a/services/core/java/com/android/server/am/ProcessProfileRecord.java b/services/core/java/com/android/server/am/ProcessProfileRecord.java index db74f1aed422..940c58b7a5f0 100644 --- a/services/core/java/com/android/server/am/ProcessProfileRecord.java +++ b/services/core/java/com/android/server/am/ProcessProfileRecord.java @@ -142,6 +142,11 @@ final class ProcessProfileRecord { final AtomicLong mCurCpuTime = new AtomicLong(0); /** + * How long the process has spent on waiting in the runqueue since fork. + */ + final AtomicLong mLastCpuDelayTime = new AtomicLong(0); + + /** * Last selected memory trimming level. */ @CompositeRWLock({"mService", "mProcLock"}) @@ -570,7 +575,11 @@ final class ProcessProfileRecord { @GuardedBy("mProfilerLock") long computeNextPssTime(int procState, boolean test, boolean sleeping, long now) { - return ProcessList.computeNextPssTime(procState, mProcStateMemTracker, test, sleeping, now); + return ProcessList.computeNextPssTime(procState, mProcStateMemTracker, test, sleeping, now, + // Cap the Pss time to make sure no Pss is collected during the very few + // minutes after the system is boot, given the system is already busy. + Math.max(mService.mBootCompletedTimestamp, mService.mLastIdleTime) + + mService.mConstants.FULL_PSS_MIN_INTERVAL); } private static void commitNextPssTime(ProcStateMemTracker tracker) { diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java index 1ba1f55af71b..16e3fdf2a6ab 100644 --- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java @@ -163,9 +163,12 @@ public class SettingsToPropertiesMapper { "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 +264,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 +351,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/audio/AudioManagerShellCommand.java b/services/core/java/com/android/server/audio/AudioManagerShellCommand.java index 241abafe6179..85acf707677a 100644 --- a/services/core/java/com/android/server/audio/AudioManagerShellCommand.java +++ b/services/core/java/com/android/server/audio/AudioManagerShellCommand.java @@ -47,6 +47,12 @@ class AudioManagerShellCommand extends ShellCommand { return setEncodedSurroundMode(); case "get-encoded-surround-mode": return getEncodedSurroundMode(); + case "set-sound-dose-value": + return setSoundDoseValue(); + case "get-sound-dose-value": + return getSoundDoseValue(); + case "reset-sound-dose-timeout": + return resetSoundDoseTimeout(); } return 0; } @@ -66,6 +72,12 @@ class AudioManagerShellCommand extends ShellCommand { pw.println(" Sets the encoded surround sound mode to SURROUND_SOUND_MODE"); pw.println(" get-encoded-surround-mode"); pw.println(" Returns the encoded surround sound mode"); + pw.println(" set-sound-dose-value"); + pw.println(" Sets the current sound dose value"); + pw.println(" get-sound-dose-value"); + pw.println(" Returns the current sound dose value"); + pw.println(" reset-sound-dose-timeout"); + pw.println(" Resets the sound dose timeout used for momentary exposure"); } private int setSurroundFormatEnabled() { @@ -162,4 +174,46 @@ class AudioManagerShellCommand extends ShellCommand { getOutPrintWriter().println("Encoded surround mode: " + am.getEncodedSurroundMode()); return 0; } + + private int setSoundDoseValue() { + String soundDoseValueText = getNextArg(); + + if (soundDoseValueText == null) { + getErrPrintWriter().println("Error: no sound dose value specified"); + return 1; + } + + float soundDoseValue = 0.f; + try { + soundDoseValue = Float.parseFloat(soundDoseValueText); + } catch (NumberFormatException e) { + getErrPrintWriter().println("Error: wrong format specified for sound dose"); + return 1; + } + + if (soundDoseValue < 0) { + getErrPrintWriter().println("Error: invalid value of sound dose"); + return 1; + } + + final Context context = mService.mContext; + final AudioManager am = context.getSystemService(AudioManager.class); + am.setCsd(soundDoseValue); + return 0; + } + + private int getSoundDoseValue() { + final Context context = mService.mContext; + final AudioManager am = context.getSystemService(AudioManager.class); + getOutPrintWriter().println("Sound dose value: " + am.getCsd()); + return 0; + } + + private int resetSoundDoseTimeout() { + final Context context = mService.mContext; + final AudioManager am = context.getSystemService(AudioManager.class); + am.setCsd(-1.f); + getOutPrintWriter().println("Reset sound dose momentary exposure timeout"); + return 0; + } } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 0aa9cc11c432..3243385c3b18 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -36,7 +36,6 @@ import static android.os.Process.INVALID_UID; import static android.provider.Settings.Secure.VOLUME_HUSH_MUTE; import static android.provider.Settings.Secure.VOLUME_HUSH_OFF; import static android.provider.Settings.Secure.VOLUME_HUSH_VIBRATE; - import static com.android.server.audio.SoundDoseHelper.ACTION_CHECK_MUSIC_ACTIVE; import static com.android.server.utils.EventLogger.Event.ALOGE; import static com.android.server.utils.EventLogger.Event.ALOGI; @@ -73,7 +72,6 @@ import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.content.pm.UserInfo; import android.content.res.Configuration; import android.content.res.Resources; @@ -267,6 +265,8 @@ public class AudioService extends IAudioService.Stub private final SettingsAdapter mSettings; private final AudioPolicyFacade mAudioPolicy; + private final MusicFxHelper mMusicFxHelper; + /** Debug audio mode */ protected static final boolean DEBUG_MODE = false; @@ -407,9 +407,15 @@ public class AudioService extends IAudioService.Stub private static final int MSG_CONFIGURATION_CHANGED = 54; private static final int MSG_BROADCAST_MASTER_MUTE = 55; - /** Messages handled by the {@link SoundDoseHelper}. */ + /** + * Messages handled by the {@link SoundDoseHelper}, do not exceed + * {@link MUSICFX_HELPER_MSG_START}. + */ /*package*/ static final int SAFE_MEDIA_VOLUME_MSG_START = 1000; + /** Messages handled by the {@link MusicFxHelper}. */ + /*package*/ static final int MUSICFX_HELPER_MSG_START = 1100; + // start of messages handled under wakelock // these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(), // and not with sendMsg(..., ..., SENDMSG_QUEUE, ...) @@ -1304,6 +1310,8 @@ public class AudioService extends IAudioService.Stub 0 /* arg1 */, 0 /* arg2 */, null /* obj */, 0 /* delay */); mDisplayManager = context.getSystemService(DisplayManager.class); + + mMusicFxHelper = new MusicFxHelper(mContext, mAudioHandler); } private void initVolumeStreamStates() { @@ -9456,11 +9464,21 @@ public class AudioService extends IAudioService.Stub onConfigurationChanged(); break; + case MusicFxHelper.MSG_EFFECT_CLIENT_GONE: + mMusicFxHelper.handleMessage(msg); + break; + + case SoundDoseHelper.MSG_CONFIGURE_SAFE_MEDIA: + case SoundDoseHelper.MSG_CONFIGURE_SAFE_MEDIA_FORCED: + case SoundDoseHelper.MSG_PERSIST_SAFE_VOLUME_STATE: + case SoundDoseHelper.MSG_PERSIST_MUSIC_ACTIVE_MS: + case SoundDoseHelper.MSG_PERSIST_CSD_VALUES: + case SoundDoseHelper.MSG_CSD_UPDATE_ATTENUATION: + mSoundDoseHelper.handleMessage(msg); + break; + default: - if (msg.what >= SAFE_MEDIA_VOLUME_MSG_START) { - // msg could be for the SoundDoseHelper - mSoundDoseHelper.handleMessage(msg); - } + Log.e(TAG, "Unsupported msgId " + msg.what); } } } @@ -9695,7 +9713,7 @@ public class AudioService extends IAudioService.Stub } } else if (action.equals(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION) || action.equals(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION)) { - handleAudioEffectBroadcast(context, intent); + mMusicFxHelper.handleAudioEffectBroadcast(context, intent); } else if (action.equals(Intent.ACTION_PACKAGES_SUSPENDED)) { final int[] suspendedUids = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST); final String[] suspendedPackages = @@ -9750,27 +9768,6 @@ public class AudioService extends IAudioService.Stub } } // end class AudioServiceUserRestrictionsListener - private void handleAudioEffectBroadcast(Context context, Intent intent) { - String target = intent.getPackage(); - if (target != null) { - Log.w(TAG, "effect broadcast already targeted to " + target); - return; - } - intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); - // TODO this should target a user-selected panel - List<ResolveInfo> ril = context.getPackageManager().queryBroadcastReceivers( - intent, 0 /* flags */); - if (ril != null && ril.size() != 0) { - ResolveInfo ri = ril.get(0); - if (ri != null && ri.activityInfo != null && ri.activityInfo.packageName != null) { - intent.setPackage(ri.activityInfo.packageName); - context.sendBroadcastAsUser(intent, UserHandle.ALL); - return; - } - } - Log.w(TAG, "couldn't find receiver package for effect intent"); - } - private void killBackgroundUserProcessesWithRecordAudioPermission(UserInfo oldUser) { PackageManager pm = mContext.getPackageManager(); // Find the home activity of the user. It should not be killed to avoid expensive restart, diff --git a/services/core/java/com/android/server/audio/MusicFxHelper.java b/services/core/java/com/android/server/audio/MusicFxHelper.java new file mode 100644 index 000000000000..6c0fef5f628d --- /dev/null +++ b/services/core/java/com/android/server/audio/MusicFxHelper.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.audio; + +import static android.content.pm.PackageManager.MATCH_ANY_USER; +import static com.android.server.audio.AudioService.MUSICFX_HELPER_MSG_START; + +import android.annotation.NonNull; +import android.app.ActivityManager; +import android.app.IUidObserver; +import android.app.UidObserver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.UserInfo; +import android.media.AudioManager; +import android.media.audiofx.AudioEffect; +import android.os.Binder; +import android.os.IBinder; +import android.os.Message; +import android.os.RemoteException; +import android.os.UserHandle; +import android.util.Log; +import android.util.SparseArray; + +import com.android.internal.annotations.GuardedBy; +import com.android.server.audio.AudioService.AudioHandler; + +import java.util.ArrayList; +import java.util.List; + +/** + * MusicFx management. + * . + */ +public class MusicFxHelper { + private static final String TAG = "AS.MusicFxHelper"; + + @NonNull private final Context mContext; + + @NonNull private final AudioHandler mAudioHandler; + + // Synchronization UidSessionMap access between UidObserver and AudioServiceBroadcastReceiver. + private final Object mClientUidMapLock = new Object(); + + // The binder token identifying the UidObserver registration. + private IBinder mUidObserverToken = null; + + // Hashmap of UID and list of open sessions for this UID. + @GuardedBy("mClientUidMapLock") + private SparseArray<List<Integer>> mClientUidSessionMap = new SparseArray<>(); + + /*package*/ static final int MSG_EFFECT_CLIENT_GONE = MUSICFX_HELPER_MSG_START + 1; + + // UID observer for effect MusicFx clients + private final IUidObserver mEffectUidObserver = new UidObserver() { + @Override public void onUidGone(int uid, boolean disabled) { + Log.w(TAG, " send MSG_EFFECT_CLIENT_GONE"); + mAudioHandler.sendMessageAtTime( + mAudioHandler.obtainMessage(MSG_EFFECT_CLIENT_GONE, + uid /* arg1 */, 0 /* arg2 */, + null /* obj */), 0 /* delay */); + } + }; + + // BindService connection implementation, we don't need any implementation now + private ServiceConnection mMusicFxBindConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + Log.d(TAG, " service connected to " + name); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + Log.d(TAG, " service disconnected from " + name); + } + }; + + MusicFxHelper(@NonNull Context context, @NonNull AudioHandler audioHandler) { + mContext = context; + mAudioHandler = audioHandler; + } + + /** + * Handle the broadcast {@link #ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION} and + * {@link #ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION} intents. + * + * If the intent is {@link #ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION}: + * - If the MusicFx process is not running, call bindService with AUTO_CREATE to create. + * - If this is the first audio session in MusicFx, call set foreground service delegate. + * - If this is the first audio session for a given UID, add the UID into observer. + * + * If the intent is {@link #ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION}: + * - MusicFx will not be foreground delegated anymore. + * - The KeepAliveService of MusicFx will be unbound. + * - The UidObserver will be removed. + */ + public void handleAudioEffectBroadcast(Context context, Intent intent) { + String target = intent.getPackage(); + if (target != null) { + Log.w(TAG, "effect broadcast already targeted to " + target); + return; + } + intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); + final PackageManager pm = context.getPackageManager(); + // TODO this should target a user-selected panel + List<ResolveInfo> ril = pm.queryBroadcastReceivers(intent, 0 /* flags */); + if (ril != null && ril.size() != 0) { + ResolveInfo ri = ril.get(0); + final String senderPackageName = intent.getStringExtra(AudioEffect.EXTRA_PACKAGE_NAME); + try { + final int senderUid = pm.getPackageUidAsUser(senderPackageName, + PackageManager.PackageInfoFlags.of(MATCH_ANY_USER), getCurrentUserId()); + if (ri != null && ri.activityInfo != null && ri.activityInfo.packageName != null) { + intent.setPackage(ri.activityInfo.packageName); + synchronized (mClientUidMapLock) { + setMusicFxServiceWithObserver(context, intent, senderUid); + } + context.sendBroadcastAsUser(intent, UserHandle.ALL); + return; + } + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Not able to find UID from package: " + senderPackageName + " error: " + + e); + } + } + Log.w(TAG, "couldn't find receiver package for effect intent"); + } + + /** + * Handle the UidObserver onUidGone callback of MusicFx clients. + * All open audio sessions of this UID will be closed. + * If this is the last UID for MusicFx: + * - MusicFx will not be foreground delegated anymore. + * - The KeepAliveService of MusicFx will be unbound. + * - The UidObserver will be removed. + */ + public void handleEffectClientUidGone(int uid) { + synchronized (mClientUidMapLock) { + Log.w(TAG, " inside handle MSG_EFFECT_CLIENT_GONE"); + // Once the uid is no longer running, close all remain audio session(s) for this UID + if (mClientUidSessionMap.get(Integer.valueOf(uid)) != null) { + final List<Integer> sessions = mClientUidSessionMap.get(Integer.valueOf(uid)); + Log.i(TAG, "UID " + uid + " gone, closing " + sessions.size() + " sessions"); + for (Integer session : sessions) { + Intent intent = new Intent( + AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION); + intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, session); + setMusicFxServiceWithObserver(mContext, intent, uid); + Log.i(TAG, "Close session " + session + " of UID " + uid); + } + mClientUidSessionMap.remove(Integer.valueOf(uid)); + } + } + } + + @GuardedBy("mClientUidMapLock") + private void setMusicFxServiceWithObserver(Context context, Intent intent, int senderUid) { + PackageManager pm = context.getPackageManager(); + try { + final int audioSession = intent.getIntExtra(AudioEffect.EXTRA_AUDIO_SESSION, + AudioManager.AUDIO_SESSION_ID_GENERATE); + if (AudioManager.AUDIO_SESSION_ID_GENERATE == audioSession) { + Log.e(TAG, "Intent missing audio session: " + audioSession); + return; + } + + // only apply to com.android.musicfx and KeepAliveService for now + final String musicFxPackageName = "com.android.musicfx"; + final String musicFxKeepAliveService = "com.android.musicfx.KeepAliveService"; + final int musicFxUid = pm.getPackageUidAsUser(musicFxPackageName, + PackageManager.PackageInfoFlags.of(MATCH_ANY_USER), getCurrentUserId()); + + if (intent.getAction().equals(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION)) { + List<Integer> sessions = new ArrayList<>(); + Log.d(TAG, "UID " + senderUid + ", open MusicFx session " + audioSession); + // start foreground service delegate and register UID observer with the first + // session of first UID open + if (0 == mClientUidSessionMap.size()) { + final int procState = ActivityManager.getService().getPackageProcessState( + musicFxPackageName, this.getClass().getPackage().getName()); + // if musicfx process not in binding state, call bindService with AUTO_CREATE + if (procState > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) { + Intent bindIntent = new Intent().setClassName(musicFxPackageName, + musicFxKeepAliveService); + context.bindServiceAsUser( + bindIntent, mMusicFxBindConnection, Context.BIND_AUTO_CREATE, + UserHandle.of(getCurrentUserId())); + Log.i(TAG, "bindService to " + musicFxPackageName); + } + + Log.i(TAG, "Package " + musicFxPackageName + " uid " + musicFxUid + + " procState " + procState); + } else if (mClientUidSessionMap.get(Integer.valueOf(senderUid)) != null) { + sessions = mClientUidSessionMap.get(Integer.valueOf(senderUid)); + if (sessions.contains(audioSession)) { + Log.e(TAG, "Audio session " + audioSession + " already exist for UID " + + senderUid + ", abort"); + return; + } + } + // first session of this UID + if (sessions.size() == 0) { + // call registerUidObserverForUids with the first UID and first session + if (mClientUidSessionMap.size() == 0 || mUidObserverToken == null) { + mUidObserverToken = ActivityManager.getService().registerUidObserverForUids( + mEffectUidObserver, ActivityManager.UID_OBSERVER_GONE, + ActivityManager.PROCESS_STATE_UNKNOWN, null, new int[]{senderUid}); + Log.i(TAG, "UID " + senderUid + " registered to observer"); + } else { + // add UID to observer for each new UID + ActivityManager.getService().addUidToObserver(mUidObserverToken, TAG, + senderUid); + Log.i(TAG, "UID " + senderUid + " addeded to observer"); + } + } + + sessions.add(Integer.valueOf(audioSession)); + mClientUidSessionMap.put(Integer.valueOf(senderUid), sessions); + } else { + if (mClientUidSessionMap.get(senderUid) != null) { + Log.d(TAG, "UID " + senderUid + ", close MusicFx session " + audioSession); + List<Integer> sessions = mClientUidSessionMap.get(Integer.valueOf(senderUid)); + sessions.remove(Integer.valueOf(audioSession)); + if (0 == sessions.size()) { + mClientUidSessionMap.remove(Integer.valueOf(senderUid)); + } else { + mClientUidSessionMap.put(Integer.valueOf(senderUid), sessions); + } + + // stop foreground service delegate and unregister UID observer with the + // last session of last UID close + if (0 == mClientUidSessionMap.size()) { + ActivityManager.getService().unregisterUidObserver(mEffectUidObserver); + mClientUidSessionMap.clear(); + context.unbindService(mMusicFxBindConnection); + Log.i(TAG, " remove all sessions, unregister UID observer, and unbind " + + musicFxPackageName); + } + } else { + // if the audio session already closed, print an error + Log.e(TAG, "UID " + senderUid + " close audio session " + audioSession + + " which does not exist"); + } + } + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Not able to find UID from package: " + e); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException " + e + " with handling intent"); + } + } + + private int getCurrentUserId() { + final long ident = Binder.clearCallingIdentity(); + try { + UserInfo currentUser = ActivityManager.getService().getCurrentUser(); + return currentUser.id; + } catch (RemoteException e) { + // Activity manager not running, nothing we can do assume user 0. + } finally { + Binder.restoreCallingIdentity(ident); + } + return UserHandle.USER_SYSTEM; + } + + /*package*/ void handleMessage(Message msg) { + switch (msg.what) { + case MSG_EFFECT_CLIENT_GONE: + Log.w(TAG, " handle MSG_EFFECT_CLIENT_GONE"); + handleEffectClientUidGone(msg.arg1 /* uid */); + break; + default: + Log.e(TAG, "Unexpected msg to handle in MusicFxHelper: " + msg.what); + break; + } + } + +} diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java index 81365bfbf2a6..d65c7c2c526d 100644 --- a/services/core/java/com/android/server/audio/SoundDoseHelper.java +++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java @@ -108,11 +108,11 @@ public class SoundDoseHelper { private static final int SAFE_MEDIA_VOLUME_INACTIVE = 2; // confirmed private static final int SAFE_MEDIA_VOLUME_ACTIVE = 3; // unconfirmed - private static final int MSG_CONFIGURE_SAFE_MEDIA = SAFE_MEDIA_VOLUME_MSG_START + 1; - private static final int MSG_CONFIGURE_SAFE_MEDIA_FORCED = SAFE_MEDIA_VOLUME_MSG_START + 2; - private static final int MSG_PERSIST_SAFE_VOLUME_STATE = SAFE_MEDIA_VOLUME_MSG_START + 3; - private static final int MSG_PERSIST_MUSIC_ACTIVE_MS = SAFE_MEDIA_VOLUME_MSG_START + 4; - private static final int MSG_PERSIST_CSD_VALUES = SAFE_MEDIA_VOLUME_MSG_START + 5; + /*package*/ static final int MSG_CONFIGURE_SAFE_MEDIA = SAFE_MEDIA_VOLUME_MSG_START + 1; + /*package*/ static final int MSG_CONFIGURE_SAFE_MEDIA_FORCED = SAFE_MEDIA_VOLUME_MSG_START + 2; + /*package*/ static final int MSG_PERSIST_SAFE_VOLUME_STATE = SAFE_MEDIA_VOLUME_MSG_START + 3; + /*package*/ static final int MSG_PERSIST_MUSIC_ACTIVE_MS = SAFE_MEDIA_VOLUME_MSG_START + 4; + /*package*/ static final int MSG_PERSIST_CSD_VALUES = SAFE_MEDIA_VOLUME_MSG_START + 5; /*package*/ static final int MSG_CSD_UPDATE_ATTENUATION = SAFE_MEDIA_VOLUME_MSG_START + 6; private static final int UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX = (20 * 3600 * 1000); // 20 hours @@ -189,6 +189,8 @@ public class SoundDoseHelper { private final AtomicBoolean mEnableCsd = new AtomicBoolean(false); + private final AtomicBoolean mForceCsdProperty = new AtomicBoolean(false); + private final Object mCsdAsAFeatureLock = new Object(); @GuardedBy("mCsdAsAFeatureLock") @@ -375,9 +377,21 @@ public class SoundDoseHelper { } } + private boolean updateCsdForTestApi() { + if (mForceCsdProperty.get() != SystemProperties.getBoolean( + SYSTEM_PROPERTY_SAFEMEDIA_CSD_FORCE, false)) { + updateCsdEnabled("SystemPropertiesChangeCallback"); + } + + return mEnableCsd.get(); + } + float getCsd() { if (!mEnableCsd.get()) { - return -1.f; + // since this will only be called by a test api enable csd if system property is set + if (!updateCsdForTestApi()) { + return -1.f; + } } final ISoundDose soundDose = mSoundDose.get(); @@ -396,7 +410,10 @@ public class SoundDoseHelper { void setCsd(float csd) { if (!mEnableCsd.get()) { - return; + // since this will only be called by a test api enable csd if system property is set + if (!updateCsdForTestApi()) { + return; + } } SoundDoseRecord[] doseRecordsArray; @@ -430,7 +447,10 @@ public class SoundDoseHelper { void resetCsdTimeouts() { if (!mEnableCsd.get()) { - return; + // since this will only be called by a test api enable csd if system property is set + if (!updateCsdForTestApi()) { + return; + } } synchronized (mCsdStateLock) { @@ -440,7 +460,10 @@ public class SoundDoseHelper { void forceUseFrameworkMel(boolean useFrameworkMel) { if (!mEnableCsd.get()) { - return; + // since this will only be called by a test api enable csd if system property is set + if (!updateCsdForTestApi()) { + return; + } } final ISoundDose soundDose = mSoundDose.get(); @@ -458,7 +481,10 @@ public class SoundDoseHelper { void forceComputeCsdOnAllDevices(boolean computeCsdOnAllDevices) { if (!mEnableCsd.get()) { - return; + // since this will only be called by a test api enable csd if system property is set + if (!updateCsdForTestApi()) { + return; + } } final ISoundDose soundDose = mSoundDose.get(); @@ -488,7 +514,7 @@ public class SoundDoseHelper { try { return soundDose.isSoundDoseHalSupported(); } catch (RemoteException e) { - Log.e(TAG, "Exception while forcing CSD computation on all devices", e); + Log.e(TAG, "Exception while querying the csd enabled status", e); } return false; } @@ -544,7 +570,7 @@ public class SoundDoseHelper { audioDeviceCategory.csdCompatible = isHeadphone; soundDose.setAudioDeviceCategory(audioDeviceCategory); } catch (RemoteException e) { - Log.e(TAG, "Exception while forcing the internal MEL computation", e); + Log.e(TAG, "Exception while setting the audio device category", e); } } @@ -894,7 +920,7 @@ public class SoundDoseHelper { mCachedAudioDeviceCategories.clear(); } } catch (RemoteException e) { - Log.e(TAG, "Exception while forcing the internal MEL computation", e); + Log.e(TAG, "Exception while initializing the cached audio device categories", e); } synchronized (mCsdAsAFeatureLock) { @@ -991,19 +1017,20 @@ public class SoundDoseHelper { } private void updateCsdEnabled(String caller) { - boolean csdForce = SystemProperties.getBoolean(SYSTEM_PROPERTY_SAFEMEDIA_CSD_FORCE, false); + mForceCsdProperty.set(SystemProperties.getBoolean(SYSTEM_PROPERTY_SAFEMEDIA_CSD_FORCE, + false)); // we are using the MCC overlaid legacy flag used for the safe volume enablement // to determine whether the MCC enforces any safe hearing standard. boolean mccEnforcedSafeMedia = mContext.getResources().getBoolean( com.android.internal.R.bool.config_safe_media_volume_enabled); boolean csdEnable = mContext.getResources().getBoolean( R.bool.config_safe_sound_dosage_enabled); - boolean newEnabledCsd = (mccEnforcedSafeMedia && csdEnable) || csdForce; + boolean newEnabledCsd = (mccEnforcedSafeMedia && csdEnable) || mForceCsdProperty.get(); synchronized (mCsdAsAFeatureLock) { if (!mccEnforcedSafeMedia && csdEnable) { mIsCsdAsAFeatureAvailable = true; - newEnabledCsd = mIsCsdAsAFeatureEnabled || csdForce; + newEnabledCsd = mIsCsdAsAFeatureEnabled || mForceCsdProperty.get(); Log.v(TAG, caller + ": CSD as a feature is not enforced and enabled: " + newEnabledCsd); } else { diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java index 4538cad513d6..1760bb3c7a6a 100644 --- a/services/core/java/com/android/server/biometrics/AuthService.java +++ b/services/core/java/com/android/server/biometrics/AuthService.java @@ -41,6 +41,7 @@ import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.ComponentInfoInternal; import android.hardware.biometrics.IAuthService; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; +import android.hardware.biometrics.IBiometricPromptStatusListener; import android.hardware.biometrics.IBiometricService; import android.hardware.biometrics.IBiometricServiceReceiver; import android.hardware.biometrics.IInvalidationCallback; @@ -357,6 +358,18 @@ public class AuthService extends SystemService { } @Override + public void registerBiometricPromptStatusListener( + IBiometricPromptStatusListener listener) throws RemoteException { + checkInternalPermission(); + final long identity = Binder.clearCallingIdentity(); + try { + mBiometricService.registerBiometricPromptStatusListener(listener); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override public void invalidateAuthenticatorIds(int userId, int fromSensorId, IInvalidationCallback callback) throws RemoteException { checkInternalPermission(); diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index 1898b8015462..9569f23e8d49 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -41,6 +41,7 @@ import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricPrompt; import android.hardware.biometrics.IBiometricAuthenticator; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; +import android.hardware.biometrics.IBiometricPromptStatusListener; import android.hardware.biometrics.IBiometricSensorReceiver; import android.hardware.biometrics.IBiometricService; import android.hardware.biometrics.IBiometricServiceReceiver; @@ -88,6 +89,7 @@ import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Supplier; @@ -105,6 +107,8 @@ public class BiometricService extends SystemService { @VisibleForTesting final SettingObserver mSettingObserver; private final List<EnabledOnKeyguardCallback> mEnabledOnKeyguardCallbacks; + private final ConcurrentLinkedQueue<BiometricPromptStatusListener> + mBiometricPromptStatusListeners; private final Random mRandom = new Random(); @NonNull private final Supplier<Long> mRequestCounter; @NonNull private final BiometricContext mBiometricContext; @@ -425,6 +429,42 @@ public class BiometricService extends SystemService { } } + final class BiometricPromptStatusListener implements IBinder.DeathRecipient { + private final IBiometricPromptStatusListener mBiometricPromptStatusListener; + + BiometricPromptStatusListener(IBiometricPromptStatusListener callback) { + mBiometricPromptStatusListener = callback; + } + + void notifyBiometricPromptShowing() { + try { + mBiometricPromptStatusListener.onBiometricPromptShowing(); + } catch (DeadObjectException e) { + Slog.w(TAG, "Death while invoking notifyHandleAuthenticate", e); + mBiometricPromptStatusListeners.remove(this); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to invoke notifyHandleAuthenticate", e); + } + } + + void notifyBiometricPromptIdle() { + try { + mBiometricPromptStatusListener.onBiometricPromptIdle(); + } catch (DeadObjectException e) { + Slog.w(TAG, "Death while invoking notifyDialogDismissed", e); + mBiometricPromptStatusListeners.remove(this); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to invoke notifyDialogDismissed", e); + } + } + + @Override + public void binderDied() { + Slog.e(TAG, "Biometric prompt callback binder died"); + mBiometricPromptStatusListeners.remove(this); + } + } + // Receives events from individual biometric sensors. private IBiometricSensorReceiver createBiometricSensorReceiver(final long requestId) { return new IBiometricSensorReceiver.Stub() { @@ -705,6 +745,22 @@ public class BiometricService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override // Binder call + public void registerBiometricPromptStatusListener(IBiometricPromptStatusListener callback) { + super.registerBiometricPromptStatusListener_enforcePermission(); + + BiometricPromptStatusListener biometricPromptStatusListener = + new BiometricPromptStatusListener(callback); + mBiometricPromptStatusListeners.add(biometricPromptStatusListener); + + if (mAuthSession != null) { + biometricPromptStatusListener.notifyBiometricPromptShowing(); + } else { + biometricPromptStatusListener.notifyBiometricPromptIdle(); + } + } + + @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) + @Override // Binder call public void invalidateAuthenticatorIds(int userId, int fromSensorId, IInvalidationCallback callback) { @@ -1044,6 +1100,7 @@ public class BiometricService extends SystemService { mDevicePolicyManager = mInjector.getDevicePolicyManager(context); mImpl = new BiometricServiceWrapper(); mEnabledOnKeyguardCallbacks = new ArrayList<>(); + mBiometricPromptStatusListeners = new ConcurrentLinkedQueue<>(); mSettingObserver = mInjector.getSettingObserver(context, mHandler, mEnabledOnKeyguardCallbacks); mRequestCounter = mInjector.getRequestGenerator(); @@ -1158,6 +1215,7 @@ public class BiometricService extends SystemService { if (finished) { Slog.d(TAG, "handleOnError: AuthSession finished"); mAuthSession = null; + notifyAuthSessionChanged(); } } catch (RemoteException e) { Slog.e(TAG, "RemoteException", e); @@ -1186,6 +1244,7 @@ public class BiometricService extends SystemService { session.onDialogDismissed(reason, credentialAttestation); mAuthSession = null; + notifyAuthSessionChanged(); } private void handleOnTryAgainPressed(long requestId) { @@ -1235,6 +1294,7 @@ public class BiometricService extends SystemService { final boolean finished = session.onClientDied(); if (finished) { mAuthSession = null; + notifyAuthSessionChanged(); } } @@ -1349,6 +1409,16 @@ public class BiometricService extends SystemService { }); } + private void notifyAuthSessionChanged() { + for (BiometricPromptStatusListener listener : mBiometricPromptStatusListeners) { + if (mAuthSession == null) { + listener.notifyBiometricPromptIdle(); + } else { + listener.notifyBiometricPromptShowing(); + } + } + } + /** * handleAuthenticate() (above) which is called from BiometricPrompt determines which * modality/modalities to start authenticating with. authenticateInternal() should only be @@ -1386,6 +1456,7 @@ public class BiometricService extends SystemService { } catch (RemoteException e) { Slog.e(TAG, "RemoteException", e); } + notifyAuthSessionChanged(); } private void handleCancelAuthentication(long requestId) { @@ -1400,6 +1471,7 @@ public class BiometricService extends SystemService { if (finished) { Slog.d(TAG, "handleCancelAuthentication: AuthSession finished"); mAuthSession = null; + notifyAuthSessionChanged(); } } 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/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java index d8831fac5c79..03b0cfc3d844 100644 --- a/services/core/java/com/android/server/display/color/ColorDisplayService.java +++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java @@ -78,6 +78,7 @@ import com.android.internal.util.DumpUtils; import com.android.server.DisplayThread; import com.android.server.LocalServices; import com.android.server.SystemService; +import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.twilight.TwilightListener; import com.android.server.twilight.TwilightManager; import com.android.server.twilight.TwilightState; @@ -148,10 +149,12 @@ public final class ColorDisplayService extends SystemService { 1f, 1f, 1f, 1f }; + private final DisplayManagerFlags mDisplayManagerFlags = new DisplayManagerFlags(); + @VisibleForTesting final DisplayWhiteBalanceTintController mDisplayWhiteBalanceTintController = new DisplayWhiteBalanceTintController( - LocalServices.getService(DisplayManagerInternal.class)); + LocalServices.getService(DisplayManagerInternal.class), mDisplayManagerFlags); private final NightDisplayTintController mNightDisplayTintController = new NightDisplayTintController(); private final TintController mGlobalSaturationTintController = @@ -716,15 +719,16 @@ public final class ColorDisplayService extends SystemService { tintController.computeMatrixForCct(to)); tintController.setAppliedCct(to); } else { + final long duration = tintController.getTransitionDurationMilliseconds(to > from); Slog.d(TAG, tintController.getClass().getSimpleName() + " animation started: toCct=" - + to + " fromCct=" + from); + + to + " fromCct=" + from + " with duration=" + duration); ValueAnimator valueAnimator = ValueAnimator.ofInt(from, to); tintController.setAnimator(valueAnimator); final CctEvaluator evaluator = tintController.getEvaluator(); if (evaluator != null) { valueAnimator.setEvaluator(evaluator); } - valueAnimator.setDuration(tintController.getTransitionDurationMilliseconds()); + valueAnimator.setDuration(duration); valueAnimator.setInterpolator(AnimationUtils.loadInterpolator( getContext(), android.R.interpolator.linear)); valueAnimator.addUpdateListener((ValueAnimator animator) -> { diff --git a/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java b/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java index bf0139f347f8..b9fd1d070cbf 100644 --- a/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java +++ b/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java @@ -34,6 +34,7 @@ import android.view.SurfaceControl.DisplayPrimaries; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.display.feature.DisplayManagerFlags; import java.io.PrintWriter; @@ -65,6 +66,8 @@ final class DisplayWhiteBalanceTintController extends ColorTemperatureTintContro boolean mSetUp = false; private final float[] mMatrixDisplayWhiteBalance = new float[16]; private long mTransitionDuration; + private long mTransitionDurationIncrease; + private long mTransitionDurationDecrease; private Boolean mIsAvailable; // This feature becomes disallowed if the device is in an unsupported strong/light state. private boolean mIsAllowed = true; @@ -74,8 +77,12 @@ final class DisplayWhiteBalanceTintController extends ColorTemperatureTintContro private final DisplayManagerInternal mDisplayManagerInternal; - DisplayWhiteBalanceTintController(DisplayManagerInternal dm) { + private final DisplayManagerFlags mDisplayManagerFlags; + + DisplayWhiteBalanceTintController(DisplayManagerInternal dm, + DisplayManagerFlags displayManagerFlags) { mDisplayManagerInternal = dm; + mDisplayManagerFlags = displayManagerFlags; } @Override @@ -139,6 +146,16 @@ final class DisplayWhiteBalanceTintController extends ColorTemperatureTintContro mTransitionDuration = res.getInteger( R.integer.config_displayWhiteBalanceTransitionTime); + if (!mDisplayManagerFlags.isAdaptiveTone2Enabled()) { + mTransitionDurationDecrease = mTransitionDuration; + mTransitionDurationIncrease = mTransitionDuration; + } else { + mTransitionDurationIncrease = res.getInteger( + R.integer.config_displayWhiteBalanceTransitionTimeIncrease); + mTransitionDurationDecrease = res.getInteger( + R.integer.config_displayWhiteBalanceTransitionTimeDecrease); + } + int[] cctRangeMinimums = res.getIntArray( R.array.config_displayWhiteBalanceDisplayRangeMinimums); int[] steps = res.getIntArray(R.array.config_displayWhiteBalanceDisplaySteps); @@ -323,6 +340,11 @@ final class DisplayWhiteBalanceTintController extends ColorTemperatureTintContro } @Override + public long getTransitionDurationMilliseconds(boolean isIncreasing) { + return isIncreasing ? mTransitionDurationIncrease : mTransitionDurationDecrease; + } + + @Override public void dump(PrintWriter pw) { synchronized (mLock) { pw.println(" mSetUp = " + mSetUp); @@ -348,6 +370,9 @@ final class DisplayWhiteBalanceTintController extends ColorTemperatureTintContro pw.println(" mMatrixDisplayWhiteBalance = " + matrixToString(mMatrixDisplayWhiteBalance, 4)); pw.println(" mIsAllowed = " + mIsAllowed); + pw.println(" mTransitionDuration = " + mTransitionDuration); + pw.println(" mTransitionDurationIncrease = " + mTransitionDurationIncrease); + pw.println(" mTransitionDurationDecrease = " + mTransitionDurationDecrease); } } diff --git a/services/core/java/com/android/server/display/color/TintController.java b/services/core/java/com/android/server/display/color/TintController.java index 384333a574b5..716661dd6c3c 100644 --- a/services/core/java/com/android/server/display/color/TintController.java +++ b/services/core/java/com/android/server/display/color/TintController.java @@ -75,6 +75,10 @@ abstract class TintController { return TRANSITION_DURATION; } + public long getTransitionDurationMilliseconds(boolean direction) { + return TRANSITION_DURATION; + } + /** * Dump debug information. */ 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 738368b9848d..7050c5a4168f 100644 --- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java +++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java @@ -47,6 +47,10 @@ public class DisplayManagerFlags { Flags.FLAG_ENABLE_ADAPTIVE_TONE_IMPROVEMENTS_1, Flags::enableAdaptiveToneImprovements1); + private final FlagState mAdaptiveToneImprovements2 = new FlagState( + Flags.FLAG_ENABLE_ADAPTIVE_TONE_IMPROVEMENTS_2, + Flags::enableAdaptiveToneImprovements2); + private final FlagState mDisplayOffloadFlagState = new FlagState( Flags.FLAG_ENABLE_DISPLAY_OFFLOAD, Flags::enableDisplayOffload); @@ -84,6 +88,13 @@ public class DisplayManagerFlags { return mAdaptiveToneImprovements1.isEnabled(); } + /** + * Returns whether adaptive tone improvements are enabled + */ + public boolean isAdaptiveTone2Enabled() { + return mAdaptiveToneImprovements2.isEnabled(); + } + /** Returns whether resolution range voting feature is enabled or not. */ public boolean isDisplayResolutionRangeVotingEnabled() { return isExternalDisplayLimitModeEnabled(); @@ -138,7 +149,6 @@ public class DisplayManagerFlags { mFlagFunction = flagFunction; } - // TODO(b/297159910): Simplify using READ-ONLY flags when available. private boolean isEnabled() { if (mEnabledSet) { if (DEBUG) { @@ -155,14 +165,7 @@ public class DisplayManagerFlags { } private boolean flagOrSystemProperty(Supplier<Boolean> flagFunction, String flagName) { - boolean flagValue = false; - try { - flagValue = flagFunction.get(); - } catch (Throwable ex) { - if (DEBUG) { - Slog.i(TAG, "Flags not ready yet. Return false for " + flagName, ex); - } - } + boolean flagValue = flagFunction.get(); // TODO(b/299462337) Remove when the infrastructure is ready. if (Build.IS_ENG || Build.IS_USERDEBUG) { return SystemProperties.getBoolean("persist.sys." + flagName + "-override", 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 a30991372709..a85e10dcfe2e 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 @@ -35,6 +35,14 @@ flag { } flag { + name: "enable_adaptive_tone_improvements_2" + namespace: "display_manager" + description: "Feature flag for Further Adaptive Tone Improvements" + bug: "294762632" + is_fixed_read_only: true +} + +flag { name: "enable_display_resolution_range_voting" namespace: "display_manager" description: "Feature flag to enable voting for ranges of resolutions" diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java index c6a50ed2580d..7b844a099841 100644 --- a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java +++ b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java @@ -48,6 +48,7 @@ import com.android.internal.util.Preconditions; import com.android.server.LocalServices; import com.android.server.SystemServerInitThreadPool; import com.android.server.SystemService; +import com.android.text.flags.Flags; import java.io.File; import java.io.FileDescriptor; @@ -240,21 +241,35 @@ public final class FontManagerService extends IFontManager.Stub { mContext = context; mIsSafeMode = safeMode; - SystemServerInitThreadPool.submit(() -> { - initialize(); - - // Set system font map only if there is updatable font directory. - // If there is no updatable font directory, `initialize` will have already loaded the - // system font map, so there's no need to set the system font map again here. - if (mUpdatableFontDir != null) { - try { - Typeface.setSystemFontMap(getCurrentFontMap()); - } catch (IOException | ErrnoException e) { - Slog.w(TAG, "Failed to set system font map of system_server"); + if (Flags.useOptimizedBoottimeFontLoading()) { + Slog.i(TAG, "Using optimized boot-time font loading."); + SystemServerInitThreadPool.submit(() -> { + initialize(); + + // Set system font map only if there is updatable font directory. + // If there is no updatable font directory, `initialize` will have already loaded + // the system font map, so there's no need to set the system font map again here. + synchronized (mUpdatableFontDirLock) { + if (mUpdatableFontDir != null) { + setSystemFontMap(); + } } - } + serviceStarted.complete(null); + }, "FontManagerService_create"); + } else { + Slog.i(TAG, "Not using optimized boot-time font loading."); + initialize(); + setSystemFontMap(); serviceStarted.complete(null); - }, "FontManagerService_create"); + } + } + + private void setSystemFontMap() { + try { + Typeface.setSystemFontMap(getCurrentFontMap()); + } catch (IOException | ErrnoException e) { + Slog.w(TAG, "Failed to set system font map of system_server"); + } } @Nullable @@ -291,9 +306,11 @@ public final class FontManagerService extends IFontManager.Stub { synchronized (mUpdatableFontDirLock) { mUpdatableFontDir = createUpdatableFontDir(); if (mUpdatableFontDir == null) { - // If fs-verity is not supported, load preinstalled system font map and use it for - // all apps. - Typeface.loadPreinstalledSystemFontMap(); + if (Flags.useOptimizedBoottimeFontLoading()) { + // If fs-verity is not supported, load preinstalled system font map and use it + // for all apps. + Typeface.loadPreinstalledSystemFontMap(); + } setSerializedFontMap(serializeSystemServerFontMap()); return; } diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 429db5eeed49..c28a68b70d1c 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -4951,6 +4951,11 @@ public class HdmiControlService extends SystemService { AudioDeviceAttributes attributes = new AudioDeviceAttributes( AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_HDMI_EARC, "", "", new ArrayList<AudioProfile>(), audioDescriptors); + // Set SAM to ON whenever CEC is disabled. Failure to do so may result in the absence + // of sound when CEC is disabled and eARC is enabled due to SAM being in the off state. + if (!isCecControlEnabled()) { + setSystemAudioActivated(true); + } getAudioManager().setWiredDeviceConnectionState(attributes, enabled ? 1 : 0); } diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java index bad6bf0f0141..8580b9664075 100644 --- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java +++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java @@ -1246,11 +1246,17 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener { isFirstConfiguration); for (int i = 0; i < imeInfoList.size(); i++) { KeyboardLayoutInfo layoutInfo = layoutInfoList.get(i); - boolean noLayoutFound = layoutInfo == null || layoutInfo.mDescriptor == null; - configurationEventBuilder.addLayoutSelection(imeInfoList.get(i).mImeSubtype, - noLayoutFound ? null : getKeyboardLayout(layoutInfo.mDescriptor), - noLayoutFound ? LAYOUT_SELECTION_CRITERIA_DEFAULT - : layoutInfo.mSelectionCriteria); + String layoutName = null; + int layoutSelectionCriteria = LAYOUT_SELECTION_CRITERIA_DEFAULT; + if (layoutInfo != null && layoutInfo.mDescriptor != null) { + layoutSelectionCriteria = layoutInfo.mSelectionCriteria; + KeyboardLayoutDescriptor d = KeyboardLayoutDescriptor.parse(layoutInfo.mDescriptor); + if (d != null) { + layoutName = d.keyboardLayoutName; + } + } + configurationEventBuilder.addLayoutSelection(imeInfoList.get(i).mImeSubtype, layoutName, + layoutSelectionCriteria); } KeyboardMetricsCollector.logKeyboardConfiguredAtom(configurationEventBuilder.build()); } diff --git a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java index 08e597701ea2..2dd2a16bed64 100644 --- a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java +++ b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java @@ -491,7 +491,7 @@ public final class KeyboardMetricsCollector { private final InputDevice mInputDevice; private boolean mIsFirstConfiguration; private final List<InputMethodSubtype> mImeSubtypeList = new ArrayList<>(); - private final List<KeyboardLayout> mSelectedLayoutList = new ArrayList<>(); + private final List<String> mSelectedLayoutList = new ArrayList<>(); private final List<Integer> mLayoutSelectionCriteriaList = new ArrayList<>(); public Builder(@NonNull InputDevice inputDevice) { @@ -511,7 +511,7 @@ public final class KeyboardMetricsCollector { * Adds keyboard layout configuration info for a particular IME subtype language */ public Builder addLayoutSelection(@NonNull InputMethodSubtype imeSubtype, - @Nullable KeyboardLayout selectedLayout, + @Nullable String selectedLayout, @LayoutSelectionCriteria int layoutSelectionCriteria) { Objects.requireNonNull(imeSubtype, "IME subtype provided should not be null"); if (!isValidSelectionCriteria(layoutSelectionCriteria)) { @@ -533,7 +533,6 @@ public final class KeyboardMetricsCollector { } List<LayoutConfiguration> configurationList = new ArrayList<>(); for (int i = 0; i < size; i++) { - KeyboardLayout selectedLayout = mSelectedLayoutList.get(i); @LayoutSelectionCriteria int layoutSelectionCriteria = mLayoutSelectionCriteriaList.get(i); InputMethodSubtype imeSubtype = mImeSubtypeList.get(i); @@ -552,9 +551,9 @@ public final class KeyboardMetricsCollector { imeSubtype.getPhysicalKeyboardHintLayoutType()); // Sanitize null values - String keyboardLayoutName = - selectedLayout == null ? DEFAULT_LAYOUT_NAME - : selectedLayout.getLabel(); + String keyboardLayoutName = mSelectedLayoutList.get(i) == null + ? DEFAULT_LAYOUT_NAME + : mSelectedLayoutList.get(i); configurationList.add( new LayoutConfiguration(keyboardLayoutType, keyboardLanguageTag, diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java index d4578dc1f74a..4851a81d3b69 100644 --- a/services/core/java/com/android/server/locales/LocaleManagerService.java +++ b/services/core/java/com/android/server/locales/LocaleManagerService.java @@ -486,9 +486,12 @@ public class LocaleManagerService extends SystemService { Settings.Secure.DEFAULT_INPUT_METHOD, userId); if (!TextUtils.isEmpty(currentInputMethod)) { - String inputMethodPkgName = ComponentName - .unflattenFromString(currentInputMethod) - .getPackageName(); + ComponentName componentName = ComponentName.unflattenFromString(currentInputMethod); + if (componentName == null) { + Slog.d(TAG, "inValid input method"); + return false; + } + String inputMethodPkgName = componentName.getPackageName(); int inputMethodUid = getPackageUid(inputMethodPkgName, userId); return inputMethodUid >= 0 && UserHandle.isSameApp(Binder.getCallingUid(), inputMethodUid); diff --git a/services/core/java/com/android/server/media/AudioAttributesUtils.java b/services/core/java/com/android/server/media/AudioAttributesUtils.java index b9c9bae8e62b..5d5d59bcb6ef 100644 --- a/services/core/java/com/android/server/media/AudioAttributesUtils.java +++ b/services/core/java/com/android/server/media/AudioAttributesUtils.java @@ -48,6 +48,8 @@ import android.media.MediaRoute2Info; case AudioDeviceInfo.TYPE_DOCK_ANALOG: return MediaRoute2Info.TYPE_DOCK; case AudioDeviceInfo.TYPE_HDMI: + case AudioDeviceInfo.TYPE_HDMI_ARC: + case AudioDeviceInfo.TYPE_HDMI_EARC: return MediaRoute2Info.TYPE_HDMI; case AudioDeviceInfo.TYPE_USB_DEVICE: return MediaRoute2Info.TYPE_USB_DEVICE; @@ -81,6 +83,8 @@ import android.media.MediaRoute2Info; case AudioDeviceInfo.TYPE_DOCK: case AudioDeviceInfo.TYPE_DOCK_ANALOG: case AudioDeviceInfo.TYPE_HDMI: + case AudioDeviceInfo.TYPE_HDMI_ARC: + case AudioDeviceInfo.TYPE_HDMI_EARC: case AudioDeviceInfo.TYPE_USB_DEVICE: return true; default: 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/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/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java index 75a0cf521a1d..6b7db2d8d071 100644 --- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java +++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java @@ -38,6 +38,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; +import android.content.pm.UserInfo; import android.content.res.Resources; import android.database.ContentObserver; import android.media.AudioAttributes; @@ -48,6 +49,7 @@ import android.os.Binder; import android.os.RemoteException; import android.os.SystemProperties; import android.os.UserHandle; +import android.os.UserManager; import android.os.VibrationEffect; import android.provider.Settings; import android.telephony.PhoneStateListener; @@ -61,18 +63,21 @@ import android.view.accessibility.AccessibilityManager; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags; +import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.server.EventLogTags; import com.android.server.lights.LightsManager; import com.android.server.lights.LogicalLight; +import com.android.server.notification.Flags; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; @@ -87,15 +92,24 @@ public final class NotificationAttentionHelper { static final boolean DEBUG_INTERRUPTIVENESS = SystemProperties.getBoolean( "debug.notification.interruptiveness", false); + private static final float DEFAULT_VOLUME = 1.0f; + // TODO (b/291899544): remove for release + private static final String POLITE_STRATEGY1 = "rule1"; + private static final String POLITE_STRATEGY2 = "rule2"; + private static final int DEFAULT_NOTIFICATION_COOLDOWN_ENABLED = 1; + private static final int DEFAULT_NOTIFICATION_COOLDOWN_ENABLED_FOR_WORK = 0; + private static final int DEFAULT_NOTIFICATION_COOLDOWN_ALL = 1; + private static final int DEFAULT_NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED = 0; + private final Context mContext; private final PackageManager mPackageManager; private final TelephonyManager mTelephonyManager; + private final UserManager mUm; private final NotificationManagerPrivate mNMP; private final SystemUiSystemPropertiesFlags.FlagResolver mFlagResolver; private AccessibilityManager mAccessibilityManager; private KeyguardManager mKeyguardManager; private AudioManager mAudioManager; - private final LightsManager mLightsManager; private final NotificationUsageStats mUsageStats; private final ZenModeHelper mZenModeHelper; @@ -126,17 +140,26 @@ public final class NotificationAttentionHelper { private final float mInCallNotificationVolume; private Binder mCallNotificationToken = null; + // Settings flags + private boolean mNotificationCooldownEnabled; + private boolean mNotificationCooldownForWorkEnabled; + private boolean mNotificationCooldownApplyToAll; + private boolean mNotificationCooldownVibrateUnlocked; + + private boolean mEnablePoliteNotificationsFeature; + private final PolitenessStrategy mStrategy; + private int mCurrentWorkProfileId = UserHandle.USER_NULL; public NotificationAttentionHelper(Context context, LightsManager lightsManager, AccessibilityManager accessibilityManager, PackageManager packageManager, - NotificationUsageStats usageStats, + UserManager userManager, NotificationUsageStats usageStats, NotificationManagerPrivate notificationManagerPrivate, ZenModeHelper zenModeHelper, SystemUiSystemPropertiesFlags.FlagResolver flagResolver) { mContext = context; mPackageManager = packageManager; mTelephonyManager = context.getSystemService(TelephonyManager.class); mAccessibilityManager = accessibilityManager; - mLightsManager = lightsManager; + mUm = userManager; mNMP = notificationManagerPrivate; mUsageStats = usageStats; mZenModeHelper = zenModeHelper; @@ -144,8 +167,8 @@ public final class NotificationAttentionHelper { mVibratorHelper = new VibratorHelper(context); - mNotificationLight = mLightsManager.getLight(LightsManager.LIGHT_ID_NOTIFICATIONS); - mAttentionLight = mLightsManager.getLight(LightsManager.LIGHT_ID_ATTENTION); + mNotificationLight = lightsManager.getLight(LightsManager.LIGHT_ID_NOTIFICATIONS); + mAttentionLight = lightsManager.getLight(LightsManager.LIGHT_ID_ATTENTION); Resources resources = context.getResources(); mUseAttentionLight = resources.getBoolean(R.bool.config_useAttentionLight); @@ -169,7 +192,39 @@ public final class NotificationAttentionHelper { .build(); mInCallNotificationVolume = resources.getFloat(R.dimen.config_inCallNotificationVolume); + mEnablePoliteNotificationsFeature = Flags.politeNotifications(); + + if (mEnablePoliteNotificationsFeature) { + mStrategy = getPolitenessStrategy(); + } else { + mStrategy = null; + } + mSettingsObserver = new SettingsObserver(); + loadUserSettings(); + } + + private PolitenessStrategy getPolitenessStrategy() { + final String politenessStrategy = mFlagResolver.getStringValue( + NotificationFlags.NOTIF_COOLDOWN_RULE); + + if (POLITE_STRATEGY2.equals(politenessStrategy)) { + return new Strategy2(mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1), + mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T2), + mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1), + mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2)); + } else { + if (!POLITE_STRATEGY1.equals(politenessStrategy)) { + Log.w(TAG, "Invalid cooldown strategy: " + politenessStrategy + ". Defaulting to " + + POLITE_STRATEGY1); + } + + return new Strategy1(mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1), + mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T2), + mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1), + mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2), + mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET)); + } } public void onSystemReady() { @@ -202,11 +257,59 @@ public final class NotificationAttentionHelper { filter.addAction(Intent.ACTION_SCREEN_OFF); filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED); filter.addAction(Intent.ACTION_USER_PRESENT); + filter.addAction(Intent.ACTION_USER_ADDED); + filter.addAction(Intent.ACTION_USER_REMOVED); + filter.addAction(Intent.ACTION_USER_SWITCHED); + filter.addAction(Intent.ACTION_USER_UNLOCKED); mContext.registerReceiverAsUser(mIntentReceiver, UserHandle.ALL, filter, null, null); mContext.getContentResolver().registerContentObserver( SettingsObserver.NOTIFICATION_LIGHT_PULSE_URI, false, mSettingsObserver, UserHandle.USER_ALL); + if (mEnablePoliteNotificationsFeature) { + mContext.getContentResolver().registerContentObserver( + SettingsObserver.NOTIFICATION_COOLDOWN_ENABLED_URI, false, mSettingsObserver, + UserHandle.USER_ALL); + mContext.getContentResolver().registerContentObserver( + SettingsObserver.NOTIFICATION_COOLDOWN_ALL_URI, false, mSettingsObserver, + UserHandle.USER_ALL); + mContext.getContentResolver().registerContentObserver( + SettingsObserver.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED_URI, false, + mSettingsObserver, UserHandle.USER_ALL); + } + } + + private void loadUserSettings() { + if (mEnablePoliteNotificationsFeature) { + try { + mCurrentWorkProfileId = getManagedProfileId(ActivityManager.getCurrentUser()); + + mNotificationCooldownEnabled = + Settings.System.getIntForUser(mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_ENABLED, + DEFAULT_NOTIFICATION_COOLDOWN_ENABLED, UserHandle.USER_CURRENT) != 0; + if (mCurrentWorkProfileId != UserHandle.USER_NULL) { + mNotificationCooldownForWorkEnabled = Settings.System.getIntForUser( + mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_ENABLED, + DEFAULT_NOTIFICATION_COOLDOWN_ENABLED_FOR_WORK, mCurrentWorkProfileId) + != 0; + } else { + mNotificationCooldownForWorkEnabled = false; + } + mNotificationCooldownApplyToAll = Settings.System.getIntForUser( + mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_ALL, DEFAULT_NOTIFICATION_COOLDOWN_ALL, + UserHandle.USER_CURRENT) != 0; + mNotificationCooldownVibrateUnlocked = Settings.System.getIntForUser( + mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED, + DEFAULT_NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED, + UserHandle.USER_CURRENT) != 0; + } catch (Exception e) { + Log.e(TAG, "Failed to read Settings: " + e); + } + } } @VisibleForTesting @@ -229,6 +332,10 @@ public final class NotificationAttentionHelper { Log.d(TAG, "buzzBeepBlinkLocked " + record); } + if (isPoliteNotificationFeatureEnabled(record)) { + mStrategy.onNotificationPosted(record); + } + // Should this notification make noise, vibe, or use the LED? final boolean aboveThreshold = mIsAutomotive @@ -269,6 +376,9 @@ public final class NotificationAttentionHelper { vibration = mVibratorHelper.createFallbackVibration(insistent); } hasValidVibrate = vibration != null; + // Vibration-only if unlocked and Settings flag set + boolean vibrateOnly = + hasValidVibrate && mNotificationCooldownVibrateUnlocked && mUserPresent; boolean hasAudibleAlert = hasValidSound || hasValidVibrate; if (hasAudibleAlert && !shouldMuteNotificationLocked(record, signals)) { if (!sentAccessibilityEvent) { @@ -277,7 +387,7 @@ public final class NotificationAttentionHelper { } if (DEBUG) Slog.v(TAG, "Interrupting!"); boolean isInsistentUpdate = isInsistentUpdate(record); - if (hasValidSound) { + if (hasValidSound && !vibrateOnly) { if (isInsistentUpdate) { // don't reset insistent sound, it's jarring beep = true; @@ -301,7 +411,7 @@ public final class NotificationAttentionHelper { if (isInsistentUpdate) { buzz = true; } else { - buzz = playVibration(record, vibration, hasValidSound); + buzz = playVibration(record, vibration, hasValidSound && !vibrateOnly); if (buzz) { mVibrateNotificationKey = key; } @@ -341,9 +451,7 @@ public final class NotificationAttentionHelper { } else if (wasShowLights) { updateLightsLocked(); } - final int buzzBeepBlink = - (buzz ? 1 : 0) | (beep ? 2 : 0) | (blink ? 4 : 0); - if (buzzBeepBlink > 0) { + if (buzz || beep || blink) { // Ignore summary updates because we don't display most of the information. if (record.getSbn().isGroup() && record.getSbn().getNotification().isGroupSummary()) { if (DEBUG_INTERRUPTIVENESS) { @@ -362,15 +470,43 @@ public final class NotificationAttentionHelper { + record.getKey() + " is interruptive: alerted"); } } + } + final int buzzBeepBlinkLoggingCode = + (buzz ? 1 : 0) | (beep ? 2 : 0) | (blink ? 4 : 0) | getPoliteBit(record); + if (buzzBeepBlinkLoggingCode > 0) { MetricsLogger.action(record.getLogMaker() .setCategory(MetricsEvent.NOTIFICATION_ALERT) .setType(MetricsEvent.TYPE_OPEN) - .setSubtype(buzzBeepBlink)); - EventLogTags.writeNotificationAlert(key, buzz ? 1 : 0, beep ? 1 : 0, blink ? 1 : 0); + .setSubtype(buzzBeepBlinkLoggingCode)); + EventLogTags.writeNotificationAlert(key, buzz ? 1 : 0, beep ? 1 : 0, blink ? 1 : 0, + getPolitenessState(record)); } record.setAudiblyAlerted(buzz || beep); + if (mEnablePoliteNotificationsFeature) { + // Update last alert time + if (buzz || beep) { + record.getChannel().setLastNotificationUpdateTimeMs(System.currentTimeMillis()); + } + } + return buzzBeepBlinkLoggingCode; + } - return buzzBeepBlink; + private int getPoliteBit(final NotificationRecord record) { + switch (getPolitenessState(record)) { + case PolitenessStrategy.POLITE_STATE_POLITE: + return MetricsProto.MetricsEvent.ALERT_POLITE; + case PolitenessStrategy.POLITE_STATE_MUTED: + return MetricsProto.MetricsEvent.ALERT_MUTED; + default: + return 0; + } + } + + private int getPolitenessState(final NotificationRecord record) { + if (!isPoliteNotificationFeatureEnabled(record)) { + return PolitenessStrategy.POLITE_STATE_DEFAULT; + } + return mStrategy.getPolitenessState(record); } boolean isInsistentUpdate(final NotificationRecord record) { @@ -468,7 +604,7 @@ public final class NotificationAttentionHelper { + record.getAudioAttributes()); } player.playAsync(soundUri, record.getSbn().getUser(), looping, - record.getAudioAttributes()); + record.getAudioAttributes(), getSoundVolume(record)); return true; } } catch (RemoteException e) { @@ -480,12 +616,56 @@ public final class NotificationAttentionHelper { return false; } + private boolean isPoliteNotificationFeatureEnabled(final NotificationRecord record) { + // Check feature flag + if (!mEnablePoliteNotificationsFeature) { + return false; + } + + // The user can enable/disable notifications cooldown from the Settings app + if (!mNotificationCooldownEnabled) { + return false; + } + + // The user can enable/disable notifications cooldown for work profile from the Settings app + if (isNotificationForWorkProfile(record) && !mNotificationCooldownForWorkEnabled) { + return false; + } + + // The user can choose to apply cooldown for all apps/conversations only from the + // Settings app + if (!mNotificationCooldownApplyToAll && record.getChannel().getConversationId() == null) { + return false; + } + + return true; + } + + private float getSoundVolume(final NotificationRecord record) { + if (!isPoliteNotificationFeatureEnabled(record)) { + return DEFAULT_VOLUME; + } + + return mStrategy.getSoundVolume(record); + } + + private float getVibrationIntensity(final NotificationRecord record) { + if (!isPoliteNotificationFeatureEnabled(record)) { + return DEFAULT_VOLUME; + } + + return mStrategy.getVibrationIntensity(record); + } + private boolean playVibration(final NotificationRecord record, final VibrationEffect effect, boolean delayVibForSound) { // Escalate privileges so we can use the vibrator even if the // notifying app does not have the VIBRATE permission. final long identity = Binder.clearCallingIdentity(); try { + final float scale = getVibrationIntensity(record); + final VibrationEffect scaledEffect = Float.compare(scale, DEFAULT_VOLUME) != 0 + ? mVibratorHelper.scale(effect, scale) : effect; if (delayVibForSound) { new Thread(() -> { // delay the vibration by the same amount as the notification sound @@ -503,7 +683,7 @@ public final class NotificationAttentionHelper { // so need to check that the notification is still valid for vibrate. if (mNMP.getNotificationByKey(record.getKey()) != null) { if (record.getKey().equals(mVibrateNotificationKey)) { - vibrate(record, effect, true); + vibrate(record, scaledEffect, true); } else { if (DEBUG) { Slog.v(TAG, "No vibration for notification " @@ -517,7 +697,7 @@ public final class NotificationAttentionHelper { } }).start(); } else { - vibrate(record, effect, false); + vibrate(record, scaledEffect, false); } return true; } finally { @@ -535,7 +715,7 @@ public final class NotificationAttentionHelper { } void playInCallNotification() { - // TODO: Should we apply politeness to mInCallNotificationVolume ? + // TODO b/270456865: Should we apply politeness to mInCallNotificationVolume ? final ContentResolver cr = mContext.getContentResolver(); if (mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_NORMAL && Settings.Secure.getIntForUser(cr, @@ -760,6 +940,22 @@ public final class NotificationAttentionHelper { || signals.isCurrentProfile); } + private boolean isNotificationForWorkProfile(final NotificationRecord record) { + return (record.getUser().getIdentifier() == mCurrentWorkProfileId + && mCurrentWorkProfileId != UserHandle.USER_NULL); + } + + private int getManagedProfileId(int parentUserId) { + final List<UserInfo> profiles = mUm.getProfiles(parentUserId); + for (UserInfo profile : profiles) { + if (profile.isManagedProfile() + && profile.getUserHandle().getIdentifier() != parentUserId) { + return profile.getUserHandle().getIdentifier(); + } + } + return UserHandle.USER_NULL; + } + void sendAccessibilityEvent(NotificationRecord record) { if (!mAccessibilityManager.isEnabled()) { return; @@ -791,6 +987,16 @@ public final class NotificationAttentionHelper { mAccessibilityManager.sendAccessibilityEvent(event); } + /** + * Notify the attention helper of a user interaction with a notification + * @param record that was interacted with + */ + public void onUserInteraction(final NotificationRecord record) { + if (isPoliteNotificationFeatureEnabled(record)) { + mStrategy.onUserInteraction(record); + } + } + public void dump(PrintWriter pw, String prefix, NotificationManagerService.DumpFilter filter) { pw.println("\n Notification attention state:"); pw.print(prefix); @@ -834,6 +1040,243 @@ public final class NotificationAttentionHelper { } } + abstract private static class PolitenessStrategy { + static final int POLITE_STATE_DEFAULT = 0; + static final int POLITE_STATE_POLITE = 1; + static final int POLITE_STATE_MUTED = 2; + + @IntDef(prefix = { "POLITE_STATE_" }, value = { + POLITE_STATE_DEFAULT, + POLITE_STATE_POLITE, + POLITE_STATE_MUTED, + }) + @Retention(RetentionPolicy.SOURCE) + @interface PolitenessState {} + + protected final Map<String, Integer> mVolumeStates; + + // Cooldown timer for transitioning into polite state + protected final int mTimeoutPolite; + // Cooldown timer for transitioning into muted state + protected final int mTimeoutMuted; + // Volume for polite state + protected final float mVolumePolite; + // Volume for muted state + protected final float mVolumeMuted; + + public PolitenessStrategy(int timeoutPolite, int timeoutMuted, int volumePolite, + int volumeMuted) { + mVolumeStates = new HashMap<>(); + + this.mTimeoutPolite = timeoutPolite; + this.mTimeoutMuted = timeoutMuted; + this.mVolumePolite = volumePolite / 100.0f; + this.mVolumeMuted = volumeMuted / 100.0f; + } + + abstract void onNotificationPosted(NotificationRecord record); + + String getChannelKey(final NotificationRecord record) { + // use conversationId if it's a conversation + String channelId = record.getChannel().getConversationId() != null + ? record.getChannel().getConversationId() : record.getChannel().getId(); + return record.getSbn().getNormalizedUserId() + ":" + record.getSbn().getPackageName() + + ":" + channelId; + } + + public float getSoundVolume(final NotificationRecord record) { + float volume = DEFAULT_VOLUME; + final String key = getChannelKey(record); + final @PolitenessState int volState = getPolitenessState(record); + + switch (volState) { + case POLITE_STATE_DEFAULT: + volume = DEFAULT_VOLUME; + break; + case POLITE_STATE_POLITE: + volume = mVolumePolite; + break; + case POLITE_STATE_MUTED: + volume = mVolumeMuted; + break; + default: + Log.w(TAG, "getSoundVolume unexpected volume state: " + volState); + break; + } + + if (DEBUG) { + Log.i(TAG, + "getSoundVolume state: " + volState + " vol: " + volume + " key: " + key); + } + + return volume; + } + + private float getVibrationIntensity(final NotificationRecord record) { + // TODO b/270456865: maybe use different scaling for vibration/sound ? + return getSoundVolume(record); + } + + public void onUserInteraction(final NotificationRecord record) { + final String key = getChannelKey(record); + // reset to default state after user interaction + mVolumeStates.put(key, POLITE_STATE_DEFAULT); + record.getChannel().setLastNotificationUpdateTimeMs(0); + } + + public final @PolitenessState int getPolitenessState(final NotificationRecord record) { + return mVolumeStates.getOrDefault(getChannelKey(record), POLITE_STATE_DEFAULT); + } + } + + // TODO b/270456865: Only one of the two strategies will be released. + // The other one need to be removed + /** + * Polite notification strategy 1: + * - Transitions from default (loud) => polite (lower volume) state if a notification + * alerts the same channel before timeoutPolite. + * - Transitions from polite => muted state if a notification alerts the same channel + * before timeoutMuted OR transitions back to the default state if a notification alerts + * after timeoutPolite. + * - Transitions from muted => default state if the muted channel received more than maxPosted + * notifications OR transitions back to the polite state if a notification alerts + * after timeoutMuted. + * - Transitions back to the default state after a user interaction with a notification. + */ + public static class Strategy1 extends PolitenessStrategy { + // Keep track of the number of notifications posted per channel + private final Map<String, Integer> mNumPosted; + // Reset to default state if number of posted notifications exceed this value when muted + private final int mMaxPostedForReset; + + public Strategy1(int timeoutPolite, int timeoutMuted, int volumePolite, int volumeMuted, + int maxPosted) { + super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted); + + mNumPosted = new HashMap<>(); + mMaxPostedForReset = maxPosted; + + if (DEBUG) { + Log.i(TAG, "Strategy1: " + timeoutPolite + " " + timeoutMuted); + } + } + + @Override + public void onNotificationPosted(final NotificationRecord record) { + long timeSinceLastNotif = System.currentTimeMillis() + - record.getChannel().getLastNotificationUpdateTimeMs(); + + final String key = getChannelKey(record); + @PolitenessState int volState = getPolitenessState(record); + + int numPosted = mNumPosted.getOrDefault(key, 0) + 1; + mNumPosted.put(key, numPosted); + + switch (volState) { + case POLITE_STATE_DEFAULT: + if (timeSinceLastNotif < mTimeoutPolite) { + volState = POLITE_STATE_POLITE; + } + break; + case POLITE_STATE_POLITE: + if (timeSinceLastNotif < mTimeoutMuted) { + volState = POLITE_STATE_MUTED; + } else if (timeSinceLastNotif > mTimeoutPolite) { + volState = POLITE_STATE_DEFAULT; + } else { + volState = POLITE_STATE_POLITE; + } + break; + case POLITE_STATE_MUTED: + if (timeSinceLastNotif > mTimeoutMuted) { + volState = POLITE_STATE_POLITE; + } else { + volState = POLITE_STATE_MUTED; + } + if (numPosted >= mMaxPostedForReset) { + volState = POLITE_STATE_DEFAULT; + mNumPosted.put(key, 0); + } + break; + default: + Log.w(TAG, "onNotificationPosted unexpected volume state: " + volState); + break; + } + + if (DEBUG) { + Log.i(TAG, "onNotificationPosted time delta: " + timeSinceLastNotif + " vol state: " + + volState + " key: " + key + " numposted " + numPosted); + } + + mVolumeStates.put(key, volState); + } + + @Override + public void onUserInteraction(final NotificationRecord record) { + super.onUserInteraction(record); + mNumPosted.put(getChannelKey(record), 0); + } + } + + /** + * Polite notification strategy 2: + * - Transitions from default (loud) => muted state if a notification + * alerts the same channel before timeoutPolite. + * - Transitions from polite => default state if a notification + * alerts the same channel before timeoutMuted. + * - Transitions from muted => default state if a notification alerts after timeoutMuted, + * otherwise transitions to the polite state. + * - Transitions back to the default state after a user interaction with a notification. + */ + public static class Strategy2 extends PolitenessStrategy { + public Strategy2(int timeoutPolite, int timeoutMuted, int volumePolite, int volumeMuted) { + super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted); + + if (DEBUG) { + Log.i(TAG, "Strategy2: " + timeoutPolite + " " + timeoutMuted); + } + } + + @Override + public void onNotificationPosted(final NotificationRecord record) { + long timeSinceLastNotif = System.currentTimeMillis() + - record.getChannel().getLastNotificationUpdateTimeMs(); + + final String key = getChannelKey(record); + @PolitenessState int volState = getPolitenessState(record); + + switch (volState) { + case POLITE_STATE_DEFAULT: + if (timeSinceLastNotif < mTimeoutPolite) { + volState = POLITE_STATE_MUTED; + } + break; + case POLITE_STATE_POLITE: + if (timeSinceLastNotif > mTimeoutMuted) { + volState = POLITE_STATE_DEFAULT; + } + break; + case POLITE_STATE_MUTED: + if (timeSinceLastNotif > mTimeoutMuted) { + volState = POLITE_STATE_DEFAULT; + } else { + volState = POLITE_STATE_POLITE; + } + break; + default: + Log.w(TAG, "onNotificationPosted unexpected volume state: " + volState); + break; + } + + if (DEBUG) { + Log.i(TAG, "onNotificationPosted time delta: " + timeSinceLastNotif + " vol state: " + + volState + " key: " + key); + } + + mVolumeStates.put(key, volState); + } + } + //====================== Observers ============================= private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @Override @@ -859,6 +1302,11 @@ public final class NotificationAttentionHelper { if (mNotificationLight != null) { mNotificationLight.turnOff(); } + } else if (action.equals(Intent.ACTION_USER_ADDED) + || action.equals(Intent.ACTION_USER_REMOVED) + || action.equals(Intent.ACTION_USER_SWITCHED) + || action.equals(Intent.ACTION_USER_UNLOCKED)) { + loadUserSettings(); } } }; @@ -867,6 +1315,12 @@ public final class NotificationAttentionHelper { private static final Uri NOTIFICATION_LIGHT_PULSE_URI = Settings.System.getUriFor( Settings.System.NOTIFICATION_LIGHT_PULSE); + private static final Uri NOTIFICATION_COOLDOWN_ENABLED_URI = Settings.System.getUriFor( + Settings.System.NOTIFICATION_COOLDOWN_ENABLED); + private static final Uri NOTIFICATION_COOLDOWN_ALL_URI = Settings.System.getUriFor( + Settings.System.NOTIFICATION_COOLDOWN_ALL); + private static final Uri NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED_URI = + Settings.System.getUriFor(Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED); public SettingsObserver() { super(null); } @@ -884,11 +1338,45 @@ public final class NotificationAttentionHelper { updateLightsLocked(); } } + if (mEnablePoliteNotificationsFeature) { + if (NOTIFICATION_COOLDOWN_ENABLED_URI.equals(uri)) { + mNotificationCooldownEnabled = Settings.System.getIntForUser( + mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_ENABLED, + DEFAULT_NOTIFICATION_COOLDOWN_ENABLED, + UserHandle.USER_CURRENT) != 0; + + if (mCurrentWorkProfileId != UserHandle.USER_NULL) { + mNotificationCooldownForWorkEnabled = Settings.System.getIntForUser( + mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_ENABLED, + DEFAULT_NOTIFICATION_COOLDOWN_ENABLED_FOR_WORK, + mCurrentWorkProfileId) + != 0; + } else { + mNotificationCooldownForWorkEnabled = false; + } + } + if (NOTIFICATION_COOLDOWN_ALL_URI.equals(uri)) { + mNotificationCooldownApplyToAll = Settings.System.getIntForUser( + mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_ALL, + DEFAULT_NOTIFICATION_COOLDOWN_ALL, UserHandle.USER_CURRENT) + != 0; + } + if (NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED_URI.equals(uri)) { + mNotificationCooldownVibrateUnlocked = Settings.System.getIntForUser( + mContext.getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED, + DEFAULT_NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED, + UserHandle.USER_CURRENT) != 0; + } + } } } - //TODO: cleanup most (all?) of these + // TODO b/270456865: cleanup most (all?) of these //======================= FOR TESTS ===================== @VisibleForTesting void setIsAutomotive(boolean isAutomotive) { @@ -931,6 +1419,11 @@ public final class NotificationAttentionHelper { } @VisibleForTesting + void setUserPresent(boolean userPresent) { + mUserPresent = userPresent; + } + + @VisibleForTesting void setLights(LogicalLight light) { mNotificationLight = light; mAttentionLight = light; diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 87c30674bfb7..837b76154363 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -2532,7 +2532,7 @@ public class NotificationManagerService extends SystemService { if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) { mAttentionHelper = new NotificationAttentionHelper(getContext(), lightsManager, - mAccessibilityManager, mPackageManagerClient, usageStats, + mAccessibilityManager, mPackageManagerClient, userManager, usageStats, mNotificationManagerPrivate, mZenModeHelper, flagResolver); } @@ -3369,6 +3369,10 @@ public class NotificationManagerService extends SystemService { mAppUsageStats.reportEvent(r.getSbn().getPackageName(), getRealUserId(r.getSbn().getUserId()), UsageEvents.Event.USER_INTERACTION); + + if (Flags.politeNotifications()) { + mAttentionHelper.onUserInteraction(r); + } } private int getRealUserId(int userId) { @@ -5730,13 +5734,18 @@ public class NotificationManagerService extends SystemService { public void setNotificationListenerAccessGrantedForUser(ComponentName listener, int userId, boolean granted, boolean userSet) { Objects.requireNonNull(listener); + if (UserHandle.getCallingUserId() != userId) { + getContext().enforceCallingOrSelfPermission( + android.Manifest.permission.INTERACT_ACROSS_USERS, + "setNotificationListenerAccessGrantedForUser for user " + userId); + } checkNotificationListenerAccess(); if (granted && listener.flattenToString().length() > NotificationManager.MAX_SERVICE_COMPONENT_NAME_LENGTH) { throw new IllegalArgumentException( "Component name too long: " + listener.flattenToString()); } - if (!userSet && isNotificationListenerAccessUserSet(listener)) { + if (!userSet && isNotificationListenerAccessUserSet(listener, userId)) { // Don't override user's choice return; } @@ -5762,9 +5771,8 @@ public class NotificationManagerService extends SystemService { } } - private boolean isNotificationListenerAccessUserSet(ComponentName listener) { - return mListeners.isPackageOrComponentUserSet(listener.flattenToString(), - getCallingUserHandle().getIdentifier()); + private boolean isNotificationListenerAccessUserSet(ComponentName listener, int userId) { + return mListeners.isPackageOrComponentUserSet(listener.flattenToString(), userId); } @Override @@ -8612,7 +8620,7 @@ public class NotificationManagerService extends SystemService { .setCategory(MetricsEvent.NOTIFICATION_ALERT) .setType(MetricsEvent.TYPE_OPEN) .setSubtype(buzzBeepBlink)); - EventLogTags.writeNotificationAlert(key, buzz ? 1 : 0, beep ? 1 : 0, blink ? 1 : 0); + EventLogTags.writeNotificationAlert(key, buzz ? 1 : 0, beep ? 1 : 0, blink ? 1 : 0, 0); } record.setAudiblyAlerted(buzz || beep); return buzzBeepBlink; @@ -8757,7 +8765,7 @@ public class NotificationManagerService extends SystemService { if (DBG) Slog.v(TAG, "Playing sound " + soundUri + " with attributes " + record.getAudioAttributes()); player.playAsync(soundUri, record.getSbn().getUser(), looping, - record.getAudioAttributes()); + record.getAudioAttributes(), 1.0f); return true; } } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/notification/VibratorHelper.java b/services/core/java/com/android/server/notification/VibratorHelper.java index e5d07bc152c0..7204d05fdce7 100644 --- a/services/core/java/com/android/server/notification/VibratorHelper.java +++ b/services/core/java/com/android/server/notification/VibratorHelper.java @@ -50,6 +50,7 @@ public final class VibratorHelper { private final long[] mFallbackPattern; @Nullable private final float[] mDefaultPwlePattern; @Nullable private final float[] mFallbackPwlePattern; + private final int mDefaultVibrationAmplitude; public VibratorHelper(Context context) { mVibrator = context.getSystemService(Vibrator.class); @@ -65,6 +66,8 @@ public final class VibratorHelper { com.android.internal.R.array.config_defaultNotificationVibeWaveform); mFallbackPwlePattern = getFloatArray(context.getResources(), com.android.internal.R.array.config_notificationFallbackVibeWaveform); + mDefaultVibrationAmplitude = context.getResources().getInteger( + com.android.internal.R.integer.config_defaultVibrationAmplitude); } /** @@ -136,6 +139,14 @@ public final class VibratorHelper { } /** + * Scale vibration effect, valid range is [0.0f, 1.0f] + * Resolves default amplitude value if not already set. + */ + public VibrationEffect scale(VibrationEffect effect, float scale) { + return effect.resolve(mDefaultVibrationAmplitude).scale(scale); + } + + /** * Vibrate the device with given {@code effect}. * * <p>We need to vibrate as "android" so we can breakthrough DND. 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/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index c6388e7bede7..edb45aa0ffb4 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -16,6 +16,7 @@ package com.android.server.pm; +import static android.content.pm.Flags.preventSdkLibApp; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED; import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS; @@ -994,10 +995,11 @@ final class InstallPackageHelper { return; } final boolean isApex = (request.getScanFlags() & SCAN_AS_APEX) != 0; - if (!isApex) { - createdAppId.put(packageName, optimisticallyRegisterAppId(request)); - } else { + final boolean isSdkLibrary = packageToScan.isSdkLibrary(); + if (isApex || (isSdkLibrary && preventSdkLibApp())) { request.getScannedPackageSetting().setAppId(Process.INVALID_UID); + } else { + createdAppId.put(packageName, optimisticallyRegisterAppId(request)); } versionInfos.put(packageName, mPm.getSettingsVersionForPackage(packageToScan)); diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java index 2d192826ba9a..7d822b50a293 100644 --- a/services/core/java/com/android/server/pm/InstallRequest.java +++ b/services/core/java/com/android/server/pm/InstallRequest.java @@ -22,6 +22,8 @@ import static android.content.pm.PackageManager.INSTALL_SCENARIO_DEFAULT; import static android.content.pm.PackageManager.INSTALL_SUCCEEDED; import static android.os.Process.INVALID_UID; +import static com.android.server.art.model.DexoptResult.DexContainerFileDexoptResult; +import static com.android.server.art.model.DexoptResult.PackageDexoptResult; import static com.android.server.pm.PackageManagerService.EMPTY_INT_ARRAY; import static com.android.server.pm.PackageManagerService.SCAN_AS_INSTANT_APP; import static com.android.server.pm.PackageManagerService.TAG; @@ -32,6 +34,7 @@ import android.apex.ApexInfo; import android.app.AppOpsManager; import android.content.pm.ArchivedPackageParcel; import android.content.pm.DataLoaderType; +import android.content.pm.Flags; import android.content.pm.IPackageInstallObserver2; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; @@ -56,6 +59,7 @@ import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import java.io.File; import java.util.ArrayList; +import java.util.LinkedHashSet; import java.util.List; final class InstallRequest { @@ -147,6 +151,9 @@ final class InstallRequest { @NonNull private int[] mUpdateBroadcastInstantUserIds = EMPTY_INT_ARRAY; + @NonNull + private ArrayList<String> mWarnings = new ArrayList<>(); + // New install InstallRequest(InstallingSession params) { mUserId = params.getUser().getIdentifier(); @@ -658,6 +665,11 @@ final class InstallRequest { return mUpdateBroadcastInstantUserIds; } + @NonNull + public ArrayList<String> getWarnings() { + return mWarnings; + } + public void setScanFlags(int scanFlags) { mScanFlags = scanFlags; } @@ -855,6 +867,10 @@ final class InstallRequest { } } + public void addWarning(@NonNull String warning) { + mWarnings.add(warning); + } + public void onPrepareStarted() { if (mPackageMetrics != null) { mPackageMetrics.onStepStarted(PackageMetrics.STEP_PREPARE); @@ -904,22 +920,37 @@ final class InstallRequest { } public void onDexoptFinished(DexoptResult dexoptResult) { - if (mPackageMetrics == null) { - return; - } - mDexoptStatus = dexoptResult.getFinalStatus(); - if (mDexoptStatus != DexoptResult.DEXOPT_PERFORMED) { - return; + // Only report external profile warnings when installing from adb. The goal is to warn app + // developers if they have provided bad external profiles, so it's not beneficial to report + // those warnings in the normal app install workflow. + if (isInstallFromAdb() && Flags.useArtServiceV2()) { + var externalProfileErrors = new LinkedHashSet<String>(); + for (PackageDexoptResult packageResult : dexoptResult.getPackageDexoptResults()) { + for (DexContainerFileDexoptResult fileResult : + packageResult.getDexContainerFileDexoptResults()) { + externalProfileErrors.addAll(fileResult.getExternalProfileErrors()); + } + } + if (!externalProfileErrors.isEmpty()) { + addWarning("Error occurred during dexopt when processing external profiles:\n " + + String.join("\n ", externalProfileErrors)); + } } - long durationMillis = 0; - for (DexoptResult.PackageDexoptResult packageResult : - dexoptResult.getPackageDexoptResults()) { - for (DexoptResult.DexContainerFileDexoptResult fileResult : - packageResult.getDexContainerFileDexoptResults()) { - durationMillis += fileResult.getDex2oatWallTimeMillis(); + + // Report dexopt metrics. + if (mPackageMetrics != null) { + mDexoptStatus = dexoptResult.getFinalStatus(); + if (mDexoptStatus == DexoptResult.DEXOPT_PERFORMED) { + long durationMillis = 0; + for (PackageDexoptResult packageResult : dexoptResult.getPackageDexoptResults()) { + for (DexContainerFileDexoptResult fileResult : + packageResult.getDexContainerFileDexoptResults()) { + durationMillis += fileResult.getDex2oatWallTimeMillis(); + } + } + mPackageMetrics.onStepFinished(PackageMetrics.STEP_DEXOPT, durationMillis); } } - mPackageMetrics.onStepFinished(PackageMetrics.STEP_DEXOPT, durationMillis); } public void onInstallCompleted() { diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index 11660a59afe6..a161e8c39ca2 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -107,19 +107,22 @@ import com.android.internal.os.BackgroundThread; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; import com.android.internal.util.Preconditions; +import com.android.internal.util.SizedInputStream; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.wm.ActivityTaskManagerInternal; +import java.io.DataInputStream; import java.io.FileDescriptor; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.io.PrintWriter; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; +import java.nio.file.StandardOpenOption; import java.nio.file.attribute.PosixFilePermission; import java.util.ArrayList; import java.util.Arrays; @@ -130,6 +133,8 @@ import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.function.BiConsumer; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @@ -216,6 +221,7 @@ public class LauncherAppsService extends SystemService { private final ShortcutChangeHandler mShortcutChangeHandler; private final Handler mCallbackHandler; + private final ExecutorService mOnDumpExecutor = Executors.newSingleThreadExecutor(); private PackageInstallerService mPackageInstallerService; @@ -1512,7 +1518,7 @@ public class LauncherAppsService extends SystemService { forEachViewCaptureWindow((fileName, is) -> { try { zipOs.putNextEntry(new ZipEntry("FS" + fileName)); - is.transferTo(zipOs); + transferViewCaptureData(is, zipOs); zipOs.closeEntry(); } catch (IOException e) { getErrPrintWriter().write("Failed to output " + fileName @@ -1553,8 +1559,9 @@ public class LauncherAppsService extends SystemService { private void dumpViewCaptureDataToWmTrace(@NonNull String fileName, @NonNull InputStream is) { Path outPath = Paths.get(fileName); - try { - Files.copy(is, outPath, StandardCopyOption.REPLACE_EXISTING); + try (OutputStream os = Files.newOutputStream(outPath, StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING)) { + transferViewCaptureData(is, os); Files.setPosixFilePermissions(outPath, WM_TRACE_FILE_PERMISSIONS); } catch (IOException e) { Log.d(TAG, "failed to write data to " + fileName + " in wmtrace dir", e); @@ -1562,6 +1569,15 @@ public class LauncherAppsService extends SystemService { } /** + * Raw input stream reads hang on the final read when transferring data in via the pipe. + * The fix used below is to count and read the exact amount of bytes being sent. + */ + private void transferViewCaptureData(InputStream is, OutputStream os) throws IOException { + DataInputStream dataInputStream = new DataInputStream(is); + new SizedInputStream(dataInputStream, dataInputStream.readInt()).transferTo(os); + } + + /** * IDumpCallback.onDump alerts the in-process ViewCapture instance to start sending data * to LauncherAppsService via the pipe's input provided. This data (as well as an output * file name) is provided to the consumer via an InputStream to output where it wants (for @@ -1569,24 +1585,37 @@ public class LauncherAppsService extends SystemService { */ private void forEachViewCaptureWindow( @NonNull BiConsumer<String, InputStream> outputtingConsumer) { - for (int i = mDumpCallbacks.beginBroadcast() - 1; i >= 0; i--) { - String packageName = (String) mDumpCallbacks.getBroadcastCookie(i); - String fileName = WM_TRACE_DIR + packageName + "_" + i + VC_FILE_SUFFIX; - - try { - // Order is important here. OnDump needs to be called before the BiConsumer - // accepts & starts blocking on reading the input stream. - ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); - mDumpCallbacks.getBroadcastItem(i).onDump(pipe[1]); - - InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(pipe[0]); - outputtingConsumer.accept(fileName, is); - is.close(); - } catch (Exception e) { - Log.d(TAG, "failed to pipe view capture data", e); - } + try { + // This multi-threading prevents ctrl-C command line command aborting from putting + // the mDumpCallbacks RemoteCallbackList in a bad Broadcast state. We need to wait + // for it to complete even though it is on a background thread. + mOnDumpExecutor.submit(() -> { + try { + for (int i = mDumpCallbacks.beginBroadcast() - 1; i >= 0; i--) { + String packageName = (String) mDumpCallbacks.getBroadcastCookie(i); + String fileName = WM_TRACE_DIR + packageName + "_" + i + VC_FILE_SUFFIX; + + try { + // Order is important here. OnDump needs to be called before the + // BiConsumer accepts & starts blocking on reading the input stream. + ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); + mDumpCallbacks.getBroadcastItem(i).onDump(pipe[1]); + + InputStream is = new ParcelFileDescriptor.AutoCloseInputStream( + pipe[0]); + outputtingConsumer.accept(fileName, is); + is.close(); + } catch (Exception e) { + Log.d(TAG, "failed to pipe view capture data", e); + } + } + } finally { + mDumpCallbacks.finishBroadcast(); + } + }).get(); + } catch (InterruptedException | ExecutionException e) { + Log.e(TAG, "background work was interrupted", e); } - mDumpCallbacks.finishBroadcast(); } @RequiresPermission(READ_FRAME_BUFFER) diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 662703992ad8..d0e5f96f8d0f 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -2930,15 +2930,40 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { * @return a future that will be completed when the whole process is completed. */ private CompletableFuture<Void> install() { + // `futures` either contains only one session (`this`) or contains one parent session + // (`this`) and n-1 child sessions. List<CompletableFuture<InstallResult>> futures = installNonStaged(); CompletableFuture<InstallResult>[] arr = new CompletableFuture[futures.size()]; return CompletableFuture.allOf(futures.toArray(arr)).whenComplete((r, t) -> { if (t == null) { setSessionApplied(); + var multiPackageWarnings = new ArrayList<String>(); + if (isMultiPackage()) { + // This is a parent session. Collect warnings from children. + for (CompletableFuture<InstallResult> f : futures) { + InstallResult result = f.join(); + if (result.session != this && result.extras != null) { + ArrayList<String> childWarnings = result.extras.getStringArrayList( + PackageInstaller.EXTRA_WARNINGS); + if (!ArrayUtils.isEmpty(childWarnings)) { + multiPackageWarnings.addAll(childWarnings); + } + } + } + } for (CompletableFuture<InstallResult> f : futures) { InstallResult result = f.join(); + Bundle extras = result.extras; + if (isMultiPackage() && result.session == this + && !multiPackageWarnings.isEmpty()) { + if (extras == null) { + extras = new Bundle(); + } + extras.putStringArrayList( + PackageInstaller.EXTRA_WARNINGS, multiPackageWarnings); + } result.session.dispatchSessionFinished( - INSTALL_SUCCEEDED, "Session installed", result.extras); + INSTALL_SUCCEEDED, "Session installed", extras); } } else { PackageManagerException e = (PackageManagerException) t.getCause(); @@ -5189,6 +5214,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { if (!TextUtils.isEmpty(existing)) { fillIn.putExtra(PackageInstaller.EXTRA_OTHER_PACKAGE_NAME, existing); } + ArrayList<String> warnings = extras.getStringArrayList(PackageInstaller.EXTRA_WARNINGS); + if (!ArrayUtils.isEmpty(warnings)) { + fillIn.putStringArrayListExtra(PackageInstaller.EXTRA_WARNINGS, warnings); + } } try { final BroadcastOptions options = BroadcastOptions.makeBasic(); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 6260dd583bf9..68aa93d28330 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -1434,6 +1434,9 @@ public class PackageManagerService implements PackageSender, TestUtilityService break; } } + if (!request.getWarnings().isEmpty()) { + extras.putStringArrayList(PackageInstaller.EXTRA_WARNINGS, request.getWarnings()); + } return extras; } diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 3a9272dc2003..7264e2eff4aa 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -4397,10 +4397,21 @@ class PackageManagerShellCommand extends ShellCommand { session.commit(receiver.getIntentSender()); if (!session.isStaged()) { final Intent result = receiver.getResult(); - final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS, - PackageInstaller.STATUS_FAILURE); + int status = result.getIntExtra( + PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE); + List<String> warnings = + result.getStringArrayListExtra(PackageInstaller.EXTRA_WARNINGS); if (status == PackageInstaller.STATUS_SUCCESS) { - if (logSuccess) { + if (!ArrayUtils.isEmpty(warnings)) { + // Don't start the output string with "Success" because that will make adb + // treat this as a success. + for (String warning : warnings) { + pw.println("Warning: " + warning); + } + // Treat warnings as failure to draw app developers' attention. + status = PackageInstaller.STATUS_FAILURE; + pw.println("Completed with warning(s)"); + } else if (logSuccess) { pw.println("Success"); } } else { diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java index 85b60a07b003..29e0c35d88bd 100644 --- a/services/core/java/com/android/server/pm/UserTypeFactory.java +++ b/services/core/java/com/android/server/pm/UserTypeFactory.java @@ -295,6 +295,8 @@ public final class UserTypeFactory { .setCredentialShareableWithParent(false) .setMediaSharedWithParent(false) .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE) + .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_SEPARATE) + .setHideInSettingsInQuietMode(true) .setCrossProfileIntentFilterAccessControl( UserProperties.CROSS_PROFILE_INTENT_FILTER_ACCESS_LEVEL_SYSTEM) .setInheritDevicePolicy(UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT)); diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java index d16a81267370..d804e01aa31e 100644 --- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java +++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java @@ -444,7 +444,7 @@ public class PackageInfoUtils { updateApplicationInfo(info, flags, state); - initForUser(info, pkg, userId); + initForUser(info, pkg, userId, state); // TODO(b/135203078): Remove PackageParser1/toAppInfoWithoutState and clean all this up PackageStateUnserialized pkgState = pkgSetting.getTransientState(); @@ -690,7 +690,7 @@ public class PackageInfoUtils { info.splitDependencies = pkg.getSplitDependencies().size() == 0 ? null : pkg.getSplitDependencies(); - initForUser(info, pkg, userId); + initForUser(info, pkg, userId, state); info.primaryCpuAbi = pkgSetting.getPrimaryCpuAbi(); info.secondaryCpuAbi = pkgSetting.getSecondaryCpuAbi(); @@ -1006,7 +1006,7 @@ public class PackageInfoUtils { } private static void initForUser(ApplicationInfo output, AndroidPackage input, - @UserIdInt int userId) { + @UserIdInt int userId, PackageUserStateInternal state) { PackageImpl pkg = ((PackageImpl) input); String packageName = input.getPackageName(); output.uid = UserHandle.getUid(userId, UserHandle.getAppId(input.getUid())); @@ -1016,6 +1016,13 @@ public class PackageInfoUtils { return; } + if (android.content.pm.Flags.nullableDataDir() + && !state.isInstalled() && !state.dataExists()) { + // The data dir has been deleted + output.dataDir = null; + return; + } + // For performance reasons, all these paths are built as strings if (userId == UserHandle.USER_SYSTEM) { output.credentialProtectedDataDir = @@ -1050,7 +1057,7 @@ public class PackageInfoUtils { // This duplicates the ApplicationInfo variant because it uses field assignment and the classes // don't inherit from each other, unfortunately. Consolidating logic would introduce overhead. private static void initForUser(InstrumentationInfo output, AndroidPackage input, - @UserIdInt int userId) { + @UserIdInt int userId, PackageUserStateInternal state) { PackageImpl pkg = ((PackageImpl) input); String packageName = input.getPackageName(); if ("android".equals(packageName)) { @@ -1058,6 +1065,13 @@ public class PackageInfoUtils { return; } + if (android.content.pm.Flags.nullableDataDir() + && !state.isInstalled() && !state.dataExists()) { + // The data dir has been deleted + output.dataDir = null; + return; + } + // For performance reasons, all these paths are built as strings if (userId == UserHandle.USER_SYSTEM) { output.credentialProtectedDataDir = @@ -1089,12 +1103,23 @@ public class PackageInfoUtils { } } - @NonNull + /** + * Returns the data dir of the app for the target user. Return null if the app isn't installed + * on the target user and doesn't have a data dir on the target user. + */ + @Nullable public static File getDataDir(PackageStateInternal ps, int userId) { if ("android".equals(ps.getPackageName())) { return Environment.getDataSystemDirectory(); } + if (android.content.pm.Flags.nullableDataDir() + && !ps.getUserStateOrDefault(userId).isInstalled() + && !ps.getUserStateOrDefault(userId).dataExists()) { + // The app has been uninstalled for the user and the data dir has been deleted + return null; + } + if (ps.isDefaultToDeviceProtectedStorage() && PackageManager.APPLY_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) { return Environment.getDataUserDePackageDirectory(ps.getVolumeUuid(), userId, diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java index f14941b2d9c8..46121dcd9dae 100644 --- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java @@ -18,6 +18,7 @@ package com.android.server.pm.pkg.parsing; import static android.content.pm.ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE; import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; +import static android.content.pm.Flags.preventSdkLibApp; import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES; @@ -403,8 +404,9 @@ public class ParsingPackageUtils { try { final File baseApk = new File(lite.getBaseApkPath()); + boolean shouldSkipComponents = lite.isIsSdkLibrary() && preventSdkLibApp(); final ParseResult<ParsingPackage> result = parseBaseApk(input, baseApk, - lite.getPath(), assetLoader, flags); + lite.getPath(), assetLoader, flags, shouldSkipComponents); if (result.isError()) { return input.error(result); } @@ -456,10 +458,11 @@ public class ParsingPackageUtils { final PackageLite lite = liteResult.getResult(); final SplitAssetLoader assetLoader = new DefaultSplitAssetLoader(lite, flags); try { + boolean shouldSkipComponents = lite.isIsSdkLibrary() && preventSdkLibApp(); final ParseResult<ParsingPackage> result = parseBaseApk(input, apkFile, apkFile.getCanonicalPath(), - assetLoader, flags); + assetLoader, flags, shouldSkipComponents); if (result.isError()) { return input.error(result); } @@ -594,7 +597,8 @@ public class ParsingPackageUtils { } private ParseResult<ParsingPackage> parseBaseApk(ParseInput input, File apkFile, - String codePath, SplitAssetLoader assetLoader, int flags) { + String codePath, SplitAssetLoader assetLoader, int flags, + boolean shouldSkipComponents) { final String apkPath = apkFile.getAbsolutePath(); final String volumeUuid = getVolumeUuid(apkPath); @@ -619,7 +623,7 @@ public class ParsingPackageUtils { final Resources res = new Resources(assets, mDisplayMetrics, null); ParseResult<ParsingPackage> result = parseBaseApk(input, apkPath, codePath, res, - parser, flags); + parser, flags, shouldSkipComponents); if (result.isError()) { return input.error(result.getErrorCode(), apkPath + " (at " + parser.getPositionDescription() + "): " @@ -719,11 +723,12 @@ public class ParsingPackageUtils { * @param res The resources from which to resolve values * @param parser The manifest parser * @param flags Flags how to parse + * @param shouldSkipComponents If the package is a sdk-library * @return Parsed package or null on error. */ private ParseResult<ParsingPackage> parseBaseApk(ParseInput input, String apkPath, - String codePath, Resources res, XmlResourceParser parser, int flags) - throws XmlPullParserException, IOException { + String codePath, Resources res, XmlResourceParser parser, int flags, + boolean shouldSkipComponents) throws XmlPullParserException, IOException { final String splitName; final String pkgName; @@ -751,7 +756,8 @@ public class ParsingPackageUtils { final ParsingPackage pkg = mCallback.startParsingPackage( pkgName, apkPath, codePath, manifestArray, isCoreApp); final ParseResult<ParsingPackage> result = - parseBaseApkTags(input, pkg, manifestArray, res, parser, flags); + parseBaseApkTags(input, pkg, manifestArray, res, parser, flags, + shouldSkipComponents); if (result.isError()) { return result; } @@ -987,10 +993,9 @@ public class ParsingPackageUtils { return ParsingUtils.unknownTag("<application>", pkg, parser, input); } } - private ParseResult<ParsingPackage> parseBaseApkTags(ParseInput input, ParsingPackage pkg, - TypedArray sa, Resources res, XmlResourceParser parser, int flags) - throws XmlPullParserException, IOException { + TypedArray sa, Resources res, XmlResourceParser parser, int flags, + boolean shouldSkipComponents) throws XmlPullParserException, IOException { ParseResult<ParsingPackage> sharedUserResult = parseSharedUser(input, pkg, sa); if (sharedUserResult.isError()) { return sharedUserResult; @@ -1027,7 +1032,8 @@ public class ParsingPackageUtils { } } else { foundApp = true; - result = parseBaseApplication(input, pkg, res, parser, flags); + result = parseBaseApplication(input, pkg, res, parser, flags, + shouldSkipComponents); } } else { result = parseBaseApkTag(tagName, input, pkg, res, parser, flags); @@ -1972,8 +1978,8 @@ public class ParsingPackageUtils { * code moves around. */ private ParseResult<ParsingPackage> parseBaseApplication(ParseInput input, - ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags) - throws XmlPullParserException, IOException { + ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags, + boolean shouldSkipComponents) throws XmlPullParserException, IOException { final String pkgName = pkg.getPackageName(); int targetSdk = pkg.getTargetSdkVersion(); @@ -2213,6 +2219,9 @@ public class ParsingPackageUtils { isActivity = true; // fall-through case "receiver": + if (shouldSkipComponents) { + continue; + } ParseResult<ParsedActivity> activityResult = ParsedActivityUtils.parseActivityOrReceiver(mSeparateProcesses, pkg, res, parser, flags, sUseRoundIcon, null /*defaultSplitName*/, @@ -2232,6 +2241,9 @@ public class ParsingPackageUtils { result = activityResult; break; case "service": + if (shouldSkipComponents) { + continue; + } ParseResult<ParsedService> serviceResult = ParsedServiceUtils.parseService(mSeparateProcesses, pkg, res, parser, flags, sUseRoundIcon, null /*defaultSplitName*/, @@ -2245,6 +2257,9 @@ public class ParsingPackageUtils { result = serviceResult; break; case "provider": + if (shouldSkipComponents) { + continue; + } ParseResult<ParsedProvider> providerResult = ParsedProviderUtils.parseProvider(mSeparateProcesses, pkg, res, parser, flags, sUseRoundIcon, null /*defaultSplitName*/, @@ -2256,6 +2271,9 @@ public class ParsingPackageUtils { result = providerResult; break; case "activity-alias": + if (shouldSkipComponents) { + continue; + } activityResult = ParsedActivityUtils.parseActivityAlias(pkg, res, parser, sUseRoundIcon, null /*defaultSplitName*/, input); @@ -2414,7 +2432,7 @@ public class ParsingPackageUtils { /** * For parsing non-MainComponents. Main ones have an order and some special handling which is * done directly in {@link #parseBaseApplication(ParseInput, ParsingPackage, Resources, - * XmlResourceParser, int)}. + * XmlResourceParser, int, boolean)}. */ private ParseResult parseBaseAppChildTag(ParseInput input, String tag, ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags) diff --git a/services/core/java/com/android/server/security/KeyAttestationApplicationIdProviderService.java b/services/core/java/com/android/server/security/KeyAttestationApplicationIdProviderService.java index c908acdd1d6c..d5bc91278aa8 100644 --- a/services/core/java/com/android/server/security/KeyAttestationApplicationIdProviderService.java +++ b/services/core/java/com/android/server/security/KeyAttestationApplicationIdProviderService.java @@ -24,9 +24,10 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.os.Binder; import android.os.RemoteException; import android.os.UserHandle; -import android.security.keymaster.IKeyAttestationApplicationIdProvider; -import android.security.keymaster.KeyAttestationApplicationId; -import android.security.keymaster.KeyAttestationPackageInfo; +import android.security.keystore.IKeyAttestationApplicationIdProvider; +import android.security.keystore.KeyAttestationApplicationId; +import android.security.keystore.KeyAttestationPackageInfo; +import android.security.keystore.Signature; /** * @hide @@ -64,14 +65,25 @@ public class KeyAttestationApplicationIdProviderService for (int i = 0; i < packageNames.length; ++i) { PackageInfo packageInfo = mPackageManager.getPackageInfoAsUser(packageNames[i], PackageManager.GET_SIGNATURES, userId); - keyAttestationPackageInfos[i] = new KeyAttestationPackageInfo(packageNames[i], - packageInfo.getLongVersionCode(), packageInfo.signatures); + KeyAttestationPackageInfo pInfo = new KeyAttestationPackageInfo(); + pInfo.packageName = new String(packageNames[i]); + pInfo.versionCode = packageInfo.getLongVersionCode(); + pInfo.signatures = new Signature[packageInfo.signatures.length]; + for (int index = 0; index < packageInfo.signatures.length; index++) { + Signature sign = new Signature(); + sign.data = packageInfo.signatures[index].toByteArray(); + pInfo.signatures[index] = sign; + } + + keyAttestationPackageInfos[i] = pInfo; } } catch (NameNotFoundException nnfe) { throw new RemoteException(nnfe.getMessage()); } finally { Binder.restoreCallingIdentity(token); } - return new KeyAttestationApplicationId(keyAttestationPackageInfos); + KeyAttestationApplicationId attestAppId = new KeyAttestationApplicationId(); + attestAppId.packageInfos = keyAttestationPackageInfos; + return attestAppId; } } diff --git a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java index bff6d502d566..6d580e97d578 100644 --- a/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java +++ b/services/core/java/com/android/server/speech/SpeechRecognitionManagerServiceImpl.java @@ -39,8 +39,10 @@ import android.speech.IRecognitionSupportCallback; import android.speech.RecognitionService; import android.speech.SpeechRecognizer; import android.util.Slog; +import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; +import com.android.modules.expresslog.Counter; import com.android.server.infra.AbstractPerUserSystemService; import java.util.HashMap; @@ -64,6 +66,9 @@ final class SpeechRecognitionManagerServiceImpl extends private final Map<Integer, Set<RemoteSpeechRecognitionService>> mRemoteServicesByUid = new HashMap<>(); + @GuardedBy("mLock") + private final SparseIntArray mSessionCountByUid = new SparseIntArray(); + SpeechRecognitionManagerServiceImpl( @NonNull SpeechRecognitionManagerService master, @NonNull Object lock, @UserIdInt int userId) { @@ -216,6 +221,7 @@ final class SpeechRecognitionManagerServiceImpl extends service.shutdown(clientToken); } synchronized (mLock) { + decrementSessionCountForUidLocked(callingUid); if (!service.hasActiveSessions()) { removeService(callingUid, service); } @@ -239,6 +245,26 @@ final class SpeechRecognitionManagerServiceImpl extends return ComponentName.unflattenFromString(serviceName); } + @GuardedBy("mLock") + private int getSessionCountByUidLocked(int uid) { + return mSessionCountByUid.get(uid, 0); + } + + @GuardedBy("mLock") + private void incrementSessionCountForUidLocked(int uid) { + mSessionCountByUid.put(uid, mSessionCountByUid.get(uid, 0) + 1); + } + + @GuardedBy("mLock") + private void decrementSessionCountForUidLocked(int uid) { + int newCount = mSessionCountByUid.get(uid, 1) - 1; + if (newCount > 0) { + mSessionCountByUid.put(uid, newCount); + } else { + mSessionCountByUid.delete(uid); + } + } + private RemoteSpeechRecognitionService createService( int callingUid, ComponentName serviceComponent) { synchronized (mLock) { @@ -247,6 +273,18 @@ final class SpeechRecognitionManagerServiceImpl extends if (servicesForClient != null && servicesForClient.size() >= MAX_CONCURRENT_CONNECTIONS_BY_CLIENT) { + Slog.w(TAG, "Number of remote services exceeded for uid: " + callingUid); + Counter.logIncrementWithUid( + "speech_recognition.value_exceed_service_connections_count", + callingUid); + return null; + } + + if (getSessionCountByUidLocked(callingUid) >= MAX_CONCURRENT_CONNECTIONS_BY_CLIENT) { + Slog.w(TAG, "Number of sessions exceeded for uid: " + callingUid); + Counter.logIncrementWithUid( + "speech_recognition.value_exceed_session_count", + callingUid); return null; } @@ -262,6 +300,7 @@ final class SpeechRecognitionManagerServiceImpl extends Slog.i(TAG, "Reused existing connection to " + serviceComponent); } + incrementSessionCountForUidLocked(callingUid); return existingService.get(); } } @@ -282,6 +321,7 @@ final class SpeechRecognitionManagerServiceImpl extends Slog.i(TAG, "Creating a new connection to " + serviceComponent); } + incrementSessionCountForUidLocked(callingUid); return service; } } diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java index a5c0fb3c46af..cddc79db6106 100644 --- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java @@ -1047,6 +1047,16 @@ public class TunerResourceManagerService extends SystemService implements IBinde // in use frontends when no available frontend has been found. int priority = getFrontendHighestClientPriority(fr.getOwnerClientId()); if (currentLowestPriority > priority) { + // we need to check the max used num if the target frontend type is not + // currently in primary use (and simply blocked due to exclusive group) + ClientProfile targetOwnerProfile = getClientProfile(fr.getOwnerClientId()); + int primaryFeId = targetOwnerProfile.getPrimaryFrontend(); + FrontendResource primaryFe = getFrontendResource(primaryFeId); + if (fr.getType() != primaryFe.getType() + && isFrontendMaxNumUseReached(fr.getType())) { + continue; + } + // update the target frontend inUseLowestPriorityFrHandle = fr.getHandle(); currentLowestPriority = priority; isRequestFromSameProcess = (requestClient.getProcessId() diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java index e4f960763d54..a34621642bcd 100644 --- a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java +++ b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java @@ -19,6 +19,7 @@ package com.android.server.vibrator; import android.annotation.Nullable; import android.content.res.Resources; import android.os.VibrationEffect; +import android.os.vibrator.Flags; import android.os.VibratorInfo; import android.os.vibrator.persistence.ParsedVibration; import android.os.vibrator.persistence.VibrationXmlParser; @@ -127,6 +128,10 @@ final class HapticFeedbackCustomization { VibrationXmlParser.VibrationXmlParserException, XmlParserException, XmlPullParserException { + if (!Flags.hapticFeedbackVibrationOemCustomizationEnabled()) { + Slog.d(TAG, "Haptic feedback customization feature is not enabled."); + return null; + } String customizationFile = res.getString( com.android.internal.R.string.config_hapticFeedbackCustomizationFile); diff --git a/services/core/java/com/android/server/vibrator/OWNERS b/services/core/java/com/android/server/vibrator/OWNERS index 9afa68210947..da5a476e9eb8 100644 --- a/services/core/java/com/android/server/vibrator/OWNERS +++ b/services/core/java/com/android/server/vibrator/OWNERS @@ -1,6 +1,5 @@ # Bug component: 345036 - +khalilahmad@google.com lsandrade@google.com michaelwr@google.com -sbowden@google.com -khalilahmad@google.com
\ No newline at end of file +roosa@google.com diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 01ea33f1aecd..0718f2f284a5 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} diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 9ffbc8edd660..ebd21d9a0487 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -8264,7 +8264,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 +8276,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(); @@ -9262,13 +9262,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A Slog.w(TAG, errorMessage); } - // Configuration's equality doesn't consider seq so if only seq number changes in resolved - // override configuration. Therefore ConfigurationContainer doesn't change merged override - // configuration, but it's used to push configuration changes so explicitly update that. - if (getMergedOverrideConfiguration().seq != getResolvedOverrideConfiguration().seq) { - onMergedOverrideConfigurationChanged(); - } - // Before PiP animation is done, th windowing mode of the activity is still the previous // mode (see RootWindowContainer#moveActivityToPinnedRootTask). So once the windowing mode // of activity is changed, it is the signal of the last step to update the PiP states. diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 27315bb23ae5..7cccf6b578ff 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -707,7 +707,7 @@ class ActivityStarter { } } - int res; + int res = START_CANCELED; synchronized (mService.mGlobalLock) { final boolean globalConfigWillChange = mRequest.globalConfig != null && mService.getGlobalConfiguration().diff(mRequest.globalConfig) != 0; @@ -719,22 +719,20 @@ class ActivityStarter { + "will change = %b", globalConfigWillChange); final long origId = Binder.clearCallingIdentity(); - - res = resolveToHeavyWeightSwitcherIfNeeded(); - if (res != START_SUCCESS) { - return res; - } - try { + res = resolveToHeavyWeightSwitcherIfNeeded(); + if (res != START_SUCCESS) { + return res; + } + res = executeRequest(mRequest); } finally { + Binder.restoreCallingIdentity(origId); mRequest.logMessage.append(" result code=").append(res); Slog.i(TAG, mRequest.logMessage.toString()); mRequest.logMessage.setLength(0); } - Binder.restoreCallingIdentity(origId); - if (globalConfigWillChange) { // If the caller also wants to switch to a new configuration, do so now. // This allows a clean switch, as we are waiting for the current activity diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java index 58d4e82961f6..794711262a75 100644 --- a/services/core/java/com/android/server/wm/ConfigurationContainer.java +++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java @@ -223,9 +223,9 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { } /** - * Update merged override configuration based on corresponding parent's config and notify all - * its children. If there is no parent, merged override configuration will set equal to current - * override config. + * Update merged override configuration based on corresponding parent's config. If there is no + * parent, merged override configuration will set equal to current override config. This + * doesn't cascade on its own since it's called by {@link #onConfigurationChanged}. * @see #mMergedOverrideConfiguration */ void onMergedOverrideConfigurationChanged() { @@ -240,10 +240,6 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { } else { mMergedOverrideConfiguration.setTo(mResolvedOverrideConfiguration); } - for (int i = getChildCount() - 1; i >= 0; --i) { - final ConfigurationContainer child = getChildAt(i); - child.onMergedOverrideConfigurationChanged(); - } } /** @@ -688,8 +684,6 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { if (newParent != null) { // Update full configuration of this container and all its children. onConfigurationChanged(newParent.mFullConfiguration); - // Update merged override configuration of this container and all its children. - onMergedOverrideConfigurationChanged(); } } diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java index f81e5d453434..df26b101a657 100644 --- a/services/core/java/com/android/server/wm/DisplayArea.java +++ b/services/core/java/com/android/server/wm/DisplayArea.java @@ -44,6 +44,7 @@ import android.util.proto.ProtoOutputStream; import android.window.DisplayAreaInfo; import android.window.IDisplayAreaOrganizer; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; import com.android.server.policy.WindowManagerPolicy; @@ -79,6 +80,12 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { private final Configuration mTmpConfiguration = new Configuration(); /** + * Prevent duplicate calls to onDisplayAreaAppeared, or early call of onDisplayAreaInfoChanged. + */ + @VisibleForTesting + boolean mDisplayAreaAppearedSent; + + /** * Whether this {@link DisplayArea} should ignore fixed-orientation request. If {@code true}, it * can never specify orientation, but shows the fixed-orientation apps below it in the * letterbox; otherwise, it rotates based on the fixed-orientation request. @@ -582,18 +589,31 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { sendDisplayAreaVanished(lastOrganizer); if (!skipDisplayAreaAppeared) { sendDisplayAreaAppeared(); + } else if (organizer != null) { + // Set as sent since the DisplayAreaAppearedInfo will be sent back when registered. + mDisplayAreaAppearedSent = true; } } + @VisibleForTesting void sendDisplayAreaAppeared() { - if (mOrganizer == null) return; + if (mOrganizer == null || mDisplayAreaAppearedSent) return; mOrganizerController.onDisplayAreaAppeared(mOrganizer, this); + mDisplayAreaAppearedSent = true; + } + + @VisibleForTesting + void sendDisplayAreaInfoChanged() { + if (mOrganizer == null || !mDisplayAreaAppearedSent) return; + mOrganizerController.onDisplayAreaInfoChanged(mOrganizer, this); } + @VisibleForTesting void sendDisplayAreaVanished(IDisplayAreaOrganizer organizer) { - if (organizer == null) return; + if (organizer == null || !mDisplayAreaAppearedSent) return; migrateToNewSurfaceControl(getSyncTransaction()); mOrganizerController.onDisplayAreaVanished(organizer, this); + mDisplayAreaAppearedSent = false; } @Override @@ -603,7 +623,7 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { super.onConfigurationChanged(newParentConfig); if (mOrganizer != null && getConfiguration().diff(mTmpConfiguration) != 0) { - mOrganizerController.onDisplayAreaInfoChanged(mOrganizer, this); + sendDisplayAreaInfoChanged(); } } diff --git a/services/core/java/com/android/server/wm/RefreshRatePolicy.java b/services/core/java/com/android/server/wm/RefreshRatePolicy.java index 99831d3f6e48..23c135a7b83a 100644 --- a/services/core/java/com/android/server/wm/RefreshRatePolicy.java +++ b/services/core/java/com/android/server/wm/RefreshRatePolicy.java @@ -19,6 +19,8 @@ package com.android.server.wm; import static android.hardware.display.DisplayManager.SWITCHING_TYPE_NONE; import static android.hardware.display.DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY; +import static com.android.window.flags.Flags.explicitRefreshRateHints; + import android.hardware.display.DisplayManager; import android.view.Display; import android.view.Display.Mode; @@ -137,7 +139,7 @@ class RefreshRatePolicy { // to run in default refresh rate. But if the display size of default mode is different // from the using preferred mode, then still keep the preferred mode to avoid disturbing // the animation. - if (w.isAnimationRunningSelfOrParent()) { + if (!explicitRefreshRateHints() && w.isAnimationRunningSelfOrParent()) { Display.Mode preferredMode = null; for (Display.Mode mode : mDisplayInfo.supportedModes) { if (preferredDisplayModeId == mode.getModeId()) { @@ -251,7 +253,7 @@ class RefreshRatePolicy { // If app is animating, it's not able to control refresh rate because we want the animation // to run in default refresh rate. - if (w.isAnimationRunningSelfOrParent()) { + if (!explicitRefreshRateHints() && w.isAnimationRunningSelfOrParent()) { return w.mFrameRateVote.reset(); } diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 471dea846ef4..f3fb7c442b78 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -703,6 +703,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { if (dc == null || mTargetDisplays.contains(dc)) return; mTargetDisplays.add(dc); addOnTopTasks(dc, mOnTopTasksStart); + mController.startPerfHintForDisplay(dc.mDisplayId); } /** diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index e686f1b73c8c..de7871e3c231 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -22,8 +22,10 @@ import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS; import static android.view.WindowManager.TRANSIT_NONE; import static android.view.WindowManager.TRANSIT_OPEN; +import static android.window.SystemPerformanceHinter.HINT_SF; import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY; +import static com.android.window.flags.Flags.explicitRefreshRateHints; import android.annotation.NonNull; import android.annotation.Nullable; @@ -39,6 +41,7 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Slog; import android.util.SparseArray; import android.util.TimeUtils; @@ -48,6 +51,8 @@ import android.view.WindowManager; import android.window.ITransitionMetricsReporter; import android.window.ITransitionPlayer; import android.window.RemoteTransition; +import android.window.SystemPerformanceHinter; +import android.window.SystemPerformanceHinter.HighPerfSession; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; import android.window.WindowContainerTransaction; @@ -125,6 +130,8 @@ class TransitionController { SnapshotController mSnapshotController; TransitionTracer mTransitionTracer; + private SystemPerformanceHinter mSystemPerformanceHinter; + private final ArrayList<WindowManagerInternal.AppTransitionListener> mLegacyListeners = new ArrayList<>(); @@ -176,6 +183,24 @@ class TransitionController { private final IBinder.DeathRecipient mTransitionPlayerDeath; + /** + * Tracks active perf sessions that boost frame rate and hint sf to increase its + * estimated work duration. + */ + private final ArraySet<HighPerfSession> mHighPerfSessions = new ArraySet<>(); + + + /** + * Starts a perf hint session which will boost the refresh rate for the display and change + * sf duration to handle larger workloads. + */ + void startPerfHintForDisplay(int displayId) { + if (explicitRefreshRateHints()) { + mHighPerfSessions.add(mSystemPerformanceHinter.startSession(HINT_SF, displayId, + "Transition collected")); + } + } + static class QueuedTransition { final Transition mTransition; final OnStartCollect mOnStartCollect; @@ -255,6 +280,12 @@ class TransitionController { mIsWaitingForDisplayEnabled = !wms.mDisplayEnabled; registerLegacyListener(wms.mActivityManagerAppTransitionNotifier); setSyncEngine(wms.mSyncEngine); + setSystemPerformanceHinter(wms.mSystemPerformanceHinter); + } + + @VisibleForTesting + void setSystemPerformanceHinter(SystemPerformanceHinter hinter) { + mSystemPerformanceHinter = hinter; } @VisibleForTesting @@ -1036,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) { @@ -1194,18 +1233,27 @@ class TransitionController { final boolean animatingState = !mPlayingTransitions.isEmpty() || (mCollectingTransition != null && mCollectingTransition.isStarted()); if (animatingState && !mAnimatingState) { - t.setEarlyWakeupStart(); + if (!explicitRefreshRateHints()) { + t.setEarlyWakeupStart(); + } // Usually transitions put quite a load onto the system already (with all the things // happening in app), so pause task snapshot persisting to not increase the load. mSnapshotController.setPause(true); mAnimatingState = true; Transition.asyncTraceBegin("animating", 0x41bfaf1 /* hashcode of TAG */); } else if (!animatingState && mAnimatingState) { - t.setEarlyWakeupEnd(); + if (!explicitRefreshRateHints()) { + t.setEarlyWakeupEnd(); + } mAtm.mWindowManager.scheduleAnimationLocked(); mSnapshotController.setPause(false); mAnimatingState = false; Transition.asyncTraceEnd(0x41bfaf1 /* hashcode of TAG */); + // We close all perf sessions here when all transitions finish. The sessions are created + // when we collect transitions because we have access to the display id. + for (HighPerfSession perfSession : mHighPerfSessions) { + perfSession.close(); + } } } diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 7e5dabbea737..674ff487800f 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -85,13 +85,8 @@ class WallpaperController { // to another, and this is the previous wallpaper target. private WindowState mPrevWallpaperTarget = null; - private float mLastWallpaperX = -1; - private float mLastWallpaperY = -1; - private float mLastWallpaperXStep = -1; - private float mLastWallpaperYStep = -1; private float mLastWallpaperZoomOut = 0; - private int mLastWallpaperDisplayOffsetX = Integer.MIN_VALUE; - private int mLastWallpaperDisplayOffsetY = Integer.MIN_VALUE; + // Whether COMMAND_FREEZE was dispatched. private boolean mLastFrozen = false; @@ -116,8 +111,6 @@ class WallpaperController { private static final int WALLPAPER_DRAW_TIMEOUT = 2; private int mWallpaperDrawState = WALLPAPER_DRAW_NORMAL; - private boolean mShouldUpdateZoom; - @Nullable private Point mLargestDisplaySize = null; private final FindWallpaperTargetResult mFindResults = new FindWallpaperTargetResult(); @@ -370,6 +363,7 @@ class WallpaperController { // Full size of the wallpaper (usually larger than bounds above to parallax scroll when // swiping through Launcher pages). final Rect wallpaperFrame = wallpaperWin.getFrame(); + WallpaperWindowToken token = wallpaperWin.mToken.asWallpaperToken(); final int diffWidth = wallpaperFrame.width() - lastWallpaperBounds.width(); final int diffHeight = wallpaperFrame.height() - lastWallpaperBounds.height(); @@ -394,10 +388,10 @@ class WallpaperController { // The 0 to 1 scale is because the "length" varies depending on how many home screens you // have, so 0 is the left of the first home screen, and 1 is the right of the last one (for // LTR, and the opposite for RTL). - float wpx = mLastWallpaperX >= 0 ? mLastWallpaperX : defaultWallpaperX; + float wpx = token.mWallpaperX >= 0 ? token.mWallpaperX : defaultWallpaperX; // "Wallpaper X step size" is how much of that 0-1 is one "page" of the home screen // when scrolling. - float wpxs = mLastWallpaperXStep >= 0 ? mLastWallpaperXStep : -1.0f; + float wpxs = token.mWallpaperXStep >= 0 ? token.mWallpaperXStep : -1.0f; // Difference between width of wallpaper image, and the last size of the wallpaper. // This is the horizontal surplus from the prior configuration. int availw = diffWidth; @@ -406,10 +400,10 @@ class WallpaperController { wallpaperWin.isRtl()); availw -= displayOffset; int offset = availw > 0 ? -(int)(availw * wpx + .5f) : 0; - if (mLastWallpaperDisplayOffsetX != Integer.MIN_VALUE) { + if (token.mWallpaperDisplayOffsetX != Integer.MIN_VALUE) { // if device is LTR, then offset wallpaper to the left (the wallpaper is drawn // always starting from the left of the screen). - offset += mLastWallpaperDisplayOffsetX; + offset += token.mWallpaperDisplayOffsetX; } else if (!wallpaperWin.isRtl()) { // In RTL the offset is calculated so that the wallpaper ends up right aligned (see // offset above). @@ -423,11 +417,11 @@ class WallpaperController { rawChanged = true; } - float wpy = mLastWallpaperY >= 0 ? mLastWallpaperY : 0.5f; - float wpys = mLastWallpaperYStep >= 0 ? mLastWallpaperYStep : -1.0f; + float wpy = token.mWallpaperY >= 0 ? token.mWallpaperY : 0.5f; + float wpys = token.mWallpaperYStep >= 0 ? token.mWallpaperYStep : -1.0f; offset = diffHeight > 0 ? -(int) (diffHeight * wpy + .5f) : 0; - if (mLastWallpaperDisplayOffsetY != Integer.MIN_VALUE) { - offset += mLastWallpaperDisplayOffsetY; + if (token.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) { + offset += token.mWallpaperDisplayOffsetY; } newYOffset = offset; @@ -549,8 +543,10 @@ class WallpaperController { void setWallpaperZoomOut(WindowState window, float zoom) { if (Float.compare(window.mWallpaperZoomOut, zoom) != 0) { window.mWallpaperZoomOut = zoom; - mShouldUpdateZoom = true; - updateWallpaperOffsetLocked(window, false); + computeLastWallpaperZoomOut(); + for (WallpaperWindowToken token : mWallpaperTokens) { + token.updateWallpaperOffset(false); + } } } @@ -598,43 +594,48 @@ class WallpaperController { // zoom effect from home. target = changingTarget; } - if (target != null) { - if (target.mWallpaperX >= 0) { - mLastWallpaperX = target.mWallpaperX; - } else if (changingTarget.mWallpaperX >= 0) { - mLastWallpaperX = changingTarget.mWallpaperX; - } - if (target.mWallpaperY >= 0) { - mLastWallpaperY = target.mWallpaperY; - } else if (changingTarget.mWallpaperY >= 0) { - mLastWallpaperY = changingTarget.mWallpaperY; - } - computeLastWallpaperZoomOut(); - if (target.mWallpaperDisplayOffsetX != Integer.MIN_VALUE) { - mLastWallpaperDisplayOffsetX = target.mWallpaperDisplayOffsetX; - } else if (changingTarget.mWallpaperDisplayOffsetX != Integer.MIN_VALUE) { - mLastWallpaperDisplayOffsetX = changingTarget.mWallpaperDisplayOffsetX; - } - if (target.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) { - mLastWallpaperDisplayOffsetY = target.mWallpaperDisplayOffsetY; - } else if (changingTarget.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) { - mLastWallpaperDisplayOffsetY = changingTarget.mWallpaperDisplayOffsetY; - } - if (target.mWallpaperXStep >= 0) { - mLastWallpaperXStep = target.mWallpaperXStep; - } else if (changingTarget.mWallpaperXStep >= 0) { - mLastWallpaperXStep = changingTarget.mWallpaperXStep; - } - if (target.mWallpaperYStep >= 0) { - mLastWallpaperYStep = target.mWallpaperYStep; - } else if (changingTarget.mWallpaperYStep >= 0) { - mLastWallpaperYStep = changingTarget.mWallpaperYStep; - } - } - for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) { - mWallpaperTokens.get(curTokenNdx).updateWallpaperOffset(sync); + WallpaperWindowToken token = getTokenForTarget(target); + if (token == null) return; + + if (target.mWallpaperX >= 0) { + token.mWallpaperX = target.mWallpaperX; + } else if (changingTarget.mWallpaperX >= 0) { + token.mWallpaperX = changingTarget.mWallpaperX; + } + if (target.mWallpaperY >= 0) { + token.mWallpaperY = target.mWallpaperY; + } else if (changingTarget.mWallpaperY >= 0) { + token.mWallpaperY = changingTarget.mWallpaperY; + } + if (target.mWallpaperDisplayOffsetX != Integer.MIN_VALUE) { + token.mWallpaperDisplayOffsetX = target.mWallpaperDisplayOffsetX; + } else if (changingTarget.mWallpaperDisplayOffsetX != Integer.MIN_VALUE) { + token.mWallpaperDisplayOffsetX = changingTarget.mWallpaperDisplayOffsetX; + } + if (target.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) { + token.mWallpaperDisplayOffsetY = target.mWallpaperDisplayOffsetY; + } else if (changingTarget.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) { + token.mWallpaperDisplayOffsetY = changingTarget.mWallpaperDisplayOffsetY; } + if (target.mWallpaperXStep >= 0) { + token.mWallpaperXStep = target.mWallpaperXStep; + } else if (changingTarget.mWallpaperXStep >= 0) { + token.mWallpaperXStep = changingTarget.mWallpaperXStep; + } + if (target.mWallpaperYStep >= 0) { + token.mWallpaperYStep = target.mWallpaperYStep; + } else if (changingTarget.mWallpaperYStep >= 0) { + token.mWallpaperYStep = changingTarget.mWallpaperYStep; + } + token.updateWallpaperOffset(sync); + } + + private WallpaperWindowToken getTokenForTarget(WindowState target) { + if (target == null) return null; + WindowState window = mFindResults.getTopWallpaper( + target.canShowWhenLocked() && mService.isKeyguardLocked()); + return window == null ? null : window.mToken.asWallpaperToken(); } void clearLastWallpaperTimeoutTime() { @@ -805,10 +806,11 @@ class WallpaperController { // all wallpapers go behind it. findWallpaperTarget(); updateWallpaperWindowsTarget(mFindResults); + WallpaperWindowToken token = getTokenForTarget(mWallpaperTarget); // The window is visible to the compositor...but is it visible to the user? // That is what the wallpaper cares about. - final boolean visible = mWallpaperTarget != null; + final boolean visible = token != null; if (DEBUG_WALLPAPER) { Slog.v(TAG, "Wallpaper visibility: " + visible + " at display " + mDisplayContent.getDisplayId()); @@ -816,19 +818,18 @@ class WallpaperController { if (visible) { if (mWallpaperTarget.mWallpaperX >= 0) { - mLastWallpaperX = mWallpaperTarget.mWallpaperX; - mLastWallpaperXStep = mWallpaperTarget.mWallpaperXStep; + token.mWallpaperX = mWallpaperTarget.mWallpaperX; + token.mWallpaperXStep = mWallpaperTarget.mWallpaperXStep; } - computeLastWallpaperZoomOut(); if (mWallpaperTarget.mWallpaperY >= 0) { - mLastWallpaperY = mWallpaperTarget.mWallpaperY; - mLastWallpaperYStep = mWallpaperTarget.mWallpaperYStep; + token.mWallpaperY = mWallpaperTarget.mWallpaperY; + token.mWallpaperYStep = mWallpaperTarget.mWallpaperYStep; } if (mWallpaperTarget.mWallpaperDisplayOffsetX != Integer.MIN_VALUE) { - mLastWallpaperDisplayOffsetX = mWallpaperTarget.mWallpaperDisplayOffsetX; + token.mWallpaperDisplayOffsetX = mWallpaperTarget.mWallpaperDisplayOffsetX; } if (mWallpaperTarget.mWallpaperDisplayOffsetY != Integer.MIN_VALUE) { - mLastWallpaperDisplayOffsetY = mWallpaperTarget.mWallpaperDisplayOffsetY; + token.mWallpaperDisplayOffsetY = mWallpaperTarget.mWallpaperDisplayOffsetY; } } @@ -1020,13 +1021,11 @@ class WallpaperController { * we'll have conflicts and break the "depth system" mental model. */ private void computeLastWallpaperZoomOut() { - if (mShouldUpdateZoom) { - mLastWallpaperZoomOut = 0; - mDisplayContent.forAllWindows(mComputeMaxZoomOutFunction, true); - mShouldUpdateZoom = false; - } + mLastWallpaperZoomOut = 0; + mDisplayContent.forAllWindows(mComputeMaxZoomOutFunction, true); } + private float zoomOutToScale(float zoomOut) { return MathUtils.lerp(mMinWallpaperScale, mMaxWallpaperScale, 1 - zoomOut); } @@ -1034,19 +1033,28 @@ class WallpaperController { void dump(PrintWriter pw, String prefix) { pw.print(prefix); pw.print("displayId="); pw.println(mDisplayContent.getDisplayId()); pw.print(prefix); pw.print("mWallpaperTarget="); pw.println(mWallpaperTarget); + pw.print(prefix); pw.print("mLastWallpaperZoomOut="); pw.println(mLastWallpaperZoomOut); if (mPrevWallpaperTarget != null) { pw.print(prefix); pw.print("mPrevWallpaperTarget="); pw.println(mPrevWallpaperTarget); } - pw.print(prefix); pw.print("mLastWallpaperX="); pw.print(mLastWallpaperX); - pw.print(" mLastWallpaperY="); pw.println(mLastWallpaperY); - if (mLastWallpaperDisplayOffsetX != Integer.MIN_VALUE - || mLastWallpaperDisplayOffsetY != Integer.MIN_VALUE) { - pw.print(prefix); - pw.print("mLastWallpaperDisplayOffsetX="); pw.print(mLastWallpaperDisplayOffsetX); - pw.print(" mLastWallpaperDisplayOffsetY="); pw.println(mLastWallpaperDisplayOffsetY); + + for (WallpaperWindowToken t : mWallpaperTokens) { + pw.print(prefix); pw.println("token " + t + ":"); + pw.print(prefix); pw.print(" canShowWhenLocked="); pw.println(t.canShowWhenLocked()); + dumpValue(pw, prefix, "mWallpaperX", t.mWallpaperX); + dumpValue(pw, prefix, "mWallpaperY", t.mWallpaperY); + dumpValue(pw, prefix, "mWallpaperXStep", t.mWallpaperXStep); + dumpValue(pw, prefix, "mWallpaperYStep", t.mWallpaperYStep); + dumpValue(pw, prefix, "mWallpaperDisplayOffsetX", t.mWallpaperDisplayOffsetX); + dumpValue(pw, prefix, "mWallpaperDisplayOffsetY", t.mWallpaperDisplayOffsetY); } } + private void dumpValue(PrintWriter pw, String prefix, String valueName, float value) { + pw.print(prefix); pw.print(" " + valueName + "="); + pw.println(value >= 0 ? value : "NA"); + } + /** Helper class for storing the results of a wallpaper target find operation. */ final private static class FindWallpaperTargetResult { diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java index c7fd147cfb4a..50ef52a4d9dd 100644 --- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java +++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java @@ -42,6 +42,12 @@ class WallpaperWindowToken extends WindowToken { private static final String TAG = TAG_WITH_CLASS_NAME ? "WallpaperWindowToken" : TAG_WM; private boolean mShowWhenLocked = false; + float mWallpaperX = -1; + float mWallpaperY = -1; + float mWallpaperXStep = -1; + float mWallpaperYStep = -1; + int mWallpaperDisplayOffsetX = Integer.MIN_VALUE; + int mWallpaperDisplayOffsetY = Integer.MIN_VALUE; WallpaperWindowToken(WindowManagerService service, IBinder token, boolean explicit, DisplayContent dc, boolean ownerCanManageAppTokens) { diff --git a/services/core/java/com/android/server/wm/WindowManagerFlags.java b/services/core/java/com/android/server/wm/WindowManagerFlags.java new file mode 100644 index 000000000000..5b9acb2f67c4 --- /dev/null +++ b/services/core/java/com/android/server/wm/WindowManagerFlags.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import com.android.window.flags.Flags; + +/** + * Utility class to read the flags used in the WindowManager server. + * + * It is not very cheap to read trunk stable flag, so having a centralized place to cache the flag + * values in the system server side. + * + * Flags should be defined in `core.java.android.window.flags` to allow access from client side. + * + * To override flag: + * adb shell device_config put [namespace] [package].[name] [true/false] + * adb reboot + * + * To access in wm: + * {@link WindowManagerService#mFlags} + * + * Notes: + * The system may use flags at anytime, so changing flags will only take effect after device + * reboot. Otherwise, it may result unexpected behavior, such as broken transition. + * When a flag needs to be read from both the server side and the client side, changing the flag + * value will result difference in server and client until device reboot. + */ +class WindowManagerFlags { + + /* Start Available Flags */ + + final boolean mSyncWindowConfigUpdateFlag = Flags.syncWindowConfigUpdateFlag(); + + final boolean mWindowStateResizeItemFlag = Flags.windowStateResizeItemFlag(); + + /* End Available Flags */ +} diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index c9107e8cf5f3..f339d249e272 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -304,6 +304,7 @@ import android.window.ClientWindowFrames; import android.window.ISurfaceSyncGroupCompletedListener; import android.window.ITaskFpsCallback; import android.window.ScreenCapture; +import android.window.SystemPerformanceHinter; import android.window.TaskSnapshot; import android.window.WindowContainerToken; import android.window.WindowContextInfo; @@ -547,6 +548,8 @@ public class WindowManagerService extends IWindowManager.Stub @VisibleForTesting WindowManagerPolicy mPolicy; + final WindowManagerFlags mFlags; + final IActivityManager mActivityManager; final ActivityManagerInternal mAmInternal; final UserManagerInternal mUmInternal; @@ -1036,6 +1039,8 @@ public class WindowManagerService extends IWindowManager.Stub sThreadPriorityBooster.reset(); } + SystemPerformanceHinter mSystemPerformanceHinter; + void openSurfaceTransaction() { try { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "openSurfaceTransaction"); @@ -1153,6 +1158,7 @@ public class WindowManagerService extends IWindowManager.Stub mGlobalLock = atm.getGlobalLock(); mAtmService = atm; mContext = context; + mFlags = new WindowManagerFlags(); mIsPc = mContext.getPackageManager().hasSystemFeature(FEATURE_PC); mAllowBootMessages = showBootMsgs; mLimitedAlphaCompositing = context.getResources().getBoolean( @@ -1329,6 +1335,13 @@ public class WindowManagerService extends IWindowManager.Stub mBlurController = new BlurController(mContext, mPowerManager); mTaskFpsCallbackController = new TaskFpsCallbackController(mContext); mAccessibilityController = new AccessibilityController(this); + mSystemPerformanceHinter = new SystemPerformanceHinter(mContext, displayId -> { + synchronized (mGlobalLock) { + DisplayContent dc = mRoot.getDisplayContent(displayId); + return (dc == null) ? null : dc.getSurfaceControl(); + } + + }, mTransactionFactory); } DisplayAreaPolicy.Provider getDisplayAreaPolicyProvider() { @@ -3101,10 +3114,15 @@ public class WindowManagerService extends IWindowManager.Stub @Override public void notifyKeyguardTrustedChanged() { - synchronized (mGlobalLock) { - if (mAtmService.mKeyguardController.isKeyguardShowing(DEFAULT_DISPLAY)) { - mRoot.ensureActivitiesVisible(null, 0, false /* preserveWindows */); + final long origId = Binder.clearCallingIdentity(); + try { + synchronized (mGlobalLock) { + if (mAtmService.mKeyguardController.isKeyguardShowing(DEFAULT_DISPLAY)) { + mRoot.ensureActivitiesVisible(null, 0, false /* preserveWindows */); + } } + } finally { + Binder.restoreCallingIdentity(origId); } } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 4beec2bc79e6..726d4d7c34e7 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -186,6 +186,7 @@ import android.annotation.Nullable; import android.app.ActivityTaskManager; import android.app.AppOpsManager; import android.app.admin.DevicePolicyCache; +import android.app.servertransaction.WindowStateResizeItem; import android.content.Context; import android.content.res.Configuration; import android.graphics.Matrix; @@ -3732,30 +3733,44 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP markRedrawForSyncReported(); - try { - mClient.resized(mClientWindowFrames, reportDraw, mLastReportedConfiguration, - getCompatInsetsState(), forceRelayout, alwaysConsumeSystemBars, displayId, - syncWithBuffers ? mSyncSeqId : -1, isDragResizing); - if (drawPending && prevRotation >= 0 && prevRotation != mLastReportedConfiguration - .getMergedConfiguration().windowConfiguration.getRotation()) { - mOrientationChangeRedrawRequestTime = SystemClock.elapsedRealtime(); - ProtoLog.v(WM_DEBUG_ORIENTATION, - "Requested redraw for orientation change: %s", this); - } - - if (mWmService.mAccessibilityController.hasCallbacks()) { - mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(displayId); + if (mWmService.mFlags.mWindowStateResizeItemFlag) { + getProcess().scheduleClientTransactionItem( + WindowStateResizeItem.obtain(mClient, mClientWindowFrames, reportDraw, + mLastReportedConfiguration, getCompatInsetsState(), forceRelayout, + alwaysConsumeSystemBars, displayId, + syncWithBuffers ? mSyncSeqId : -1, isDragResizing)); + onResizePostDispatched(drawPending, prevRotation, displayId); + } else { + // TODO(b/301870955): cleanup after launch + try { + mClient.resized(mClientWindowFrames, reportDraw, mLastReportedConfiguration, + getCompatInsetsState(), forceRelayout, alwaysConsumeSystemBars, displayId, + syncWithBuffers ? mSyncSeqId : -1, isDragResizing); + onResizePostDispatched(drawPending, prevRotation, displayId); + } catch (RemoteException e) { + // Cancel orientation change of this window to avoid blocking unfreeze display. + setOrientationChanging(false); + mLastFreezeDuration = (int) (SystemClock.elapsedRealtime() + - mWmService.mDisplayFreezeTime); + Slog.w(TAG, "Failed to report 'resized' to " + this + " due to " + e); } - } catch (RemoteException e) { - // Cancel orientation change of this window to avoid blocking unfreeze display. - setOrientationChanging(false); - mLastFreezeDuration = (int)(SystemClock.elapsedRealtime() - - mWmService.mDisplayFreezeTime); - Slog.w(TAG, "Failed to report 'resized' to " + this + " due to " + e); } Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } + private void onResizePostDispatched(boolean drawPending, int prevRotation, int displayId) { + if (drawPending && prevRotation >= 0 && prevRotation != mLastReportedConfiguration + .getMergedConfiguration().windowConfiguration.getRotation()) { + mOrientationChangeRedrawRequestTime = SystemClock.elapsedRealtime(); + ProtoLog.v(WM_DEBUG_ORIENTATION, + "Requested redraw for orientation change: %s", this); + } + + if (mWmService.mAccessibilityController.hasCallbacks()) { + mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(displayId); + } + } + boolean inRelaunchingActivity() { return mActivityRecord != null && mActivityRecord.isRelaunching(); } diff --git a/services/core/jni/tvinput/JTvInputHal.cpp b/services/core/jni/tvinput/JTvInputHal.cpp index 68e2c9a57c24..c7366173ecda 100644 --- a/services/core/jni/tvinput/JTvInputHal.cpp +++ b/services/core/jni/tvinput/JTvInputHal.cpp @@ -147,7 +147,6 @@ int JTvInputHal::removeStream(int deviceId, int streamId) { } int JTvInputHal::setTvMessageEnabled(int deviceId, int streamId, int type, bool enabled) { - Mutex::Autolock autoLock(&mLock); if (!mTvInput->setTvMessageEnabled(deviceId, streamId, static_cast<AidlTvMessageEventType>(type), enabled) .isOk()) { @@ -188,7 +187,7 @@ static const std::map<std::pair<AidlAudioDeviceType, std::string>, audio_devices void JTvInputHal::onDeviceAvailable(const TvInputDeviceInfoWrapper& info) { { - Mutex::Autolock autoLock(&mLock); + Mutex::Autolock autoLock(&mStreamLock); mConnections.add(info.deviceId, KeyedVector<int, Connection>()); } JNIEnv* env = AndroidRuntime::getJNIEnv(); @@ -275,7 +274,7 @@ void JTvInputHal::onDeviceAvailable(const TvInputDeviceInfoWrapper& info) { void JTvInputHal::onDeviceUnavailable(int deviceId) { { - Mutex::Autolock autoLock(&mLock); + Mutex::Autolock autoLock(&mStreamLock); KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId); for (size_t i = 0; i < connections.size(); ++i) { removeStream(deviceId, connections.keyAt(i)); @@ -289,7 +288,7 @@ void JTvInputHal::onDeviceUnavailable(int deviceId) { void JTvInputHal::onStreamConfigurationsChanged(int deviceId, int cableConnectionStatus) { { - Mutex::Autolock autoLock(&mLock); + Mutex::Autolock autoLock(&mStreamLock); KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId); for (size_t i = 0; i < connections.size(); ++i) { removeStream(deviceId, connections.keyAt(i)); @@ -330,7 +329,7 @@ void JTvInputHal::onTvMessage(int deviceId, int streamId, AidlTvMessageEventType void JTvInputHal::onCaptured(int deviceId, int streamId, uint32_t seq, bool succeeded) { sp<BufferProducerThread> thread; { - Mutex::Autolock autoLock(&mLock); + Mutex::Autolock autoLock(&mStreamLock); KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId); Connection& connection = connections.editValueFor(streamId); if (connection.mThread == NULL) { diff --git a/services/core/jni/tvinput/JTvInputHal.h b/services/core/jni/tvinput/JTvInputHal.h index b7b4b1621512..1d8d1629f67d 100644 --- a/services/core/jni/tvinput/JTvInputHal.h +++ b/services/core/jni/tvinput/JTvInputHal.h @@ -220,7 +220,6 @@ private: void onTvMessage(int deviceId, int streamId, AidlTvMessageEventType type, AidlTvMessage& message, signed char data[], int dataLength); - Mutex mLock; Mutex mStreamLock; jweak mThiz; sp<Looper> mLooper; diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java index 08df65169224..a4adf5866f3d 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java @@ -65,7 +65,6 @@ import android.util.Slog; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; -import com.android.internal.content.PackageMonitor; import com.android.server.credentials.metrics.ApiName; import com.android.server.credentials.metrics.ApiStatus; import com.android.server.infra.AbstractMasterSystemService; @@ -90,7 +89,7 @@ import java.util.stream.Collectors; */ public final class CredentialManagerService extends AbstractMasterSystemService< - CredentialManagerService, CredentialManagerServiceImpl> { + CredentialManagerService, CredentialManagerServiceImpl> { private static final String TAG = "CredManSysService"; private static final String PERMISSION_DENIED_ERROR = "permission_denied"; @@ -111,7 +110,8 @@ public final class CredentialManagerService /** Cache of all ongoing request sessions per user id. */ @GuardedBy("mLock") - private final SparseArray<Map<IBinder, RequestSession>> mRequestSessions = new SparseArray<>(); + private final SparseArray<Map<IBinder, RequestSession>> mRequestSessions = + new SparseArray<>(); private final SessionManager mSessionManager = new SessionManager(); @@ -123,8 +123,6 @@ public final class CredentialManagerService null, PACKAGE_UPDATE_POLICY_REFRESH_EAGER); mContext = context; - - mPackageMonitor.register(context, context.getMainLooper(), false); } @NonNull @@ -141,7 +139,8 @@ public final class CredentialManagerService serviceInfos.forEach( info -> { services.add( - new CredentialManagerServiceImpl(this, mLock, resolvedUserId, info)); + new CredentialManagerServiceImpl(this, mLock, resolvedUserId, + info)); }); return services; } @@ -217,8 +216,8 @@ public final class CredentialManagerService for (CredentialManagerServiceImpl serviceToBeRemoved : servicesToBeRemoved) { removeServiceFromCache(serviceToBeRemoved, userId); removeServiceFromSystemServicesCache(serviceToBeRemoved, userId); - removeServiceFromMultiModeSettings( - serviceToBeRemoved.getComponentName().flattenToString(), userId); + removeServiceFromMultiModeSettings(serviceToBeRemoved.getComponentName() + .flattenToString(), userId); CredentialDescriptionRegistry.forUser(userId) .evictProviderWithPackageName(serviceToBeRemoved.getServicePackageName()); } @@ -287,20 +286,13 @@ public final class CredentialManagerService } private static Set<ComponentName> getPrimaryProvidersForUserId(Context context, int userId) { - final int resolvedUserId = - ActivityManager.handleIncomingUser( - Binder.getCallingPid(), - Binder.getCallingUid(), - userId, - false, - false, - "getPrimaryProvidersForUserId", - null); - SecureSettingsServiceNameResolver resolver = - new SecureSettingsServiceNameResolver( - context, - Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, - /* isMultipleMode= */ true); + final int resolvedUserId = ActivityManager.handleIncomingUser( + Binder.getCallingPid(), Binder.getCallingUid(), + userId, false, false, + "getPrimaryProvidersForUserId", null); + SecureSettingsServiceNameResolver resolver = new SecureSettingsServiceNameResolver( + context, Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, + /* isMultipleMode= */ true); String[] serviceNames = resolver.readServiceNameList(resolvedUserId); if (serviceNames == null) { return new HashSet<ComponentName>(); @@ -337,8 +329,7 @@ public final class CredentialManagerService final long origId = Binder.clearCallingIdentity(); try { return DeviceConfig.getBoolean( - DeviceConfig.NAMESPACE_CREDENTIAL, - DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API, + DeviceConfig.NAMESPACE_CREDENTIAL, DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API, false); } finally { Binder.restoreCallingIdentity(origId); @@ -354,14 +345,13 @@ public final class CredentialManagerService List<ProviderSession> providerSessions = new ArrayList<>(); for (Pair<CredentialOption, CredentialDescriptionRegistry.FilterResult> result : activeCredentialContainers) { - ProviderSession providerSession = - ProviderRegistryGetSession.createNewSession( - mContext, - UserHandle.getCallingUserId(), - session, - session.mClientAppInfo, - result.second.mPackageName, - result.first); + ProviderSession providerSession = ProviderRegistryGetSession.createNewSession( + mContext, + UserHandle.getCallingUserId(), + session, + session.mClientAppInfo, + result.second.mPackageName, + result.first); providerSessions.add(providerSession); session.addProviderSession(providerSession.getComponentName(), providerSession); } @@ -377,23 +367,23 @@ public final class CredentialManagerService List<ProviderSession> providerSessions = new ArrayList<>(); for (Pair<CredentialOption, CredentialDescriptionRegistry.FilterResult> result : activeCredentialContainers) { - ProviderSession providerSession = - ProviderRegistryGetSession.createNewSession( - mContext, - UserHandle.getCallingUserId(), - session, - session.mClientAppInfo, - result.second.mPackageName, - result.first); + ProviderSession providerSession = ProviderRegistryGetSession.createNewSession( + mContext, + UserHandle.getCallingUserId(), + session, + session.mClientAppInfo, + result.second.mPackageName, + result.first); providerSessions.add(providerSession); session.addProviderSession(providerSession.getComponentName(), providerSession); } return providerSessions; } + @NonNull private Set<Pair<CredentialOption, CredentialDescriptionRegistry.FilterResult>> - getFilteredResultFromRegistry(List<CredentialOption> options) { + getFilteredResultFromRegistry(List<CredentialOption> options) { // Session for active/provisioned credential descriptions; CredentialDescriptionRegistry registry = CredentialDescriptionRegistry.forUser(UserHandle.getCallingUserId()); @@ -403,12 +393,10 @@ public final class CredentialManagerService options.stream() .map( getCredentialOption -> - new HashSet<>( - getCredentialOption - .getCredentialRetrievalData() - .getStringArrayList( - CredentialOption - .SUPPORTED_ELEMENT_KEYS))) + new HashSet<>(getCredentialOption + .getCredentialRetrievalData() + .getStringArrayList( + CredentialOption.SUPPORTED_ELEMENT_KEYS))) .collect(Collectors.toSet()); // All requested credential descriptions based on the given request. @@ -420,14 +408,12 @@ public final class CredentialManagerService for (CredentialDescriptionRegistry.FilterResult filterResult : filterResults) { for (CredentialOption credentialOption : options) { - Set<String> requestedElementKeys = - new HashSet<>( - credentialOption - .getCredentialRetrievalData() - .getStringArrayList( - CredentialOption.SUPPORTED_ELEMENT_KEYS)); - if (CredentialDescriptionRegistry.checkForMatch( - filterResult.mElementKeys, requestedElementKeys)) { + Set<String> requestedElementKeys = new HashSet<>( + credentialOption + .getCredentialRetrievalData() + .getStringArrayList(CredentialOption.SUPPORTED_ELEMENT_KEYS)); + if (CredentialDescriptionRegistry.checkForMatch(filterResult.mElementKeys, + requestedElementKeys)) { result.add(new Pair<>(credentialOption, filterResult)); } } @@ -463,7 +449,9 @@ public final class CredentialManagerService } private CallingAppInfo constructCallingAppInfo( - String realPackageName, int userId, @Nullable String origin) { + String realPackageName, + int userId, + @Nullable String origin) { final PackageInfo packageInfo; CallingAppInfo callingAppInfo; try { @@ -489,7 +477,8 @@ public final class CredentialManagerService GetCredentialRequest request, IGetCandidateCredentialsCallback callback, final String callingPackage) { - Slog.i(TAG, "starting getCandidateCredentials with callingPackage: " + callingPackage); + Slog.i(TAG, "starting getCandidateCredentials with callingPackage: " + + callingPackage); ICancellationSignal cancelTransport = CancellationSignal.createTransport(); final int userId = UserHandle.getCallingUserId(); @@ -507,7 +496,8 @@ public final class CredentialManagerService request, constructCallingAppInfo(callingPackage, userId, request.getOrigin()), getEnabledProvidersForUser(userId), - CancellationSignal.fromTransport(cancelTransport)); + CancellationSignal.fromTransport(cancelTransport) + ); addSessionLocked(userId, session); List<ProviderSession> providerSessions = @@ -541,7 +531,8 @@ public final class CredentialManagerService IGetCredentialCallback callback, final String callingPackage) { final long timestampBegan = System.nanoTime(); - Slog.i(TAG, "starting executeGetCredential with callingPackage: " + callingPackage); + Slog.i(TAG, "starting executeGetCredential with callingPackage: " + + callingPackage); ICancellationSignal cancelTransport = CancellationSignal.createTransport(); final int userId = UserHandle.getCallingUserId(); @@ -566,7 +557,8 @@ public final class CredentialManagerService timestampBegan); addSessionLocked(userId, session); - List<ProviderSession> providerSessions = prepareProviderSessions(request, session); + List<ProviderSession> providerSessions = + prepareProviderSessions(request, session); if (providerSessions.isEmpty()) { try { @@ -625,17 +617,15 @@ public final class CredentialManagerService if (providerSessions.isEmpty()) { try { prepareGetCredentialCallback.onResponse( - new PrepareGetCredentialResponseInternal( - PermissionUtils.hasPermission( - mContext, - callingPackage, - Manifest.permission - .CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS - ), - /* credentialResultTypes= */ null, - /* hasAuthenticationResults= */ false, - /* hasRemoteResults= */ false, - /* pendingIntent= */ null)); + new PrepareGetCredentialResponseInternal(PermissionUtils.hasPermission( + mContext, + callingPackage, + Manifest.permission + .CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS), + /*credentialResultTypes=*/null, + /*hasAuthenticationResults=*/false, + /*hasRemoteResults=*/false, + /*pendingIntent=*/null)); } catch (RemoteException e) { Slog.e( TAG, @@ -651,32 +641,27 @@ public final class CredentialManagerService } private List<ProviderSession> prepareProviderSessions( - GetCredentialRequest request, GetRequestSession session) { + GetCredentialRequest request, + GetRequestSession session) { List<ProviderSession> providerSessions; if (isCredentialDescriptionApiEnabled()) { List<CredentialOption> optionsThatRequireActiveCredentials = request.getCredentialOptions().stream() - .filter( - credentialOption -> - credentialOption - .getCredentialRetrievalData() - .getStringArrayList( - CredentialOption - .SUPPORTED_ELEMENT_KEYS) - != null) + .filter(credentialOption -> credentialOption + .getCredentialRetrievalData() + .getStringArrayList( + CredentialOption + .SUPPORTED_ELEMENT_KEYS) != null) .toList(); List<CredentialOption> optionsThatDoNotRequireActiveCredentials = request.getCredentialOptions().stream() - .filter( - credentialOption -> - credentialOption - .getCredentialRetrievalData() - .getStringArrayList( - CredentialOption - .SUPPORTED_ELEMENT_KEYS) - == null) + .filter(credentialOption -> credentialOption + .getCredentialRetrievalData() + .getStringArrayList( + CredentialOption + .SUPPORTED_ELEMENT_KEYS) == null) .toList(); List<ProviderSession> sessionsWithoutRemoteService = @@ -721,7 +706,8 @@ public final class CredentialManagerService ICreateCredentialCallback callback, String callingPackage) { final long timestampBegan = System.nanoTime(); - Slog.i(TAG, "starting executeCreateCredential with callingPackage: " + callingPackage); + Slog.i(TAG, "starting executeCreateCredential with callingPackage: " + + callingPackage); ICancellationSignal cancelTransport = CancellationSignal.createTransport(); if (request.getOrigin() != null) { @@ -770,8 +756,8 @@ public final class CredentialManagerService } catch (RemoteException e) { Slog.e( TAG, - "Issue invoking onError on ICreateCredentialCallback " + "callback: ", - e); + "Issue invoking onError on ICreateCredentialCallback " + + "callback: ", e); } } @@ -784,8 +770,8 @@ public final class CredentialManagerService try { var initMetric = session.mRequestSessionMetric.getInitialPhaseMetric(); initMetric.setCredentialServiceBeginQueryTimeNanoseconds(System.nanoTime()); - MetricUtilities.logApiCalledInitialPhase( - initMetric, session.mRequestSessionMetric.returnIncrementSequence()); + MetricUtilities.logApiCalledInitialPhase(initMetric, + session.mRequestSessionMetric.returnIncrementSequence()); } catch (Exception e) { Slog.i(TAG, "Unexpected error during metric logging: ", e); } @@ -793,32 +779,25 @@ public final class CredentialManagerService @Override public void setEnabledProviders( - List<String> primaryProviders, - List<String> providers, - int userId, + List<String> primaryProviders, List<String> providers, int userId, ISetEnabledProvidersCallback callback) { final int callingUid = Binder.getCallingUid(); if (!hasWriteSecureSettingsPermission()) { try { MetricUtilities.logApiCalledSimpleV2( - ApiName.SET_ENABLED_PROVIDERS, ApiStatus.FAILURE, callingUid); + ApiName.SET_ENABLED_PROVIDERS, + ApiStatus.FAILURE, callingUid); callback.onError( PERMISSION_DENIED_ERROR, PERMISSION_DENIED_WRITE_SECURE_SETTINGS_ERROR); } catch (RemoteException e) { MetricUtilities.logApiCalledSimpleV2( - ApiName.SET_ENABLED_PROVIDERS, ApiStatus.FAILURE, callingUid); + ApiName.SET_ENABLED_PROVIDERS, + ApiStatus.FAILURE, callingUid); Slog.e(TAG, "Issue with invoking response: ", e); } return; } - // If we don't have any primary providers enabled anymore then - // we should erase all the providers since the feature is - // now disabled. - if (primaryProviders.isEmpty()) { - providers.clear(); - } - userId = ActivityManager.handleIncomingUser( Binder.getCallingPid(), @@ -829,19 +808,17 @@ public final class CredentialManagerService "setEnabledProviders", null); - Set<String> enabledProviders = new HashSet<>(providers); - enabledProviders.addAll(primaryProviders); + Set<String> enableProvider = new HashSet<>(providers); + enableProvider.addAll(primaryProviders); boolean writeEnabledStatus = - Settings.Secure.putStringForUser( - getContext().getContentResolver(), + Settings.Secure.putStringForUser(getContext().getContentResolver(), Settings.Secure.CREDENTIAL_SERVICE, - String.join(":", enabledProviders), + String.join(":", enableProvider), userId); boolean writePrimaryStatus = - Settings.Secure.putStringForUser( - getContext().getContentResolver(), + Settings.Secure.putStringForUser(getContext().getContentResolver(), Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, String.join(":", primaryProviders), userId); @@ -850,13 +827,15 @@ public final class CredentialManagerService Slog.e(TAG, "Failed to store setting containing enabled or primary providers"); try { MetricUtilities.logApiCalledSimpleV2( - ApiName.SET_ENABLED_PROVIDERS, ApiStatus.FAILURE, callingUid); + ApiName.SET_ENABLED_PROVIDERS, + ApiStatus.FAILURE, callingUid); callback.onError( "failed_setting_store", "Failed to store setting containing enabled or primary providers"); } catch (RemoteException e) { MetricUtilities.logApiCalledSimpleV2( - ApiName.SET_ENABLED_PROVIDERS, ApiStatus.FAILURE, callingUid); + ApiName.SET_ENABLED_PROVIDERS, + ApiStatus.FAILURE, callingUid); Slog.e(TAG, "Issue with invoking error response: ", e); return; } @@ -865,11 +844,13 @@ public final class CredentialManagerService // Call the callback. try { MetricUtilities.logApiCalledSimpleV2( - ApiName.SET_ENABLED_PROVIDERS, ApiStatus.SUCCESS, callingUid); + ApiName.SET_ENABLED_PROVIDERS, + ApiStatus.SUCCESS, callingUid); callback.onResponse(); } catch (RemoteException e) { MetricUtilities.logApiCalledSimpleV2( - ApiName.SET_ENABLED_PROVIDERS, ApiStatus.FAILURE, callingUid); + ApiName.SET_ENABLED_PROVIDERS, + ApiStatus.FAILURE, callingUid); Slog.e(TAG, "Issue with invoking response: ", e); // TODO: Propagate failure } @@ -878,10 +859,8 @@ public final class CredentialManagerService @Override public boolean isEnabledCredentialProviderService( ComponentName componentName, String callingPackage) { - Slog.i( - TAG, - "isEnabledCredentialProviderService with componentName: " - + componentName.flattenToString()); + Slog.i(TAG, "isEnabledCredentialProviderService with componentName: " + + componentName.flattenToString()); // TODO(253157366): Check additional set of services. final int userId = UserHandle.getCallingUserId(); @@ -898,8 +877,7 @@ public final class CredentialManagerService // The component name and the package name do not match. MetricUtilities.logApiCalledSimpleV2( ApiName.IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE, - ApiStatus.FAILURE, - callingUid); + ApiStatus.FAILURE, callingUid); Slog.w( TAG, "isEnabledCredentialProviderService: Component name does " @@ -908,8 +886,7 @@ public final class CredentialManagerService } MetricUtilities.logApiCalledSimpleV2( ApiName.IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE, - ApiStatus.SUCCESS, - callingUid); + ApiStatus.SUCCESS, callingUid); return true; } } @@ -924,14 +901,13 @@ public final class CredentialManagerService verifyGetProvidersPermission(); final int callingUid = Binder.getCallingUid(); MetricUtilities.logApiCalledSimpleV2( - ApiName.GET_CREDENTIAL_PROVIDER_SERVICES, ApiStatus.SUCCESS, callingUid); + ApiName.GET_CREDENTIAL_PROVIDER_SERVICES, + ApiStatus.SUCCESS, callingUid); + return CredentialProviderInfoFactory + .getCredentialProviderServices( + mContext, userId, providerFilter, getEnabledProvidersForUser(userId), + getPrimaryProvidersForUserId(mContext, userId)); - return CredentialProviderInfoFactory.getCredentialProviderServices( - mContext, - userId, - providerFilter, - getEnabledProvidersForUser(userId), - getPrimaryProvidersForUserId(mContext, userId)); } @Override @@ -941,10 +917,7 @@ public final class CredentialManagerService final int userId = UserHandle.getCallingUserId(); return CredentialProviderInfoFactory.getCredentialProviderServicesForTesting( - mContext, - userId, - providerFilter, - getEnabledProvidersForUser(userId), + mContext, userId, providerFilter, getEnabledProvidersForUser(userId), getPrimaryProvidersForUserId(mContext, userId)); } @@ -962,22 +935,15 @@ public final class CredentialManagerService } private Set<ComponentName> getEnabledProvidersForUser(int userId) { - final int resolvedUserId = - ActivityManager.handleIncomingUser( - Binder.getCallingPid(), - Binder.getCallingUid(), - userId, - false, - false, - "getEnabledProvidersForUser", - null); + final int resolvedUserId = ActivityManager.handleIncomingUser( + Binder.getCallingPid(), Binder.getCallingUid(), + userId, false, false, + "getEnabledProvidersForUser", null); Set<ComponentName> enabledProviders = new HashSet<>(); - String directValue = - Settings.Secure.getStringForUser( - mContext.getContentResolver(), - Settings.Secure.CREDENTIAL_SERVICE, - resolvedUserId); + String directValue = Settings.Secure.getStringForUser( + mContext.getContentResolver(), Settings.Secure.CREDENTIAL_SERVICE, + resolvedUserId); if (!TextUtils.isEmpty(directValue)) { String[] components = directValue.split(":"); @@ -998,7 +964,8 @@ public final class CredentialManagerService IClearCredentialStateCallback callback, String callingPackage) { final long timestampBegan = System.nanoTime(); - Slog.i(TAG, "starting clearCredentialState with callingPackage: " + callingPackage); + Slog.i(TAG, "starting clearCredentialState with callingPackage: " + + callingPackage); final int userId = UserHandle.getCallingUserId(); int callingUid = Binder.getCallingUid(); enforceCallingPackage(callingPackage, callingUid); @@ -1029,13 +996,13 @@ public final class CredentialManagerService if (providerSessions.isEmpty()) { try { // TODO("Replace with properly defined error type") - callback.onError("UNKNOWN", "No credentials available on " + "this device"); + callback.onError("UNKNOWN", "No credentials available on " + + "this device"); } catch (RemoteException e) { Slog.e( TAG, "Issue invoking onError on IClearCredentialStateCallback " - + "callback: ", - e); + + "callback: ", e); } } @@ -1068,7 +1035,9 @@ public final class CredentialManagerService public void unregisterCredentialDescription( UnregisterCredentialDescriptionRequest request, String callingPackage) throws IllegalArgumentException { - Slog.i(TAG, "unregisterCredentialDescription with callingPackage: " + callingPackage); + Slog.i(TAG, "unregisterCredentialDescription with callingPackage: " + + callingPackage); + if (!isCredentialDescriptionApiEnabled()) { throw new UnsupportedOperationException("Feature not supported"); @@ -1092,18 +1061,18 @@ public final class CredentialManagerService } private void enforcePermissionForAllowedProviders(GetCredentialRequest request) { - boolean containsAllowedProviders = - request.getCredentialOptions().stream() - .anyMatch( - option -> - option.getAllowedProviders() != null - && !option.getAllowedProviders().isEmpty()); + boolean containsAllowedProviders = request.getCredentialOptions() + .stream() + .anyMatch(option -> option.getAllowedProviders() != null + && !option.getAllowedProviders().isEmpty()); if (containsAllowedProviders) { - mContext.enforceCallingPermission(CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS, null); + mContext.enforceCallingPermission(CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS, + null); } } - private void addSessionLocked(@UserIdInt int userId, RequestSession requestSession) { + private void addSessionLocked(@UserIdInt int userId, + RequestSession requestSession) { synchronized (mLock) { mSessionManager.addSession(userId, requestSession.mRequestId, requestSession); } @@ -1111,11 +1080,11 @@ public final class CredentialManagerService private void enforceCallingPackage(String callingPackage, int callingUid) { int packageUid; - PackageManager pm = - mContext.createContextAsUser(UserHandle.getUserHandleForUid(callingUid), 0) - .getPackageManager(); + PackageManager pm = mContext.createContextAsUser( + UserHandle.getUserHandleForUid(callingUid), 0).getPackageManager(); try { - packageUid = pm.getPackageUid(callingPackage, PackageManager.PackageInfoFlags.of(0)); + packageUid = pm.getPackageUid(callingPackage, + PackageManager.PackageInfoFlags.of(0)); } catch (PackageManager.NameNotFoundException e) { throw new SecurityException(callingPackage + " not found"); } @@ -1141,72 +1110,4 @@ public final class CredentialManagerService mRequestSessions.get(userId).put(token, requestSession); } } - - /** Updates settings when packages are removed. */ - private final PackageMonitor mPackageMonitor = - new PackageMonitor() { - - @Override - public void onPackageRemoved(String packageName, int uid) { - Slog.d(TAG, "onPackageRemoved: " + packageName); - - // Remove any providers from the primary setting that contain the package name - // being removed. - Set<String> primaryProviders = - getStoredProviders( - Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, packageName); - if (!Settings.Secure.putString( - getContext().getContentResolver(), - Settings.Secure.CREDENTIAL_SERVICE_PRIMARY, - String.join(":", primaryProviders))) { - Slog.w(TAG, "Failed to remove primary package: " + packageName); - return; - } - - // Get the secondary providers and if there are no primary providers then - // we should erase all the providers from the secondary list because the - // feature is now disabled. - if (!primaryProviders.isEmpty()) { - return; - } - - if (!Settings.Secure.putString( - getContext().getContentResolver(), - Settings.Secure.CREDENTIAL_SERVICE, - "")) { - Slog.w(TAG, "Failed to remove secondary package: " + packageName); - return; - } - } - - private Set<String> getStoredProviders(String key, String packageName) { - // Get the current providers. - String rawProviders = - Settings.Secure.getStringForUser( - getContext().getContentResolver(), key, - UserHandle.myUserId()); - if (rawProviders == null) { - Slog.w(TAG, "settings key is null: " + key); - return new HashSet<>(); - } - - // If the app being removed matches any of the package names from - // this list then don't add it in the output. - Set<String> providers = new HashSet<>(); - for (String rawComponentName : rawProviders.split(":")) { - if (TextUtils.isEmpty(rawComponentName) - || rawComponentName.equals("null")) { - Slog.d(TAG, "provider component name is empty or null"); - continue; - } - - ComponentName cn = ComponentName.unflattenFromString(rawComponentName); - if (cn != null && !cn.getPackageName().equals(packageName)) { - providers.add(cn.flattenToString()); - } - } - - return providers; - } - }; } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 43e47d7a45fc..49af89b5f02e 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -872,17 +872,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { "enable_permission_based_access"; private static final boolean DEFAULT_VALUE_PERMISSION_BASED_ACCESS_FLAG = false; - private static final String ENABLE_DEVICE_POLICY_ENGINE_FOR_FINANCE_FLAG = - "enable_device_policy_engine"; - private static final boolean DEFAULT_ENABLE_DEVICE_POLICY_ENGINE_FOR_FINANCE_FLAG = true; - // TODO(b/265683382) remove the flag after rollout. public static final boolean DEFAULT_KEEP_PROFILES_RUNNING_FLAG = false; - // TODO(b/261999445) remove the flag after rollout. - private static final String HEADLESS_FLAG = "headless"; - private static final boolean DEFAULT_HEADLESS_FLAG = true; - // TODO(b/266831522) remove the flag after rollout. private static final String APPLICATION_EXEMPTIONS_FLAG = "application_exemptions"; private static final boolean DEFAULT_APPLICATION_EXEMPTIONS_FLAG = true; @@ -4025,75 +4017,41 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } private void clearDeviceOwnerUserRestriction(UserHandle userHandle) { - if (isHeadlessFlagEnabled()) { - for (int userId : mUserManagerInternal.getUserIds()) { - UserHandle user = UserHandle.of(userId); - // ManagedProvisioning/DPC sets DISALLOW_ADD_USER. Clear to recover to the - // original state - if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER, user)) { - mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER, - false, user); - } - // When a device owner is set, the system automatically restricts adding a - // managed profile. - // Remove this restriction when the device owner is cleared. - if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, - user)) { - mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, - false, - user); - } - // When a device owner is set, the system automatically restricts adding a - // clone profile. - // Remove this restriction when the device owner is cleared. - if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE, user)) { - mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE, - false, user); - } - - // When a device owner is set, the system automatically restricts adding a - // private profile. - // Remove this restriction when the device owner is cleared. - if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE, - user)) { - mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE, - false, user); - } - } - } else { - // ManagedProvisioning/DPC sets DISALLOW_ADD_USER. Clear to recover to the original state - if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER, userHandle)) { - mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER, false, - userHandle); + for (int userId : mUserManagerInternal.getUserIds()) { + UserHandle user = UserHandle.of(userId); + // ManagedProvisioning/DPC sets DISALLOW_ADD_USER. Clear to recover to the + // original state + if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER, user)) { + mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER, + false, user); } // When a device owner is set, the system automatically restricts adding a // managed profile. // Remove this restriction when the device owner is cleared. if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, - userHandle)) { + user)) { mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, false, - userHandle); + user); } - // When a device owner is set, the system automatically restricts adding a clone - // profile. + // When a device owner is set, the system automatically restricts adding a + // clone profile. // Remove this restriction when the device owner is cleared. - if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE, - userHandle)) { + if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE, user)) { mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE, - false, - userHandle); + false, user); } // When a device owner is set, the system automatically restricts adding a // private profile. // Remove this restriction when the device owner is cleared. if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE, - userHandle)) { + user)) { mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE, - false, userHandle); + false, user); } } + } /** @@ -6476,7 +6434,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { KeyChain.bindAsUser(mContext, userHandle)) { IKeyChainService keyChain = keyChainConnection.getService(); return keyChain.setGrant(granteeUid, alias, hasGrant); - } catch (RemoteException e) { + } catch (RemoteException | AssertionError e) { Slogf.e(LOG_TAG, "Setting grant for package.", e); return false; } @@ -7956,14 +7914,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { hasCallingOrSelfPermission(permission.TRIGGER_LOST_MODE)); synchronized (getLockObject()) { - // TODO(b/261999445): Remove - ActiveAdmin admin; - if (isHeadlessFlagEnabled()) { - admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); - } else { - admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked( - UserHandle.USER_SYSTEM); - } + ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); + Preconditions.checkState(admin != null, "Lost mode location updates can only be sent on an organization-owned device."); mInjector.binderWithCleanCallingIdentity(() -> { @@ -9449,39 +9401,24 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // profile, such that the admin on that managed profile has extended management // capabilities that can affect the entire device (but not access private data // on the primary profile). - if (isHeadlessFlagEnabled()) { - for (int u : mUserManagerInternal.getUserIds()) { - mUserManager.setUserRestriction( - UserManager.DISALLOW_ADD_MANAGED_PROFILE, true, - UserHandle.of(u)); - // Restrict adding a clone profile when a device owner is set on the device. - // That is to prevent the co-existence of a clone profile and a device owner - // on the same device. - // CDD for reference : https://source.android.com/compatibility/12/android-12-cdd#95_multi-user_support - mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE, - true, - UserHandle.of(u)); - - // Restrict adding a private profile when a device owner is set. - mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE, - true, - UserHandle.of(u)); - } - } else { - mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, - true, - UserHandle.of(userId)); + for (int u : mUserManagerInternal.getUserIds()) { + mUserManager.setUserRestriction( + UserManager.DISALLOW_ADD_MANAGED_PROFILE, true, + UserHandle.of(u)); // Restrict adding a clone profile when a device owner is set on the device. // That is to prevent the co-existence of a clone profile and a device owner // on the same device. // CDD for reference : https://source.android.com/compatibility/12/android-12-cdd#95_multi-user_support mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE, true, - UserHandle.of(userId)); + UserHandle.of(u)); + + // Restrict adding a private profile when a device owner is set. mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE, true, - UserHandle.of(userId)); + UserHandle.of(u)); } + // TODO Send to system too? sendOwnerChangedBroadcast(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED, userId); }); @@ -20119,14 +20056,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { synchronized (getLockObject()) { // Only DO or COPE PO can turn on CC mode, so take a shortcut here and only look at // their ActiveAdmin, instead of iterating through all admins. - ActiveAdmin admin; - // TODO(b/261999445): remove - if (isHeadlessFlagEnabled()) { - admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); - } else { - admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked( - UserHandle.USER_SYSTEM); - } + ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); + return admin != null ? admin.mCommonCriteriaMode : false; } } @@ -21393,7 +21324,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } private void disallowAddUser() { - if (!isHeadlessFlagEnabled() || mIsAutomotive) { + if (mIsAutomotive) { // Auto still enables adding users due to the communal nature of those devices if (mInjector.userManagerIsHeadlessSystemUserMode()) { Slogf.i(LOG_TAG, "Not setting DISALLOW_ADD_USER on headless system user mode."); @@ -21711,14 +21642,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } private boolean isUsbDataSignalingEnabledInternalLocked() { - // TODO(b/261999445): remove - ActiveAdmin admin; - if (isHeadlessFlagEnabled()) { - admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); - } else { - admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked( - UserHandle.USER_SYSTEM); - } + ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); return admin == null || admin.mUsbDataSignalingEnabled; } @@ -21785,14 +21709,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public int getMinimumRequiredWifiSecurityLevel() { synchronized (getLockObject()) { - ActiveAdmin admin; - // TODO(b/261999445): remove - if (isHeadlessFlagEnabled()) { - admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); - } else { - admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked( - UserHandle.USER_SYSTEM); - } + ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); return (admin == null) ? DevicePolicyManager.WIFI_SECURITY_OPEN : admin.mWifiMinimumSecurityLevel; } @@ -23169,16 +23086,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { || isProfileOwnerOfOrganizationOwnedDevice(caller)); } synchronized (getLockObject()) { - // TODO(b/261999445): Remove - ActiveAdmin admin; - if (isHeadlessFlagEnabled()) { - admin = + ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); - } else { - admin = - getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked( - UserHandle.USER_SYSTEM); - } if (admin != null) { final String memtagProperty = "arm64.memtag.bootctl"; @@ -23211,29 +23120,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { || isSystemUid(caller)); } synchronized (getLockObject()) { - // TODO(b/261999445): Remove - ActiveAdmin admin; - if (isHeadlessFlagEnabled()) { - admin = + ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); - } else { - admin = - getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked( - UserHandle.USER_SYSTEM); - } return admin != null ? admin.mtePolicy : DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY; } } - private boolean isHeadlessFlagEnabled() { - return DeviceConfig.getBoolean( - NAMESPACE_DEVICE_POLICY_MANAGER, - HEADLESS_FLAG, - DEFAULT_HEADLESS_FLAG); - } - @Override public ManagedSubscriptionsPolicy getManagedSubscriptionsPolicy() { synchronized (getLockObject()) { diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 49ad84a8e6d9..59f1edcf309d 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -51,6 +51,7 @@ import android.credentials.CredentialManager; import android.database.sqlite.SQLiteCompatibilityWalFlags; import android.database.sqlite.SQLiteGlobal; import android.graphics.GraphicsStatsService; +import android.graphics.Typeface; import android.hardware.display.DisplayManagerInternal; import android.net.ConnectivityManager; import android.net.ConnectivityModuleConnector; @@ -916,6 +917,14 @@ public final class SystemServer implements Dumpable { SystemServerInitThreadPool tp = SystemServerInitThreadPool.start(); mDumper.addDumpable(tp); + // Lazily load the pre-installed system font map in SystemServer only if we're not doing + // the optimized font loading in the FontManagerService. + if (!com.android.text.flags.Flags.useOptimizedBoottimeFontLoading() + && Typeface.ENABLE_LAZY_TYPEFACE_INITIALIZATION) { + Slog.i(TAG, "Loading pre-installed system font map."); + Typeface.loadPreinstalledSystemFontMap(); + } + // Attach JVMTI agent if this is a debuggable build and the system property is set. if (Build.IS_DEBUGGABLE) { // Property is of the form "library_path=parameters". diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java index 486ddb4cb354..a8902fcf77af 100644 --- a/services/midi/java/com/android/server/midi/MidiService.java +++ b/services/midi/java/com/android/server/midi/MidiService.java @@ -1391,7 +1391,6 @@ public class MidiService extends IMidiManager.Stub { private static final String[] EMPTY_STRING_ARRAY = new String[0]; private void addLegacyPackageDeviceServer(ServiceInfo serviceInfo, int userId) { - Log.d(TAG, "addLegacyPackageDeviceServer()" + userId); XmlResourceParser parser = null; try { @@ -1529,7 +1528,6 @@ public class MidiService extends IMidiManager.Stub { @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS) private void addUmpPackageDeviceServer(ServiceInfo serviceInfo, int userId) { - Log.d(TAG, "addUmpPackageDeviceServer()" + userId); XmlResourceParser parser = null; try { diff --git a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt index c1d137f3a8bd..93530cf5b0a9 100644 --- a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt +++ b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt @@ -43,8 +43,7 @@ import kotlin.contracts.contract @Keep class AccessCheckingService(context: Context) : SystemService(context) { - @Volatile - private lateinit var state: AccessState + @Volatile private lateinit var state: AccessState private val stateLock = Any() private val policy = AccessPolicy() @@ -86,17 +85,22 @@ class AccessCheckingService(context: Context) : SystemService(context) { val state = MutableAccessState() policy.initialize( - state, userIds, packageStates, disabledSystemPackageStates, knownPackages, isLeanback, - configPermissions, privilegedPermissionAllowlistPackages, permissionAllowlist, + state, + userIds, + packageStates, + disabledSystemPackageStates, + knownPackages, + isLeanback, + configPermissions, + privilegedPermissionAllowlistPackages, + permissionAllowlist, implicitToSourcePermissions ) persistence.initialize() persistence.read(state) this.state = state - mutateState { - with(policy) { onInitialized() } - } + mutateState { with(policy) { onInitialized() } } appOpService.initialize() permissionService.initialize() @@ -106,40 +110,40 @@ class AccessCheckingService(context: Context) : SystemService(context) { get() = PackageManager.FEATURE_LEANBACK in availableFeatures private val SystemConfig.privilegedPermissionAllowlistPackages: IndexedListSet<String> - get() = MutableIndexedListSet<String>().apply { - this += "android" - if (PackageManager.FEATURE_AUTOMOTIVE in availableFeatures) { - // Note that SystemProperties.get(String, String) forces returning an empty string - // even if we pass null for the def parameter. - val carServicePackage = SystemProperties.get("ro.android.car.carservice.package") - if (carServicePackage.isNotEmpty()) { - this += carServicePackage + get() = + MutableIndexedListSet<String>().apply { + this += "android" + if (PackageManager.FEATURE_AUTOMOTIVE in availableFeatures) { + // Note that SystemProperties.get(String, String) forces returning an empty + // string + // even if we pass null for the def parameter. + val carServicePackage = + SystemProperties.get("ro.android.car.carservice.package") + if (carServicePackage.isNotEmpty()) { + this += carServicePackage + } } } - } private val SystemConfig.implicitToSourcePermissions: IndexedMap<String, IndexedListSet<String>> @Suppress("UNCHECKED_CAST") - get() = MutableIndexedMap<String, MutableIndexedListSet<String>>().apply { - splitPermissions.forEach { splitPermissionInfo -> - val sourcePermissionName = splitPermissionInfo.splitPermission - splitPermissionInfo.newPermissions.forEach { implicitPermissionName -> - getOrPut(implicitPermissionName) { MutableIndexedListSet() } += - sourcePermissionName + get() = + MutableIndexedMap<String, MutableIndexedListSet<String>>().apply { + splitPermissions.forEach { splitPermissionInfo -> + val sourcePermissionName = splitPermissionInfo.splitPermission + splitPermissionInfo.newPermissions.forEach { implicitPermissionName -> + getOrPut(implicitPermissionName) { MutableIndexedListSet() } += + sourcePermissionName + } } - } - } as IndexedMap<String, IndexedListSet<String>> + } as IndexedMap<String, IndexedListSet<String>> internal fun onUserAdded(userId: Int) { - mutateState { - with(policy) { onUserAdded(userId) } - } + mutateState { with(policy) { onUserAdded(userId) } } } internal fun onUserRemoved(userId: Int) { - mutateState { - with(policy) { onUserRemoved(userId) } - } + mutateState { with(policy) { onUserRemoved(userId) } } } internal fun onStorageVolumeMounted( @@ -152,8 +156,12 @@ class AccessCheckingService(context: Context) : SystemService(context) { mutateState { with(policy) { onStorageVolumeMounted( - packageStates, disabledSystemPackageStates, knownPackages, volumeUuid, - packageNames, isSystemUpdated + packageStates, + disabledSystemPackageStates, + knownPackages, + volumeUuid, + packageNames, + isSystemUpdated ) } } @@ -165,7 +173,10 @@ class AccessCheckingService(context: Context) : SystemService(context) { mutateState { with(policy) { onPackageAdded( - packageStates, disabledSystemPackageStates, knownPackages, packageName + packageStates, + disabledSystemPackageStates, + knownPackages, + packageName ) } } @@ -177,7 +188,11 @@ class AccessCheckingService(context: Context) : SystemService(context) { mutateState { with(policy) { onPackageRemoved( - packageStates, disabledSystemPackageStates, knownPackages, packageName, appId + packageStates, + disabledSystemPackageStates, + knownPackages, + packageName, + appId ) } } @@ -189,7 +204,11 @@ class AccessCheckingService(context: Context) : SystemService(context) { mutateState { with(policy) { onPackageInstalled( - packageStates, disabledSystemPackageStates, knownPackages, packageName, userId + packageStates, + disabledSystemPackageStates, + knownPackages, + packageName, + userId ) } } @@ -201,7 +220,11 @@ class AccessCheckingService(context: Context) : SystemService(context) { mutateState { with(policy) { onPackageUninstalled( - packageStates, disabledSystemPackageStates, knownPackages, packageName, appId, + packageStates, + disabledSystemPackageStates, + knownPackages, + packageName, + appId, userId ) } @@ -224,34 +247,42 @@ class AccessCheckingService(context: Context) : SystemService(context) { private fun PackageManagerInternal.getKnownPackages( packageStates: Map<String, PackageState> - ): IntMap<Array<String>> = MutableIntMap<Array<String>>().apply { - this[KnownPackages.PACKAGE_INSTALLER] = - getKnownPackageNames(KnownPackages.PACKAGE_INSTALLER, UserHandle.USER_SYSTEM) - this[KnownPackages.PACKAGE_PERMISSION_CONTROLLER] = getKnownPackageNames( - KnownPackages.PACKAGE_PERMISSION_CONTROLLER, UserHandle.USER_SYSTEM - ) - this[KnownPackages.PACKAGE_VERIFIER] = - getKnownPackageNames(KnownPackages.PACKAGE_VERIFIER, UserHandle.USER_SYSTEM) - this[KnownPackages.PACKAGE_SETUP_WIZARD] = - getKnownPackageNames(KnownPackages.PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM) - this[KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER] = getKnownPackageNames( - KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER, UserHandle.USER_SYSTEM - ) - this[KnownPackages.PACKAGE_CONFIGURATOR] = - getKnownPackageNames(KnownPackages.PACKAGE_CONFIGURATOR, UserHandle.USER_SYSTEM) - this[KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER] = getKnownPackageNames( - KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER, UserHandle.USER_SYSTEM - ) - this[KnownPackages.PACKAGE_APP_PREDICTOR] = - getKnownPackageNames(KnownPackages.PACKAGE_APP_PREDICTOR, UserHandle.USER_SYSTEM) - this[KnownPackages.PACKAGE_COMPANION] = - getKnownPackageNames(KnownPackages.PACKAGE_COMPANION, UserHandle.USER_SYSTEM) - this[KnownPackages.PACKAGE_RETAIL_DEMO] = - getKnownPackageNames(KnownPackages.PACKAGE_RETAIL_DEMO, UserHandle.USER_SYSTEM) - .filter { isProfileOwner(it, packageStates) }.toTypedArray() - this[KnownPackages.PACKAGE_RECENTS] = - getKnownPackageNames(KnownPackages.PACKAGE_RECENTS, UserHandle.USER_SYSTEM) - } + ): IntMap<Array<String>> = + MutableIntMap<Array<String>>().apply { + this[KnownPackages.PACKAGE_INSTALLER] = + getKnownPackageNames(KnownPackages.PACKAGE_INSTALLER, UserHandle.USER_SYSTEM) + this[KnownPackages.PACKAGE_PERMISSION_CONTROLLER] = + getKnownPackageNames( + KnownPackages.PACKAGE_PERMISSION_CONTROLLER, + UserHandle.USER_SYSTEM + ) + this[KnownPackages.PACKAGE_VERIFIER] = + getKnownPackageNames(KnownPackages.PACKAGE_VERIFIER, UserHandle.USER_SYSTEM) + this[KnownPackages.PACKAGE_SETUP_WIZARD] = + getKnownPackageNames(KnownPackages.PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM) + this[KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER] = + getKnownPackageNames( + KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER, + UserHandle.USER_SYSTEM + ) + this[KnownPackages.PACKAGE_CONFIGURATOR] = + getKnownPackageNames(KnownPackages.PACKAGE_CONFIGURATOR, UserHandle.USER_SYSTEM) + this[KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER] = + getKnownPackageNames( + KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER, + UserHandle.USER_SYSTEM + ) + this[KnownPackages.PACKAGE_APP_PREDICTOR] = + getKnownPackageNames(KnownPackages.PACKAGE_APP_PREDICTOR, UserHandle.USER_SYSTEM) + this[KnownPackages.PACKAGE_COMPANION] = + getKnownPackageNames(KnownPackages.PACKAGE_COMPANION, UserHandle.USER_SYSTEM) + this[KnownPackages.PACKAGE_RETAIL_DEMO] = + getKnownPackageNames(KnownPackages.PACKAGE_RETAIL_DEMO, UserHandle.USER_SYSTEM) + .filter { isProfileOwner(it, packageStates) } + .toTypedArray() + this[KnownPackages.PACKAGE_RECENTS] = + getKnownPackageNames(KnownPackages.PACKAGE_RECENTS, UserHandle.USER_SYSTEM) + } private fun isProfileOwner( packageName: String, diff --git a/services/permission/java/com/android/server/permission/access/AccessPersistence.kt b/services/permission/java/com/android/server/permission/access/AccessPersistence.kt index a3f65af6bc82..d0913d29f504 100644 --- a/services/permission/java/com/android/server/permission/access/AccessPersistence.kt +++ b/services/permission/java/com/android/server/permission/access/AccessPersistence.kt @@ -38,16 +38,11 @@ import com.android.server.permission.access.util.writeWithReserveCopy import java.io.File import java.io.FileNotFoundException -class AccessPersistence( - private val policy: AccessPolicy -) { +class AccessPersistence(private val policy: AccessPolicy) { private val scheduleLock = Any() - @GuardedBy("scheduleLock") - private val pendingMutationTimesMillis = SparseLongArray() - @GuardedBy("scheduleLock") - private val pendingStates = MutableIntMap<AccessState>() - @GuardedBy("scheduleLock") - private lateinit var writeHandler: WriteHandler + @GuardedBy("scheduleLock") private val pendingMutationTimesMillis = SparseLongArray() + @GuardedBy("scheduleLock") private val pendingStates = MutableIntMap<AccessState>() + @GuardedBy("scheduleLock") private lateinit var writeHandler: WriteHandler private val writeLock = Any() @@ -60,17 +55,16 @@ class AccessPersistence( */ fun read(state: MutableAccessState) { readSystemState(state) - state.externalState.userIds.forEachIndexed { _, userId -> - readUserState(state, userId) - } + state.externalState.userIds.forEachIndexed { _, userId -> readUserState(state, userId) } } private fun readSystemState(state: MutableAccessState) { - val fileExists = systemFile.parse { - // This is the canonical way to call an extension function in a different class. - // TODO(b/259469752): Use context receiver for this when it becomes stable. - with(policy) { parseSystemState(state) } - } + val fileExists = + systemFile.parse { + // This is the canonical way to call an extension function in a different class. + // TODO(b/259469752): Use context receiver for this when it becomes stable. + with(policy) { parseSystemState(state) } + } if (!fileExists) { policy.migrateSystemState(state) @@ -79,9 +73,8 @@ class AccessPersistence( } private fun readUserState(state: MutableAccessState, userId: Int) { - val fileExists = getUserFile(userId).parse { - with(policy) { parseUserState(state, userId) } - } + val fileExists = + getUserFile(userId).parse { with(policy) { parseUserState(state, userId) } } if (!fileExists) { policy.migrateUserState(state, userId) @@ -90,8 +83,8 @@ class AccessPersistence( } /** - * @return {@code true} if the file is successfully read from the disk; {@code false} if - * the file doesn't exist yet. + * @return {@code true} if the file is successfully read from the disk; {@code false} if the + * file doesn't exist yet. */ private inline fun File.parse(block: BinaryXmlPullParser.() -> Unit): Boolean = try { @@ -106,9 +99,7 @@ class AccessPersistence( fun write(state: AccessState) { state.systemState.write(state, UserHandle.USER_ALL) - state.userStates.forEachIndexed { _, userId, userState -> - userState.write(state, userId) - } + state.userStates.forEachIndexed { _, userId, userState -> userState.write(state, userId) } } private fun WritableState.write(state: AccessState, userId: Int) { @@ -127,8 +118,10 @@ class AccessPersistence( if (currentDelayMillis > MAX_WRITE_DELAY_MILLIS) { message.sendToTarget() } else { - val newDelayMillis = WRITE_DELAY_TIME_MILLIS - .coerceAtMost(MAX_WRITE_DELAY_MILLIS - currentDelayMillis) + val newDelayMillis = + WRITE_DELAY_TIME_MILLIS.coerceAtMost( + MAX_WRITE_DELAY_MILLIS - currentDelayMillis + ) writeHandler.sendMessageDelayed(message, newDelayMillis) } } @@ -161,15 +154,11 @@ class AccessPersistence( } private fun writeSystemState(state: AccessState) { - systemFile.serialize { - with(policy) { serializeSystemState(state) } - } + systemFile.serialize { with(policy) { serializeSystemState(state) } } } private fun writeUserState(state: AccessState, userId: Int) { - getUserFile(userId).serialize { - with(policy) { serializeUserState(state, userId) } - } + getUserFile(userId).serialize { with(policy) { serializeUserState(state, userId) } } } private inline fun File.serialize(block: BinaryXmlSerializer.() -> Unit) { diff --git a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt index 6a349e237ffe..754f77ec38f9 100644 --- a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt @@ -37,21 +37,24 @@ import com.android.server.permission.access.util.tagName import com.android.server.pm.permission.PermissionAllowlist import com.android.server.pm.pkg.PackageState -class AccessPolicy private constructor( +class AccessPolicy +private constructor( private val schemePolicies: IndexedMap<String, IndexedMap<String, SchemePolicy>> ) { @Suppress("UNCHECKED_CAST") - constructor() : this( - MutableIndexedMap<String, MutableIndexedMap<String, SchemePolicy>>().apply { - fun addPolicy(policy: SchemePolicy) { - getOrPut(policy.subjectScheme) { MutableIndexedMap() }[policy.objectScheme] = policy - } - addPolicy(AppIdPermissionPolicy()) - addPolicy(DevicePermissionPolicy()) - addPolicy(AppIdAppOpPolicy()) - addPolicy(PackageAppOpPolicy()) - } as IndexedMap<String, IndexedMap<String, SchemePolicy>> - ) + constructor() : + this( + MutableIndexedMap<String, MutableIndexedMap<String, SchemePolicy>>().apply { + fun addPolicy(policy: SchemePolicy) { + getOrPut(policy.subjectScheme) { MutableIndexedMap() }[policy.objectScheme] = + policy + } + addPolicy(AppIdPermissionPolicy()) + addPolicy(DevicePermissionPolicy()) + addPolicy(AppIdAppOpPolicy()) + addPolicy(PackageAppOpPolicy()) + } as IndexedMap<String, IndexedMap<String, SchemePolicy>> + ) fun getSchemePolicy(subjectScheme: String, objectScheme: String): SchemePolicy = checkNotNull(schemePolicies[subjectScheme]?.get(objectScheme)) { @@ -92,23 +95,17 @@ class AccessPolicy private constructor( } fun GetStateScope.onStateMutated() { - forEachSchemePolicy { - with(it) { onStateMutated() } - } + forEachSchemePolicy { with(it) { onStateMutated() } } } fun MutateStateScope.onInitialized() { - forEachSchemePolicy { - with(it) { onInitialized() } - } + forEachSchemePolicy { with(it) { onInitialized() } } } fun MutateStateScope.onUserAdded(userId: Int) { newState.mutateExternalState().mutateUserIds() += userId newState.mutateUserStatesNoWrite()[userId] = MutableUserState() - forEachSchemePolicy { - with(it) { onUserAdded(userId) } - } + forEachSchemePolicy { with(it) { onUserAdded(userId) } } newState.externalState.packageStates.forEach { (_, packageState) -> upgradePackageVersion(packageState, userId) } @@ -117,9 +114,7 @@ class AccessPolicy private constructor( fun MutateStateScope.onUserRemoved(userId: Int) { newState.mutateExternalState().mutateUserIds() -= userId newState.mutateUserStatesNoWrite() -= userId - forEachSchemePolicy { - with(it) { onUserRemoved(userId) } - } + forEachSchemePolicy { with(it) { onUserRemoved(userId) } } } fun MutateStateScope.onStorageVolumeMounted( @@ -154,9 +149,7 @@ class AccessPolicy private constructor( setKnownPackages(knownPackages) } addedAppIds.forEachIndexed { _, appId -> - forEachSchemePolicy { - with(it) { onAppIdAdded(appId) } - } + forEachSchemePolicy { with(it) { onAppIdAdded(appId) } } } forEachSchemePolicy { with(it) { onStorageVolumeMounted(volumeUuid, packageNames, isSystemUpdated) } @@ -192,13 +185,9 @@ class AccessPolicy private constructor( setKnownPackages(knownPackages) } if (isAppIdAdded) { - forEachSchemePolicy { - with(it) { onAppIdAdded(appId) } - } - } - forEachSchemePolicy { - with(it) { onPackageAdded(packageState) } + forEachSchemePolicy { with(it) { onAppIdAdded(appId) } } } + forEachSchemePolicy { with(it) { onPackageAdded(packageState) } } newState.userStates.forEachIndexed { _, userId, _ -> upgradePackageVersion(packageState, userId) } @@ -227,13 +216,9 @@ class AccessPolicy private constructor( } setKnownPackages(knownPackages) } - forEachSchemePolicy { - with(it) { onPackageRemoved(packageName, appId) } - } + forEachSchemePolicy { with(it) { onPackageRemoved(packageName, appId) } } if (isAppIdRemoved) { - forEachSchemePolicy { - with(it) { onAppIdRemoved(appId) } - } + forEachSchemePolicy { with(it) { onAppIdRemoved(appId) } } } newState.userStates.forEachIndexed { userStateIndex, _, userState -> if (packageName in userState.packageVersions) { @@ -258,9 +243,7 @@ class AccessPolicy private constructor( checkNotNull(packageState) { "Installed package $packageName isn't found in packageStates in onPackageInstalled()" } - forEachSchemePolicy { - with(it) { onPackageInstalled(packageState, userId) } - } + forEachSchemePolicy { with(it) { onPackageInstalled(packageState, userId) } } } fun MutateStateScope.onPackageUninstalled( @@ -276,9 +259,7 @@ class AccessPolicy private constructor( setDisabledSystemPackageStates(disabledSystemPackageStates) setKnownPackages(knownPackages) } - forEachSchemePolicy { - with(it) { onPackageUninstalled(packageName, appId, userId) } - } + forEachSchemePolicy { with(it) { onPackageUninstalled(packageName, appId, userId) } } } fun MutateStateScope.onSystemReady( @@ -292,21 +273,15 @@ class AccessPolicy private constructor( setKnownPackages(knownPackages) setSystemReady(true) } - forEachSchemePolicy { - with(it) { onSystemReady() } - } + forEachSchemePolicy { with(it) { onSystemReady() } } } fun migrateSystemState(state: MutableAccessState) { - forEachSchemePolicy { - with(it) { migrateSystemState(state) } - } + forEachSchemePolicy { with(it) { migrateSystemState(state) } } } fun migrateUserState(state: MutableAccessState, userId: Int) { - forEachSchemePolicy { - with(it) { migrateUserState(state, userId) } - } + forEachSchemePolicy { with(it) { migrateUserState(state, userId) } } } private fun MutateStateScope.upgradePackageVersion(packageState: PackageState, userId: Int) { @@ -330,10 +305,12 @@ class AccessPolicy private constructor( VERSION_LATEST } version == VERSION_LATEST -> {} - else -> Slog.w( - LOG_TAG, "Unexpected version $version for package $packageName," + - "latest version is $VERSION_LATEST" - ) + else -> + Slog.w( + LOG_TAG, + "Unexpected version $version for package $packageName," + + "latest version is $VERSION_LATEST" + ) } } @@ -341,11 +318,7 @@ class AccessPolicy private constructor( forEachTag { when (tagName) { TAG_ACCESS -> { - forEachTag { - forEachSchemePolicy { - with(it) { parseSystemState(state) } - } - } + forEachTag { forEachSchemePolicy { with(it) { parseSystemState(state) } } } } else -> Slog.w(LOG_TAG, "Ignoring unknown tag $tagName when parsing system state") } @@ -353,11 +326,7 @@ class AccessPolicy private constructor( } fun BinaryXmlSerializer.serializeSystemState(state: AccessState) { - tag(TAG_ACCESS) { - forEachSchemePolicy { - with(it) { serializeSystemState(state) } - } - } + tag(TAG_ACCESS) { forEachSchemePolicy { with(it) { serializeSystemState(state) } } } } fun BinaryXmlPullParser.parseUserState(state: MutableAccessState, userId: Int) { @@ -370,9 +339,7 @@ class AccessPolicy private constructor( TAG_DEFAULT_PERMISSION_GRANT -> parseDefaultPermissionGrant(state, userId) else -> { - forEachSchemePolicy { - with(it) { parseUserState(state, userId) } - } + forEachSchemePolicy { with(it) { parseUserState(state, userId) } } } } } @@ -428,9 +395,7 @@ class AccessPolicy private constructor( serializeDefaultPermissionGrantFingerprint( state.userStates[userId]!!.defaultPermissionGrantFingerprint ) - forEachSchemePolicy { - with(it) { serializeUserState(state, userId) } - } + forEachSchemePolicy { with(it) { serializeUserState(state, userId) } } } } @@ -451,9 +416,7 @@ class AccessPolicy private constructor( fingerprint: String? ) { if (fingerprint != null) { - tag(TAG_DEFAULT_PERMISSION_GRANT) { - attributeInterned(ATTR_FINGERPRINT, fingerprint) - } + tag(TAG_DEFAULT_PERMISSION_GRANT) { attributeInterned(ATTR_FINGERPRINT, fingerprint) } } } @@ -462,9 +425,7 @@ class AccessPolicy private constructor( private inline fun forEachSchemePolicy(action: (SchemePolicy) -> Unit) { schemePolicies.forEachIndexed { _, _, objectSchemePolicies -> - objectSchemePolicies.forEachIndexed { _, _, schemePolicy -> - action(schemePolicy) - } + objectSchemePolicies.forEachIndexed { _, _, schemePolicy -> action(schemePolicy) } } } diff --git a/services/permission/java/com/android/server/permission/access/AccessState.kt b/services/permission/java/com/android/server/permission/access/AccessState.kt index 94c878a453c9..49d2f813c487 100644 --- a/services/permission/java/com/android/server/permission/access/AccessState.kt +++ b/services/permission/java/com/android/server/permission/access/AccessState.kt @@ -28,7 +28,9 @@ private typealias ExternalStateReference = MutableReference<ExternalState, Mutab private typealias SystemStateReference = MutableReference<SystemState, MutableSystemState> typealias UserStates = IntReferenceMap<UserState, MutableUserState> + typealias MutableUserStates = MutableIntReferenceMap<UserState, MutableUserState> + private typealias UserStatesReference = MutableReference<UserStates, MutableUserStates> sealed class AccessState( @@ -48,22 +50,22 @@ sealed class AccessState( override fun toMutable(): MutableAccessState = MutableAccessState(this) } -class MutableAccessState private constructor( +class MutableAccessState +private constructor( externalStateReference: ExternalStateReference, systemStateReference: SystemStateReference, userStatesReference: UserStatesReference -) : AccessState( - externalStateReference, - systemStateReference, - userStatesReference -) { - constructor() : this( - ExternalStateReference(MutableExternalState()), - SystemStateReference(MutableSystemState()), - UserStatesReference(MutableUserStates()) - ) - - internal constructor(accessState: AccessState) : this( +) : AccessState(externalStateReference, systemStateReference, userStatesReference) { + constructor() : + this( + ExternalStateReference(MutableExternalState()), + SystemStateReference(MutableSystemState()), + UserStatesReference(MutableUserStates()) + ) + + internal constructor( + accessState: AccessState + ) : this( accessState.externalStateReference.toImmutable(), accessState.systemStateReference.toImmutable(), accessState.userStatesReference.toImmutable() @@ -86,8 +88,10 @@ class MutableAccessState private constructor( private typealias UserIdsReference = MutableReference<IntSet, MutableIntSet> typealias AppIdPackageNames = IntReferenceMap<IndexedListSet<String>, MutableIndexedListSet<String>> + typealias MutableAppIdPackageNames = MutableIntReferenceMap<IndexedListSet<String>, MutableIndexedListSet<String>> + private typealias AppIdPackageNamesReference = MutableReference<AppIdPackageNames, MutableAppIdPackageNames> @@ -142,7 +146,8 @@ sealed class ExternalState( override fun toMutable(): MutableExternalState = MutableExternalState(this) } -class MutableExternalState private constructor( +class MutableExternalState +private constructor( userIdsReference: UserIdsReference, packageStates: Map<String, PackageState>, disabledSystemPackageStates: Map<String, PackageState>, @@ -154,34 +159,38 @@ class MutableExternalState private constructor( permissionAllowlist: PermissionAllowlist, implicitToSourcePermissions: IndexedMap<String, IndexedListSet<String>>, isSystemReady: Boolean -) : ExternalState( - userIdsReference, - packageStates, - disabledSystemPackageStates, - appIdPackageNamesReference, - knownPackages, - isLeanback, - configPermissions, - privilegedPermissionAllowlistPackages, - permissionAllowlist, - implicitToSourcePermissions, - isSystemReady -) { - constructor() : this( - UserIdsReference(MutableIntSet()), - emptyMap(), - emptyMap(), - AppIdPackageNamesReference(MutableAppIdPackageNames()), - MutableIntMap(), - false, - emptyMap(), - MutableIndexedListSet(), - PermissionAllowlist(), - MutableIndexedMap(), - false - ) - - internal constructor(externalState: ExternalState) : this( +) : + ExternalState( + userIdsReference, + packageStates, + disabledSystemPackageStates, + appIdPackageNamesReference, + knownPackages, + isLeanback, + configPermissions, + privilegedPermissionAllowlistPackages, + permissionAllowlist, + implicitToSourcePermissions, + isSystemReady + ) { + constructor() : + this( + UserIdsReference(MutableIntSet()), + emptyMap(), + emptyMap(), + AppIdPackageNamesReference(MutableAppIdPackageNames()), + MutableIntMap(), + false, + emptyMap(), + MutableIndexedListSet(), + PermissionAllowlist(), + MutableIndexedMap(), + false + ) + + internal constructor( + externalState: ExternalState + ) : this( externalState.userIdsReference.toImmutable(), externalState.packageStates, externalState.disabledSystemPackageStates, @@ -249,9 +258,10 @@ class MutableExternalState private constructor( } } -private typealias PermissionGroupsReference = MutableReference< - IndexedMap<String, PermissionGroupInfo>, MutableIndexedMap<String, PermissionGroupInfo> -> +private typealias PermissionGroupsReference = + MutableReference< + IndexedMap<String, PermissionGroupInfo>, MutableIndexedMap<String, PermissionGroupInfo> + > private typealias PermissionTreesReference = MutableReference<IndexedMap<String, Permission>, MutableIndexedMap<String, Permission>> @@ -280,25 +290,31 @@ sealed class SystemState( override fun toMutable(): MutableSystemState = MutableSystemState(this) } -class MutableSystemState private constructor( +class MutableSystemState +private constructor( permissionGroupsReference: PermissionGroupsReference, permissionTreesReference: PermissionTreesReference, permissionsReference: PermissionsReference, writeMode: Int -) : SystemState( - permissionGroupsReference, - permissionTreesReference, - permissionsReference, - writeMode -), MutableWritableState { - constructor() : this( - PermissionGroupsReference(MutableIndexedMap()), - PermissionTreesReference(MutableIndexedMap()), - PermissionsReference(MutableIndexedMap()), - WriteMode.NONE - ) - - internal constructor(systemState: SystemState) : this( +) : + SystemState( + permissionGroupsReference, + permissionTreesReference, + permissionsReference, + writeMode + ), + MutableWritableState { + constructor() : + this( + PermissionGroupsReference(MutableIndexedMap()), + PermissionTreesReference(MutableIndexedMap()), + PermissionsReference(MutableIndexedMap()), + WriteMode.NONE + ) + + internal constructor( + systemState: SystemState + ) : this( systemState.permissionGroupsReference.toImmutable(), systemState.permissionTreesReference.toImmutable(), systemState.permissionsReference.toImmutable(), @@ -311,8 +327,7 @@ class MutableSystemState private constructor( fun mutatePermissionTrees(): MutableIndexedMap<String, Permission> = permissionTreesReference.mutate() - fun mutatePermissions(): MutableIndexedMap<String, Permission> = - permissionsReference.mutate() + fun mutatePermissions(): MutableIndexedMap<String, Permission> = permissionsReference.mutate() override fun requestWriteMode(writeMode: Int) { this.writeMode = maxOf(this.writeMode, writeMode) @@ -324,34 +339,42 @@ private typealias PackageVersionsReference = typealias AppIdPermissionFlags = IntReferenceMap<IndexedMap<String, Int>, MutableIndexedMap<String, Int>> + typealias MutableAppIdPermissionFlags = MutableIntReferenceMap<IndexedMap<String, Int>, MutableIndexedMap<String, Int>> + private typealias AppIdPermissionFlagsReference = MutableReference<AppIdPermissionFlags, MutableAppIdPermissionFlags> - typealias DevicePermissionFlags = IndexedReferenceMap<String, IndexedMap<String, Int>, MutableIndexedMap<String, Int>> + typealias MutableDevicePermissionFlags = MutableIndexedReferenceMap<String, IndexedMap<String, Int>, MutableIndexedMap<String, Int>> + typealias AppIdDevicePermissionFlags = IntReferenceMap<DevicePermissionFlags, MutableDevicePermissionFlags> + typealias MutableAppIdDevicePermissionFlags = MutableIntReferenceMap<DevicePermissionFlags, MutableDevicePermissionFlags> + private typealias AppIdDevicePermissionFlagsReference = MutableReference<AppIdDevicePermissionFlags, MutableAppIdDevicePermissionFlags> -typealias AppIdAppOpModes = - IntReferenceMap<IndexedMap<String, Int>, MutableIndexedMap<String, Int>> +typealias AppIdAppOpModes = IntReferenceMap<IndexedMap<String, Int>, MutableIndexedMap<String, Int>> + typealias MutableAppIdAppOpModes = MutableIntReferenceMap<IndexedMap<String, Int>, MutableIndexedMap<String, Int>> + private typealias AppIdAppOpModesReference = MutableReference<AppIdAppOpModes, MutableAppIdAppOpModes> typealias PackageAppOpModes = IndexedReferenceMap<String, IndexedMap<String, Int>, MutableIndexedMap<String, Int>> + typealias MutablePackageAppOpModes = MutableIndexedReferenceMap<String, IndexedMap<String, Int>, MutableIndexedMap<String, Int>> + private typealias PackageAppOpModesReference = MutableReference<PackageAppOpModes, MutablePackageAppOpModes> @@ -388,7 +411,8 @@ sealed class UserState( override fun toMutable(): MutableUserState = MutableUserState(this) } -class MutableUserState private constructor( +class MutableUserState +private constructor( packageVersionsReference: PackageVersionsReference, appIdPermissionFlagsReference: AppIdPermissionFlagsReference, appIdDevicePermissionFlagsReference: AppIdDevicePermissionFlagsReference, @@ -396,26 +420,31 @@ class MutableUserState private constructor( packageAppOpModesReference: PackageAppOpModesReference, defaultPermissionGrantFingerprint: String?, writeMode: Int -) : UserState( - packageVersionsReference, - appIdPermissionFlagsReference, - appIdDevicePermissionFlagsReference, - appIdAppOpModesReference, - packageAppOpModesReference, - defaultPermissionGrantFingerprint, - writeMode -), MutableWritableState { - constructor() : this( - PackageVersionsReference(MutableIndexedMap<String, Int>()), - AppIdPermissionFlagsReference(MutableAppIdPermissionFlags()), - AppIdDevicePermissionFlagsReference(MutableAppIdDevicePermissionFlags()), - AppIdAppOpModesReference(MutableAppIdAppOpModes()), - PackageAppOpModesReference(MutablePackageAppOpModes()), - null, - WriteMode.NONE - ) - - internal constructor(userState: UserState) : this( +) : + UserState( + packageVersionsReference, + appIdPermissionFlagsReference, + appIdDevicePermissionFlagsReference, + appIdAppOpModesReference, + packageAppOpModesReference, + defaultPermissionGrantFingerprint, + writeMode + ), + MutableWritableState { + constructor() : + this( + PackageVersionsReference(MutableIndexedMap<String, Int>()), + AppIdPermissionFlagsReference(MutableAppIdPermissionFlags()), + AppIdDevicePermissionFlagsReference(MutableAppIdDevicePermissionFlags()), + AppIdAppOpModesReference(MutableAppIdAppOpModes()), + PackageAppOpModesReference(MutablePackageAppOpModes()), + null, + WriteMode.NONE + ) + + internal constructor( + userState: UserState + ) : this( userState.packageVersionsReference.toImmutable(), userState.appIdPermissionFlagsReference.toImmutable(), userState.appIdDevicePermissionFlagsReference.toImmutable(), @@ -461,11 +490,7 @@ interface MutableWritableState : WritableState { fun requestWriteMode(writeMode: Int) } -open class GetStateScope( - val state: AccessState -) +open class GetStateScope(val state: AccessState) -class MutateStateScope( - val oldState: AccessState, - val newState: MutableAccessState -) : GetStateScope(newState) +class MutateStateScope(val oldState: AccessState, val newState: MutableAccessState) : + GetStateScope(newState) diff --git a/services/permission/java/com/android/server/permission/access/AccessUri.kt b/services/permission/java/com/android/server/permission/access/AccessUri.kt index 1d46ca71fe0f..1f5a4df33cc9 100644 --- a/services/permission/java/com/android/server/permission/access/AccessUri.kt +++ b/services/permission/java/com/android/server/permission/access/AccessUri.kt @@ -18,9 +18,7 @@ package com.android.server.permission.access import android.os.UserHandle -sealed class AccessUri( - val scheme: String -) { +sealed class AccessUri(val scheme: String) { override fun equals(other: Any?): Boolean { throw NotImplementedError() } @@ -34,9 +32,7 @@ sealed class AccessUri( } } -data class AppOpUri( - val appOpName: String -) : AccessUri(SCHEME) { +data class AppOpUri(val appOpName: String) : AccessUri(SCHEME) { override fun toString(): String = "$scheme:///$appOpName" companion object { @@ -44,10 +40,7 @@ data class AppOpUri( } } -data class PackageUri( - val packageName: String, - val userId: Int -) : AccessUri(SCHEME) { +data class PackageUri(val packageName: String, val userId: Int) : AccessUri(SCHEME) { override fun toString(): String = "$scheme:///$packageName/$userId" companion object { @@ -55,9 +48,7 @@ data class PackageUri( } } -data class PermissionUri( - val permissionName: String -) : AccessUri(SCHEME) { +data class PermissionUri(val permissionName: String) : AccessUri(SCHEME) { override fun toString(): String = "$scheme:///$permissionName" companion object { @@ -65,10 +56,7 @@ data class PermissionUri( } } -data class DevicePermissionUri( - val permissionName: String, - val deviceId: Int -) : AccessUri(SCHEME) { +data class DevicePermissionUri(val permissionName: String, val deviceId: Int) : AccessUri(SCHEME) { override fun toString(): String = "$scheme:///$permissionName/$deviceId" companion object { @@ -76,9 +64,7 @@ data class DevicePermissionUri( } } -data class UidUri( - val uid: Int -) : AccessUri(SCHEME) { +data class UidUri(val uid: Int) : AccessUri(SCHEME) { val userId: Int get() = UserHandle.getUserId(uid) diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpMigration.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpMigration.kt index 96d315e923ba..6c1b080e4ff4 100644 --- a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpMigration.kt +++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpMigration.kt @@ -46,9 +46,7 @@ class AppIdAppOpMigration { val appOpModes = MutableIndexedMap<String, Int>() appIdAppOpModes[appId] = appOpModes - legacyAppOpModes.forEach { (appOpName, appOpMode) -> - appOpModes[appOpName] = appOpMode - } + legacyAppOpModes.forEach { (appOpName, appOpMode) -> appOpModes[appOpName] = appOpMode } if (packageNames != null) { val packageVersions = userState.mutatePackageVersions() diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPersistence.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPersistence.kt index 4c7e94688d00..f291b1ab77e9 100644 --- a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPersistence.kt +++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPersistence.kt @@ -51,8 +51,10 @@ class AppIdAppOpPersistence : BaseAppOpPersistence() { } userState.appIdAppOpModes.forEachReversedIndexed { appIdIndex, appId, _ -> // Non-application UIDs may not have an Android package but may still have app op state. - if (appId !in state.externalState.appIdPackageNames && - appId >= Process.FIRST_APPLICATION_UID) { + if ( + appId !in state.externalState.appIdPackageNames && + appId >= Process.FIRST_APPLICATION_UID + ) { Slog.w(LOG_TAG, "Dropping unknown app ID $appId when parsing app-op state") appIdAppOpModes.removeAt(appIdIndex) userState.requestWriteMode(WriteMode.ASYNCHRONOUS) diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt index c02fe4df67c0..94caf2865b66 100644 --- a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt @@ -46,7 +46,9 @@ class AppIdAppOpPolicy : BaseAppOpPolicy(AppIdAppOpPersistence()) { newState.userStates.forEachIndexed { userStateIndex, _, userState -> val appIdIndex = userState.appIdAppOpModes.indexOfKey(appId) if (appIdIndex >= 0) { - newState.mutateUserStateAt(userStateIndex).mutateAppIdAppOpModes() + newState + .mutateUserStateAt(userStateIndex) + .mutateAppIdAppOpModes() .removeAt(appIdIndex) // Skip notifying the change listeners since the app ID no longer exists. } @@ -61,8 +63,8 @@ class AppIdAppOpPolicy : BaseAppOpPolicy(AppIdAppOpPersistence()) { if (userStateIndex < 0) { return false } - val appIdIndex = newState.userStates.valueAt(userStateIndex).appIdAppOpModes - .indexOfKey(appId) + val appIdIndex = + newState.userStates.valueAt(userStateIndex).appIdAppOpModes.indexOfKey(appId) if (appIdIndex < 0) { return false } @@ -71,7 +73,9 @@ class AppIdAppOpPolicy : BaseAppOpPolicy(AppIdAppOpPersistence()) { } fun GetStateScope.getAppOpMode(appId: Int, userId: Int, appOpName: String): Int = - state.userStates[userId]?.appIdAppOpModes?.get(appId) + state.userStates[userId] + ?.appIdAppOpModes + ?.get(appId) .getWithDefault(appOpName, AppOpsManager.opToDefaultMode(appOpName)) fun MutateStateScope.setAppOpMode( @@ -81,8 +85,10 @@ class AppIdAppOpPolicy : BaseAppOpPolicy(AppIdAppOpPersistence()) { mode: Int ): Boolean { val defaultMode = AppOpsManager.opToDefaultMode(appOpName) - val oldMode = newState.userStates[userId]!!.appIdAppOpModes[appId] - .getWithDefault(appOpName, defaultMode) + val oldMode = + newState.userStates[userId]!! + .appIdAppOpModes[appId] + .getWithDefault(appOpName, defaultMode) if (oldMode == mode) { return false } @@ -122,9 +128,7 @@ class AppIdAppOpPolicy : BaseAppOpPolicy(AppIdAppOpPersistence()) { with(upgrade) { upgradePackageState(packageState, userId, version) } } - /** - * Listener for app op mode changes. - */ + /** Listener for app op mode changes. */ abstract class OnAppOpModeChangedListener { /** * Called when an app op mode change has been made to the upcoming new state. diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpUpgrade.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpUpgrade.kt index 12df95e26ec4..10c77645bf15 100644 --- a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpUpgrade.kt +++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpUpgrade.kt @@ -28,11 +28,13 @@ class AppIdAppOpUpgrade(private val policy: AppIdAppOpPolicy) { ) { if (version <= 2) { with(policy) { - val appOpMode = getAppOpMode( - packageState.appId, userId, AppOpsManager.OPSTR_RUN_IN_BACKGROUND - ) + val appOpMode = + getAppOpMode(packageState.appId, userId, AppOpsManager.OPSTR_RUN_IN_BACKGROUND) setAppOpMode( - packageState.appId, userId, AppOpsManager.OPSTR_RUN_ANY_IN_BACKGROUND, appOpMode + packageState.appId, + userId, + AppOpsManager.OPSTR_RUN_ANY_IN_BACKGROUND, + appOpMode ) } } @@ -40,14 +42,19 @@ class AppIdAppOpUpgrade(private val policy: AppIdAppOpPolicy) { val permissionName = AppOpsManager.opToPermission(AppOpsManager.OP_SCHEDULE_EXACT_ALARM) if (permissionName in packageState.androidPackage!!.requestedPermissions) { with(policy) { - val appOpMode = getAppOpMode( - packageState.appId, userId, AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM - ) + val appOpMode = + getAppOpMode( + packageState.appId, + userId, + AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM + ) val defaultAppOpMode = AppOpsManager.opToDefaultMode(AppOpsManager.OP_SCHEDULE_EXACT_ALARM) if (appOpMode == defaultAppOpMode) { setAppOpMode( - packageState.appId, userId, AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM, + packageState.appId, + userId, + AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM, AppOpsManager.MODE_ALLOWED ) } diff --git a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt index 5b91ad9834df..26ea9d24f918 100644 --- a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt +++ b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt @@ -33,19 +33,16 @@ import com.android.server.permission.access.UidUri import com.android.server.permission.access.collection.forEachIndexed import com.android.server.permission.access.collection.set -class AppOpService( - private val service: AccessCheckingService -) : AppOpsCheckingServiceInterface { - private val packagePolicy = service.getSchemePolicy(PackageUri.SCHEME, AppOpUri.SCHEME) - as PackageAppOpPolicy - private val appIdPolicy = service.getSchemePolicy(UidUri.SCHEME, AppOpUri.SCHEME) - as AppIdAppOpPolicy +class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingServiceInterface { + private val packagePolicy = + service.getSchemePolicy(PackageUri.SCHEME, AppOpUri.SCHEME) as PackageAppOpPolicy + private val appIdPolicy = + service.getSchemePolicy(UidUri.SCHEME, AppOpUri.SCHEME) as AppIdAppOpPolicy private val context = service.context private lateinit var handler: Handler - @Volatile - private var listeners = ArraySet<AppOpsModeChangedListener>() + @Volatile private var listeners = ArraySet<AppOpsModeChangedListener>() private val listenersLock = Any() fun initialize() { @@ -86,9 +83,7 @@ class AppOpService( val appId = UserHandle.getAppId(uid) val userId = UserHandle.getUserId(uid) val opName = AppOpsManager.opToPublicName(op) - return service.getState { - with(appIdPolicy) { getAppOpMode(appId, userId, opName) } - } + return service.getState { with(appIdPolicy) { getAppOpMode(appId, userId, opName) } } } private fun getUidModes(uid: Int): ArrayMap<String, Int>? { @@ -115,10 +110,7 @@ class AppOpService( } } - private fun getPackageModes( - packageName: String, - userId: Int - ): ArrayMap<String, Int>? = + private fun getPackageModes(packageName: String, userId: Int): ArrayMap<String, Int>? = service.getState { with(packagePolicy) { getAppOpModes(packageName, userId) } }?.map override fun setPackageMode(packageName: String, op: Int, mode: Int, userId: Int) { @@ -131,15 +123,13 @@ class AppOpService( override fun removeUid(uid: Int) { val appId = UserHandle.getAppId(uid) val userId = UserHandle.getUserId(uid) - service.mutateState { - with(appIdPolicy) { removeAppOpModes(appId, userId) } - } + service.mutateState { with(appIdPolicy) { removeAppOpModes(appId, userId) } } } override fun removePackage(packageName: String, userId: Int): Boolean { var wasChanged = false service.mutateState { - wasChanged = with (packagePolicy) { removeAppOpModes(packageName, userId) } + wasChanged = with(packagePolicy) { removeAppOpModes(packageName, userId) } } return wasChanged } diff --git a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPersistence.kt b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPersistence.kt index a267637dd0c4..edeef713e6d7 100644 --- a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPersistence.kt +++ b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPersistence.kt @@ -52,9 +52,7 @@ abstract class BaseAppOpPersistence { } protected fun BinaryXmlSerializer.serializeAppOps(appOpModes: IndexedMap<String, Int>) { - appOpModes.forEachIndexed { _, name, mode -> - serializeAppOp(name, mode) - } + appOpModes.forEachIndexed { _, name, mode -> serializeAppOp(name, mode) } } private fun BinaryXmlSerializer.serializeAppOp(name: String, mode: Int) { diff --git a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt index c0a85f8e4b3f..758cec00da50 100644 --- a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt @@ -23,9 +23,7 @@ import com.android.server.permission.access.AppOpUri import com.android.server.permission.access.MutableAccessState import com.android.server.permission.access.SchemePolicy -abstract class BaseAppOpPolicy( - private val persistence: BaseAppOpPersistence -) : SchemePolicy() { +abstract class BaseAppOpPolicy(private val persistence: BaseAppOpPersistence) : SchemePolicy() { override val objectScheme: String get() = AppOpUri.SCHEME diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpMigration.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpMigration.kt index 03311a238410..8797e39754d7 100644 --- a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpMigration.kt +++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpMigration.kt @@ -44,9 +44,7 @@ class PackageAppOpMigration { val appOpModes = MutableIndexedMap<String, Int>() packageAppOpModes[packageName] = appOpModes - legacyAppOpModes.forEach { (appOpName, appOpMode) -> - appOpModes[appOpName] = appOpMode - } + legacyAppOpModes.forEach { (appOpName, appOpMode) -> appOpModes[appOpName] = appOpMode } userState.mutatePackageVersions()[packageName] = version } diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt index 5398a57d084e..0d9470edc4ea 100644 --- a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt @@ -46,7 +46,9 @@ class PackageAppOpPolicy : BaseAppOpPolicy(PackageAppOpPersistence()) { newState.userStates.forEachIndexed { userStateIndex, _, userState -> val packageNameIndex = userState.packageAppOpModes.indexOfKey(packageName) if (packageNameIndex >= 0) { - newState.mutateUserStateAt(userStateIndex).mutatePackageAppOpModes() + newState + .mutateUserStateAt(userStateIndex) + .mutatePackageAppOpModes() .removeAt(packageNameIndex) // Skip notifying the change listeners since the package no longer exists. } @@ -61,18 +63,22 @@ class PackageAppOpPolicy : BaseAppOpPolicy(PackageAppOpPersistence()) { if (userStateIndex < 0) { return false } - val packageNameIndex = newState.userStates.valueAt(userStateIndex).packageAppOpModes - .indexOfKey(packageName) + val packageNameIndex = + newState.userStates.valueAt(userStateIndex).packageAppOpModes.indexOfKey(packageName) if (packageNameIndex < 0) { return false } - newState.mutateUserStateAt(userStateIndex).mutatePackageAppOpModes() + newState + .mutateUserStateAt(userStateIndex) + .mutatePackageAppOpModes() .removeAt(packageNameIndex) return true } fun GetStateScope.getAppOpMode(packageName: String, userId: Int, appOpName: String): Int = - state.userStates[userId]?.packageAppOpModes?.get(packageName) + state.userStates[userId] + ?.packageAppOpModes + ?.get(packageName) .getWithDefault(appOpName, AppOpsManager.opToDefaultMode(appOpName)) fun MutateStateScope.setAppOpMode( @@ -82,8 +88,10 @@ class PackageAppOpPolicy : BaseAppOpPolicy(PackageAppOpPersistence()) { mode: Int ): Boolean { val defaultMode = AppOpsManager.opToDefaultMode(appOpName) - val oldMode = newState.userStates[userId]!!.packageAppOpModes[packageName] - .getWithDefault(appOpName, defaultMode) + val oldMode = + newState.userStates[userId]!! + .packageAppOpModes[packageName] + .getWithDefault(appOpName, defaultMode) if (oldMode == mode) { return false } @@ -123,9 +131,7 @@ class PackageAppOpPolicy : BaseAppOpPolicy(PackageAppOpPersistence()) { with(upgrade) { upgradePackageState(packageState, userId, version) } } - /** - * Listener for app op mode changes. - */ + /** Listener for app op mode changes. */ abstract class OnAppOpModeChangedListener { /** * Called when an app op mode change has been made to the upcoming new state. diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpUpgrade.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpUpgrade.kt index 8e370936291f..f5eedf714158 100644 --- a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpUpgrade.kt +++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpUpgrade.kt @@ -28,11 +28,16 @@ class PackageAppOpUpgrade(private val policy: PackageAppOpPolicy) { ) { if (version <= 2) { with(policy) { - val appOpMode = getAppOpMode( - packageState.packageName, userId, AppOpsManager.OPSTR_RUN_IN_BACKGROUND - ) + val appOpMode = + getAppOpMode( + packageState.packageName, + userId, + AppOpsManager.OPSTR_RUN_IN_BACKGROUND + ) setAppOpMode( - packageState.packageName, userId, AppOpsManager.OPSTR_RUN_ANY_IN_BACKGROUND, + packageState.packageName, + userId, + AppOpsManager.OPSTR_RUN_ANY_IN_BACKGROUND, appOpMode ) } diff --git a/services/permission/java/com/android/server/permission/access/collection/ArrayMapExtensions.kt b/services/permission/java/com/android/server/permission/access/collection/ArrayMapExtensions.kt index 686db42bbe63..b74f47734701 100644 --- a/services/permission/java/com/android/server/permission/access/collection/ArrayMapExtensions.kt +++ b/services/permission/java/com/android/server/permission/access/collection/ArrayMapExtensions.kt @@ -49,7 +49,9 @@ inline fun <K, V> ArrayMap<K, V>.forEachReversedIndexed(action: (Int, K, V) -> U } inline fun <K, V> ArrayMap<K, V>.getOrPut(key: K, defaultValue: () -> V): V { - get(key)?.let { return it } + get(key)?.let { + return it + } return defaultValue().also { put(key, it) } } diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt index ce4aa4446698..ea8e07fd31ff 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt @@ -16,12 +16,8 @@ package com.android.server.permission.access.immutable -/** - * Immutable list with index-based access. - */ -sealed class IndexedList<T>( - internal val list: ArrayList<T> -) : Immutable<MutableIndexedList<T>> { +/** Immutable list with index-based access. */ +sealed class IndexedList<T>(internal val list: ArrayList<T>) : Immutable<MutableIndexedList<T>> { val size: Int get() = list.size @@ -29,20 +25,15 @@ sealed class IndexedList<T>( operator fun contains(element: T): Boolean = list.contains(element) - @Suppress("ReplaceGetOrSet") - operator fun get(index: Int): T = list.get(index) + @Suppress("ReplaceGetOrSet") operator fun get(index: Int): T = list.get(index) override fun toMutable(): MutableIndexedList<T> = MutableIndexedList(this) override fun toString(): String = list.toString() } -/** - * Mutable list with index-based access. - */ -class MutableIndexedList<T>( - list: ArrayList<T> = ArrayList() -) : IndexedList<T>(list) { +/** Mutable list with index-based access. */ +class MutableIndexedList<T>(list: ArrayList<T> = ArrayList()) : IndexedList<T>(list) { constructor(indexedList: IndexedList<T>) : this(ArrayList(indexedList.list)) @Suppress("ReplaceGetOrSet") diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedListExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedListExtensions.kt index dc9bae323662..a9d804ec2fd2 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IndexedListExtensions.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedListExtensions.kt @@ -70,9 +70,7 @@ inline fun <T> IndexedList<T>.reduceIndexed( accumulator: (Int, Int, T) -> Int ): Int { var value = initialValue - forEachIndexed { index, element -> - value = accumulator(value, index, element) - } + forEachIndexed { index, element -> value = accumulator(value, index, element) } return value } diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt index 77e71baf0ab7..3a2fd2f386c8 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt @@ -16,12 +16,9 @@ package com.android.server.permission.access.immutable -/** - * Immutable set with index-based access, implemented using a list. - */ -sealed class IndexedListSet<T>( - internal val list: ArrayList<T> -) : Immutable<MutableIndexedListSet<T>> { +/** Immutable set with index-based access, implemented using a list. */ +sealed class IndexedListSet<T>(internal val list: ArrayList<T>) : + Immutable<MutableIndexedListSet<T>> { val size: Int get() = list.size @@ -31,20 +28,15 @@ sealed class IndexedListSet<T>( fun indexOf(element: T): Int = list.indexOf(element) - @Suppress("ReplaceGetOrSet") - fun elementAt(index: Int): T = list.get(index) + @Suppress("ReplaceGetOrSet") fun elementAt(index: Int): T = list.get(index) override fun toMutable(): MutableIndexedListSet<T> = MutableIndexedListSet(this) override fun toString(): String = list.toString() } -/** - * Mutable set with index-based access, implemented using a list. - */ -class MutableIndexedListSet<T>( - list: ArrayList<T> = ArrayList() -) : IndexedListSet<T>(list) { +/** Mutable set with index-based access, implemented using a list. */ +class MutableIndexedListSet<T>(list: ArrayList<T> = ArrayList()) : IndexedListSet<T>(list) { constructor(indexedListSet: IndexedListSet<T>) : this(ArrayList(indexedListSet.list)) fun add(element: T): Boolean = diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedListSetExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedListSetExtensions.kt index 13fc141c5a66..2634b5308049 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IndexedListSetExtensions.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedListSetExtensions.kt @@ -70,9 +70,7 @@ inline fun <T> IndexedListSet<T>.reduceIndexed( accumulator: (Int, Int, T) -> Int ): Int { var value = initialValue - forEachIndexed { index, element -> - value = accumulator(value, index, element) - } + forEachIndexed { index, element -> value = accumulator(value, index, element) } return value } diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt index 299cc89d9a07..873c9c83607e 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt @@ -18,12 +18,9 @@ package com.android.server.permission.access.immutable import android.util.ArrayMap -/** - * Immutable map with index-based access. - */ -sealed class IndexedMap<K, V>( - internal val map: ArrayMap<K, V> -) : Immutable<MutableIndexedMap<K, V>> { +/** Immutable map with index-based access. */ +sealed class IndexedMap<K, V>(internal val map: ArrayMap<K, V>) : + Immutable<MutableIndexedMap<K, V>> { val size: Int get() = map.size @@ -31,8 +28,7 @@ sealed class IndexedMap<K, V>( operator fun contains(key: K): Boolean = map.containsKey(key) - @Suppress("ReplaceGetOrSet") - operator fun get(key: K): V? = map.get(key) + @Suppress("ReplaceGetOrSet") operator fun get(key: K): V? = map.get(key) fun indexOfKey(key: K): Int = map.indexOfKey(key) @@ -45,12 +41,8 @@ sealed class IndexedMap<K, V>( override fun toString(): String = map.toString() } -/** - * Mutable map with index-based access. - */ -class MutableIndexedMap<K, V>( - map: ArrayMap<K, V> = ArrayMap() -) : IndexedMap<K, V>(map) { +/** Mutable map with index-based access. */ +class MutableIndexedMap<K, V>(map: ArrayMap<K, V> = ArrayMap()) : IndexedMap<K, V>(map) { constructor(indexedMap: IndexedMap<K, V>) : this(ArrayMap(indexedMap.map)) fun put(key: K, value: V): V? = map.put(key, value) diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedMapExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedMapExtensions.kt index 69f1779cff8d..48637cc271bf 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IndexedMapExtensions.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedMapExtensions.kt @@ -36,7 +36,9 @@ inline fun <K, V> IndexedMap<K, V>.anyIndexed(predicate: (Int, K, V) -> Boolean) inline fun <K, V, R> IndexedMap<K, V>.firstNotNullOfOrNullIndexed(transform: (Int, K, V) -> R): R? { forEachIndexed { index, key, value -> - transform(index, key, value)?.let { return it } + transform(index, key, value)?.let { + return it + } } return null } @@ -75,9 +77,7 @@ inline fun <K, V, R, C : MutableCollection<R>> IndexedMap<K, V>.mapIndexedTo( destination: C, transform: (Int, K, V) -> R, ): C { - forEachIndexed { index, key, value -> - transform(index, key, value).let { destination += it } - } + forEachIndexed { index, key, value -> transform(index, key, value).let { destination += it } } return destination } @@ -85,14 +85,14 @@ inline fun <K, V, R, C : MutableCollection<R>> IndexedMap<K, V>.mapNotNullIndexe destination: C, transform: (Int, K, V) -> R? ): C { - forEachIndexed { index, key, value -> - transform(index, key, value)?.let { destination += it } - } + forEachIndexed { index, key, value -> transform(index, key, value)?.let { destination += it } } return destination } inline fun <K, V> MutableIndexedMap<K, V>.getOrPut(key: K, defaultValue: () -> V): V { - get(key)?.let { return it } + get(key)?.let { + return it + } return defaultValue().also { put(key, it) } } diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt index ff76a4745c8b..6fe471802878 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt @@ -33,8 +33,7 @@ sealed class IndexedReferenceMap<K, I : Immutable<M>, M : I>( operator fun contains(key: K): Boolean = map.containsKey(key) - @Suppress("ReplaceGetOrSet") - operator fun get(key: K): I? = map.get(key)?.get() + @Suppress("ReplaceGetOrSet") operator fun get(key: K): I? = map.get(key)?.get() fun indexOfKey(key: K): Int = map.indexOfKey(key) @@ -55,7 +54,9 @@ sealed class IndexedReferenceMap<K, I : Immutable<M>, M : I>( class MutableIndexedReferenceMap<K, I : Immutable<M>, M : I>( map: ArrayMap<K, MutableReference<I, M>> = ArrayMap() ) : IndexedReferenceMap<K, I, M>(map) { - constructor(indexedReferenceMap: IndexedReferenceMap<K, I, M>) : this( + constructor( + indexedReferenceMap: IndexedReferenceMap<K, I, M> + ) : this( ArrayMap(indexedReferenceMap.map).apply { for (i in 0 until size) { setValueAt(i, valueAt(i).toImmutable()) @@ -63,8 +64,7 @@ class MutableIndexedReferenceMap<K, I : Immutable<M>, M : I>( } ) - @Suppress("ReplaceGetOrSet") - fun mutate(key: K): M? = map.get(key)?.mutate() + @Suppress("ReplaceGetOrSet") fun mutate(key: K): M? = map.get(key)?.mutate() fun put(key: K, value: M): I? = map.put(key, MutableReference(value))?.get() diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMapExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMapExtensions.kt index 22b4d521176e..43a902b23ba5 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMapExtensions.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMapExtensions.kt @@ -72,7 +72,9 @@ inline fun <K, I : Immutable<M>, M : I> MutableIndexedReferenceMap<K, I, M>.muta key: K, defaultValue: () -> M ): M { - mutate(key)?.let { return it } + mutate(key)?.let { + return it + } return defaultValue().also { put(key, it) } } diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt index 547e56cef62a..cbc24b1a64a7 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt @@ -18,12 +18,8 @@ package com.android.server.permission.access.immutable import android.util.ArraySet -/** - * Immutable set with index-based access. - */ -sealed class IndexedSet<T>( - internal val set: ArraySet<T> -) : Immutable<MutableIndexedSet<T>> { +/** Immutable set with index-based access. */ +sealed class IndexedSet<T>(internal val set: ArraySet<T>) : Immutable<MutableIndexedSet<T>> { val size: Int get() = set.size @@ -40,12 +36,8 @@ sealed class IndexedSet<T>( override fun toString(): String = set.toString() } -/** - * Mutable set with index-based access. - */ -class MutableIndexedSet<T>( - set: ArraySet<T> = ArraySet() -) : IndexedSet<T>(set) { +/** Mutable set with index-based access. */ +class MutableIndexedSet<T>(set: ArraySet<T> = ArraySet()) : IndexedSet<T>(set) { constructor(indexedSet: IndexedSet<T>) : this(ArraySet(indexedSet.set)) fun add(element: T): Boolean = set.add(element) diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt index 7ed29e8813ac..e9a405f9fd6f 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt @@ -18,12 +18,8 @@ package com.android.server.permission.access.immutable import android.util.SparseArray -/** - * Immutable map with index-based access and [Int] keys. - */ -sealed class IntMap<T>( - internal val array: SparseArray<T> -) : Immutable<MutableIntMap<T>> { +/** Immutable map with index-based access and [Int] keys. */ +sealed class IntMap<T>(internal val array: SparseArray<T>) : Immutable<MutableIntMap<T>> { val size: Int get() = array.size() @@ -44,12 +40,8 @@ sealed class IntMap<T>( override fun toString(): String = array.toString() } -/** - * Mutable map with index-based access and [Int] keys. - */ -class MutableIntMap<T>( - array: SparseArray<T> = SparseArray() -) : IntMap<T>(array) { +/** Mutable map with index-based access and [Int] keys. */ +class MutableIntMap<T>(array: SparseArray<T> = SparseArray()) : IntMap<T>(array) { constructor(intMap: IntMap<T>) : this(intMap.array.clone()) fun put(key: Int, value: T): T? = array.putReturnOld(key, value) diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntMapExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IntMapExtensions.kt index 9aa0a4182eb7..09d7319ec62e 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IntMapExtensions.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IntMapExtensions.kt @@ -36,7 +36,9 @@ inline fun <T> IntMap<T>.anyIndexed(predicate: (Int, Int, T) -> Boolean): Boolea inline fun <T, R> IntMap<T>.firstNotNullOfOrNullIndexed(transform: (Int, Int, T) -> R): R? { forEachIndexed { index, key, value -> - transform(index, key, value)?.let { return it } + transform(index, key, value)?.let { + return it + } } return null } @@ -72,7 +74,9 @@ inline fun <T> IntMap<T>.noneIndexed(predicate: (Int, Int, T) -> Boolean): Boole } inline fun <T> MutableIntMap<T>.getOrPut(key: Int, defaultValue: () -> T): T { - get(key)?.let { return it } + get(key)?.let { + return it + } return defaultValue().also { put(key, it) } } diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt index 160b2279a0ba..3f2651736a54 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt @@ -33,8 +33,7 @@ sealed class IntReferenceMap<I : Immutable<M>, M : I>( operator fun contains(key: Int): Boolean = array.contains(key) - @Suppress("ReplaceGetOrSet") - operator fun get(key: Int): I? = array.get(key)?.get() + @Suppress("ReplaceGetOrSet") operator fun get(key: Int): I? = array.get(key)?.get() fun indexOfKey(key: Int): Int = array.indexOfKey(key) @@ -55,7 +54,9 @@ sealed class IntReferenceMap<I : Immutable<M>, M : I>( class MutableIntReferenceMap<I : Immutable<M>, M : I>( array: SparseArray<MutableReference<I, M>> = SparseArray() ) : IntReferenceMap<I, M>(array) { - constructor(intReferenceMap: IntReferenceMap<I, M>) : this( + constructor( + intReferenceMap: IntReferenceMap<I, M> + ) : this( intReferenceMap.array.clone().apply { for (i in 0 until size()) { setValueAt(i, valueAt(i).toImmutable()) @@ -63,8 +64,7 @@ class MutableIntReferenceMap<I : Immutable<M>, M : I>( } ) - @Suppress("ReplaceGetOrSet") - fun mutate(key: Int): M? = array.get(key)?.mutate() + @Suppress("ReplaceGetOrSet") fun mutate(key: Int): M? = array.get(key)?.mutate() fun put(key: Int, value: M): I? = array.putReturnOld(key, MutableReference(value))?.get() diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMapExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMapExtensions.kt index 1ed4f8a777d2..a1bab9579c3d 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMapExtensions.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMapExtensions.kt @@ -72,7 +72,9 @@ inline fun <I : Immutable<M>, M : I> MutableIntReferenceMap<I, M>.mutateOrPut( key: Int, defaultValue: () -> M ): M { - mutate(key)?.let { return it } + mutate(key)?.let { + return it + } return defaultValue().also { put(key, it) } } diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt b/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt index 21f2af20c3a9..125479706586 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt @@ -18,12 +18,8 @@ package com.android.server.permission.access.immutable import android.util.SparseBooleanArray -/** - * Immutable set with index-based access and [Int] elements. - */ -sealed class IntSet( - internal val array: SparseBooleanArray -) : Immutable<MutableIntSet> { +/** Immutable set with index-based access and [Int] elements. */ +sealed class IntSet(internal val array: SparseBooleanArray) : Immutable<MutableIntSet> { val size: Int get() = array.size() @@ -40,12 +36,8 @@ sealed class IntSet( override fun toString(): String = array.toString() } -/** - * Mutable set with index-based access and [Int] elements. - */ -class MutableIntSet( - array: SparseBooleanArray = SparseBooleanArray() -) : IntSet(array) { +/** Mutable set with index-based access and [Int] elements. */ +class MutableIntSet(array: SparseBooleanArray = SparseBooleanArray()) : IntSet(array) { constructor(intSet: IntSet) : this(intSet.array.clone()) fun add(element: Int): Boolean = diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntSetExtensions.kt b/services/permission/java/com/android/server/permission/access/immutable/IntSetExtensions.kt index 163ebbf85aed..9d0d14f01ce0 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/IntSetExtensions.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/IntSetExtensions.kt @@ -66,7 +66,7 @@ inline fun IntSet.noneIndexed(predicate: (Int, Int) -> Boolean): Boolean { operator fun IntSet.plus(element: Int): MutableIntSet = toMutable().apply { this += element } -fun MutableIntSet(values: IntArray): MutableIntSet = MutableIntSet().apply{ this += values } +fun MutableIntSet(values: IntArray): MutableIntSet = MutableIntSet().apply { this += values } operator fun MutableIntSet.plusAssign(element: Int) { array.put(element, true) diff --git a/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt b/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt index 171cfeb4379d..471a71b04635 100644 --- a/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt +++ b/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt @@ -27,21 +27,17 @@ package com.android.server.permission.access.immutable * exposed on the immutable interface of the data structure as a `getFoo` method, and the [mutate] * method exposed on the mutable interface of the data structure as a `mutateFoo` method. When the * data structure is mutated/copied, a new instance of this class should be obtained with - * [toImmutable], which makes the wrapped reference immutable-only again and thus prevents - * further modifications to a data structure accessed with its immutable interface. + * [toImmutable], which makes the wrapped reference immutable-only again and thus prevents further + * modifications to a data structure accessed with its immutable interface. * * @see MutableIndexedReferenceMap * @see MutableIntReferenceMap */ -class MutableReference<I : Immutable<M>, M : I> private constructor( - private var immutable: I, - private var mutable: M? -) { +class MutableReference<I : Immutable<M>, M : I> +private constructor(private var immutable: I, private var mutable: M?) { constructor(mutable: M) : this(mutable, mutable) - /** - * Return an immutable reference to the wrapped mutable data structure. - */ + /** Return an immutable reference to the wrapped mutable data structure. */ fun get(): I = immutable /** @@ -50,7 +46,9 @@ class MutableReference<I : Immutable<M>, M : I> private constructor( * already mutable. */ fun mutate(): M { - mutable?.let { return it } + mutable?.let { + return it + } return immutable.toMutable().also { immutable = it mutable = it diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionMigration.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionMigration.kt index 691ed8f10220..29838959ccb3 100644 --- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionMigration.kt +++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionMigration.kt @@ -23,9 +23,7 @@ import com.android.server.permission.access.immutable.* // ktlint-disable no-wil import com.android.server.permission.access.util.PackageVersionMigration import com.android.server.pm.permission.PermissionMigrationHelper -/** - * This class migrate legacy permissions to unified permission subsystem - */ +/** This class migrate legacy permissions to unified permission subsystem */ class AppIdPermissionMigration { internal fun migrateSystemState(state: MutableAccessState) { val legacyPermissionsManager = @@ -34,10 +32,15 @@ class AppIdPermissionMigration { return } - migratePermissions(state.mutateSystemState().mutatePermissions(), - legacyPermissionsManager.legacyPermissions) - migratePermissions(state.mutateSystemState().mutatePermissionTrees(), - legacyPermissionsManager.legacyPermissionTrees, true) + migratePermissions( + state.mutateSystemState().mutatePermissions(), + legacyPermissionsManager.legacyPermissions + ) + migratePermissions( + state.mutateSystemState().mutatePermissionTrees(), + legacyPermissionsManager.legacyPermissionTrees, + true + ) } private fun migratePermissions( @@ -46,14 +49,15 @@ class AppIdPermissionMigration { isPermissionTree: Boolean = false ) { legacyPermissions.forEach { (_, legacyPermission) -> - val permission = Permission( - legacyPermission.permissionInfo, false, legacyPermission.type, 0 - ) + val permission = + Permission(legacyPermission.permissionInfo, false, legacyPermission.type, 0) permissions[permission.name] = permission if (DEBUG_MIGRATION) { - Slog.v(LOG_TAG, "Migrated permission: ${permission.name}, type: " + - "${permission.type}, appId: ${permission.appId}, protectionLevel: " + - "${permission.protectionLevel}, tree: $isPermissionTree" + Slog.v( + LOG_TAG, + "Migrated permission: ${permission.name}, type: " + + "${permission.type}, appId: ${permission.appId}, protectionLevel: " + + "${permission.protectionLevel}, tree: $isPermissionTree" ) } } @@ -81,25 +85,23 @@ class AppIdPermissionMigration { val permissionFlags = MutableIndexedMap<String, Int>() appIdPermissionFlags[appId] = permissionFlags - legacyPermissionStates.forEach forEachPermission@ { + legacyPermissionStates.forEach forEachPermission@{ (permissionName, legacyPermissionState) -> val permission = state.systemState.permissions[permissionName] if (permission == null) { Slog.w( - LOG_TAG, "Dropping unknown permission $permissionName for app ID $appId" + + LOG_TAG, + "Dropping unknown permission $permissionName for app ID $appId" + " when migrating permission state" ) return@forEachPermission } - permissionFlags[permissionName] = migratePermissionFlags( - permission, legacyPermissionState, appId, userId - ) + permissionFlags[permissionName] = + migratePermissionFlags(permission, legacyPermissionState, appId, userId) } val packageVersions = userState.mutatePackageVersions() - packageNames.forEachIndexed { _, packageName -> - packageVersions[packageName] = version - } + packageNames.forEachIndexed { _, packageName -> packageVersions[packageName] = version } } } @@ -109,29 +111,35 @@ class AppIdPermissionMigration { appId: Int, userId: Int ): Int { - var flags = when { - permission.isNormal -> if (legacyPermissionState.isGranted) { - PermissionFlags.INSTALL_GRANTED - } else { - PermissionFlags.INSTALL_REVOKED - } - permission.isSignature || permission.isInternal -> - if (legacyPermissionState.isGranted) { - if (permission.isDevelopment || permission.isRole) { - PermissionFlags.PROTECTION_GRANTED or PermissionFlags.RUNTIME_GRANTED + var flags = + when { + permission.isNormal -> + if (legacyPermissionState.isGranted) { + PermissionFlags.INSTALL_GRANTED } else { - PermissionFlags.PROTECTION_GRANTED + PermissionFlags.INSTALL_REVOKED } - } else { - 0 - } - permission.isRuntime -> - if (legacyPermissionState.isGranted) PermissionFlags.RUNTIME_GRANTED else 0 - else -> 0 - } - flags = PermissionFlags.updateFlags( - permission, flags, legacyPermissionState.flags, legacyPermissionState.flags - ) + permission.isSignature || permission.isInternal -> + if (legacyPermissionState.isGranted) { + if (permission.isDevelopment || permission.isRole) { + PermissionFlags.PROTECTION_GRANTED or PermissionFlags.RUNTIME_GRANTED + } else { + PermissionFlags.PROTECTION_GRANTED + } + } else { + 0 + } + permission.isRuntime -> + if (legacyPermissionState.isGranted) PermissionFlags.RUNTIME_GRANTED else 0 + else -> 0 + } + flags = + PermissionFlags.updateFlags( + permission, + flags, + legacyPermissionState.flags, + legacyPermissionState.flags + ) if (DEBUG_MIGRATION) { val oldFlagString = PermissionFlags.apiFlagsToString(legacyPermissionState.flags) val newFlagString = PermissionFlags.toString(flags) @@ -139,7 +147,8 @@ class AppIdPermissionMigration { val newGrantState = PermissionFlags.isPermissionGranted(flags) val flagsMismatch = legacyPermissionState.flags != PermissionFlags.toApiFlags(flags) Slog.v( - LOG_TAG, "Migrated appId: $appId, permission: " + + LOG_TAG, + "Migrated appId: $appId, permission: " + "${permission.name}, user: $userId, oldGrantState: $oldGrantState" + ", oldFlags: $oldFlagString, newFlags: $newFlagString, grantMismatch: " + "${oldGrantState != newGrantState}, flagsMismatch: $flagsMismatch" diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPersistence.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPersistence.kt index 2c8175b585af..1f40f01ffef4 100644 --- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPersistence.kt +++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPersistence.kt @@ -57,11 +57,12 @@ class AppIdPermissionPersistence { isPermissionTree: Boolean ) { val systemState = state.mutateSystemState(WriteMode.NONE) - val permissions = if (isPermissionTree) { - systemState.mutatePermissionTrees() - } else { - systemState.mutatePermissions() - } + val permissions = + if (isPermissionTree) { + systemState.mutatePermissionTrees() + } else { + systemState.mutatePermissions() + } forEachTag { when (val tagName = tagName) { TAG_PERMISSION -> parsePermission(permissions) @@ -71,10 +72,13 @@ class AppIdPermissionPersistence { permissions.forEachReversedIndexed { permissionIndex, _, permission -> val packageName = permission.packageName val externalState = state.externalState - if (packageName !in externalState.packageStates && - packageName !in externalState.disabledSystemPackageStates) { + if ( + packageName !in externalState.packageStates && + packageName !in externalState.disabledSystemPackageStates + ) { Slog.w( - LOG_TAG, "Dropping permission ${permission.name} from unknown package" + + LOG_TAG, + "Dropping permission ${permission.name} from unknown package" + " $packageName when parsing permissions" ) permissions.removeAt(permissionIndex) @@ -88,11 +92,12 @@ class AppIdPermissionPersistence { ) { val name = getAttributeValueOrThrow(ATTR_NAME).intern() @Suppress("DEPRECATION") - val permissionInfo = PermissionInfo().apply { - this.name = name - packageName = getAttributeValueOrThrow(ATTR_PACKAGE_NAME).intern() - protectionLevel = getAttributeIntHexOrThrow(ATTR_PROTECTION_LEVEL) - } + val permissionInfo = + PermissionInfo().apply { + this.name = name + packageName = getAttributeValueOrThrow(ATTR_PACKAGE_NAME).intern() + protectionLevel = getAttributeIntHexOrThrow(ATTR_PROTECTION_LEVEL) + } val type = getAttributeIntOrThrow(ATTR_TYPE) when (type) { Permission.TYPE_MANIFEST -> {} @@ -125,15 +130,14 @@ class AppIdPermissionPersistence { tagName: String, permissions: IndexedMap<String, Permission> ) { - tag(tagName) { - permissions.forEachIndexed { _, _, it -> serializePermission(it) } - } + tag(tagName) { permissions.forEachIndexed { _, _, it -> serializePermission(it) } } } private fun BinaryXmlSerializer.serializePermission(permission: Permission) { val type = permission.type when (type) { - Permission.TYPE_MANIFEST, Permission.TYPE_DYNAMIC -> {} + Permission.TYPE_MANIFEST, + Permission.TYPE_DYNAMIC -> {} Permission.TYPE_CONFIG -> return else -> { Slog.w(LOG_TAG, "Skipping serializing permission $name with unknown type $type") @@ -228,11 +232,12 @@ class AppIdPermissionPersistence { tag(TAG_PERMISSION) { attributeInterned(ATTR_NAME, name) // Never serialize one-time permissions as granted. - val serializedFlags = if (flags.hasBits(PermissionFlags.ONE_TIME)) { - flags andInv PermissionFlags.RUNTIME_GRANTED - } else { - flags - } + val serializedFlags = + if (flags.hasBits(PermissionFlags.ONE_TIME)) { + flags andInv PermissionFlags.RUNTIME_GRANTED + } else { + flags + } attributeInt(ATTR_FLAGS, serializedFlags) } } diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt index 345f101cbc14..08ba75397a09 100644 --- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt @@ -55,7 +55,8 @@ class AppIdPermissionPolicy : SchemePolicy() { @Volatile private var onPermissionFlagsChangedListeners: - IndexedListSet<OnPermissionFlagsChangedListener> = MutableIndexedListSet() + IndexedListSet<OnPermissionFlagsChangedListener> = + MutableIndexedListSet() private val onPermissionFlagsChangedListenersLock = Any() private val privilegedPermissionAllowlistViolations = MutableIndexedSet<String>() @@ -73,30 +74,37 @@ class AppIdPermissionPolicy : SchemePolicy() { override fun MutateStateScope.onInitialized() { newState.externalState.configPermissions.forEach { (permissionName, permissionEntry) -> val oldPermission = newState.systemState.permissions[permissionName] - val newPermission = if (oldPermission != null) { - if (permissionEntry.gids != null) { - oldPermission.copy( - gids = permissionEntry.gids, areGidsPerUser = permissionEntry.perUser - ) - } else { - return@forEach - } - } else { - @Suppress("DEPRECATION") - val permissionInfo = PermissionInfo().apply { - name = permissionName - packageName = PLATFORM_PACKAGE_NAME - protectionLevel = PermissionInfo.PROTECTION_SIGNATURE - } - if (permissionEntry.gids != null) { - Permission( - permissionInfo, false, Permission.TYPE_CONFIG, 0, permissionEntry.gids, - permissionEntry.perUser - ) + val newPermission = + if (oldPermission != null) { + if (permissionEntry.gids != null) { + oldPermission.copy( + gids = permissionEntry.gids, + areGidsPerUser = permissionEntry.perUser + ) + } else { + return@forEach + } } else { - Permission(permissionInfo, false, Permission.TYPE_CONFIG, 0) + @Suppress("DEPRECATION") + val permissionInfo = + PermissionInfo().apply { + name = permissionName + packageName = PLATFORM_PACKAGE_NAME + protectionLevel = PermissionInfo.PROTECTION_SIGNATURE + } + if (permissionEntry.gids != null) { + Permission( + permissionInfo, + false, + Permission.TYPE_CONFIG, + 0, + permissionEntry.gids, + permissionEntry.perUser + ) + } else { + Permission(permissionInfo, false, Permission.TYPE_CONFIG, 0) + } } - } newState.mutateSystemState().mutatePermissions()[permissionName] = newPermission } } @@ -200,30 +208,32 @@ class AppIdPermissionPolicy : SchemePolicy() { val androidPackage = packageState.androidPackage ?: return val appId = packageState.appId androidPackage.requestedPermissions.forEach { permissionName -> - val permission = newState.systemState.permissions[permissionName] - ?: return@forEach + val permission = newState.systemState.permissions[permissionName] ?: return@forEach if (!permission.isHardOrSoftRestricted) { return@forEach } - val isRequestedBySystemPackage = anyPackageInAppId(appId) { - it.isSystem && permissionName in it.androidPackage!!.requestedPermissions - } + val isRequestedBySystemPackage = + anyPackageInAppId(appId) { + it.isSystem && permissionName in it.androidPackage!!.requestedPermissions + } if (isRequestedBySystemPackage) { return@forEach } val oldFlags = getPermissionFlags(appId, userId, permissionName) var newFlags = oldFlags andInv PermissionFlags.UPGRADE_EXEMPT val isExempt = newFlags.hasAnyBit(PermissionFlags.MASK_EXEMPT) - newFlags = if (permission.isHardRestricted && !isExempt) { - newFlags or PermissionFlags.RESTRICTION_REVOKED - } else { - newFlags andInv PermissionFlags.RESTRICTION_REVOKED - } - newFlags = if (permission.isSoftRestricted && !isExempt) { - newFlags or PermissionFlags.SOFT_RESTRICTED - } else { - newFlags andInv PermissionFlags.SOFT_RESTRICTED - } + newFlags = + if (permission.isHardRestricted && !isExempt) { + newFlags or PermissionFlags.RESTRICTION_REVOKED + } else { + newFlags andInv PermissionFlags.RESTRICTION_REVOKED + } + newFlags = + if (permission.isSoftRestricted && !isExempt) { + newFlags or PermissionFlags.SOFT_RESTRICTED + } else { + newFlags andInv PermissionFlags.SOFT_RESTRICTED + } setPermissionFlags(appId, userId, permissionName, newFlags) } } @@ -243,15 +253,15 @@ class AppIdPermissionPolicy : SchemePolicy() { val androidPackage = packageState.androidPackage ?: return val appId = packageState.appId androidPackage.requestedPermissions.forEach { permissionName -> - val permission = newState.systemState.permissions[permissionName] - ?: return@forEach + val permission = newState.systemState.permissions[permissionName] ?: return@forEach if (!permission.isRuntime || permission.isRemoved) { return@forEach } - val isRequestedByOtherPackages = anyPackageInAppId(appId) { - it.packageName != packageName && - permissionName in it.androidPackage!!.requestedPermissions - } + val isRequestedByOtherPackages = + anyPackageInAppId(appId) { + it.packageName != packageName && + permissionName in it.androidPackage!!.requestedPermissions + } if (isRequestedByOtherPackages) { return@forEach } @@ -260,13 +270,15 @@ class AppIdPermissionPolicy : SchemePolicy() { return@forEach } var newFlags = oldFlags - newFlags = if ( - newFlags.hasBits(PermissionFlags.ROLE) || newFlags.hasBits(PermissionFlags.PREGRANT) - ) { - newFlags or PermissionFlags.RUNTIME_GRANTED - } else { - newFlags andInv PermissionFlags.RUNTIME_GRANTED - } + newFlags = + if ( + newFlags.hasBits(PermissionFlags.ROLE) || + newFlags.hasBits(PermissionFlags.PREGRANT) + ) { + newFlags or PermissionFlags.RUNTIME_GRANTED + } else { + newFlags andInv PermissionFlags.RUNTIME_GRANTED + } newFlags = newFlags andInv USER_SETTABLE_MASK if (newFlags.hasBits(PermissionFlags.LEGACY_GRANTED)) { newFlags = newFlags or PermissionFlags.IMPLICIT @@ -285,24 +297,32 @@ class AppIdPermissionPolicy : SchemePolicy() { if (!canAdoptPermissions(packageName, originalPackageName)) { return@forEachIndexed } - newState.systemState.permissions.forEachIndexed permissions@ { - permissionIndex, permissionName, oldPermission -> + newState.systemState.permissions.forEachIndexed permissions@{ + permissionIndex, + permissionName, + oldPermission -> if (oldPermission.packageName != originalPackageName) { return@permissions } @Suppress("DEPRECATION") - val newPermissionInfo = PermissionInfo().apply { - name = oldPermission.permissionInfo.name - this.packageName = packageName - protectionLevel = oldPermission.permissionInfo.protectionLevel - } + val newPermissionInfo = + PermissionInfo().apply { + name = oldPermission.permissionInfo.name + this.packageName = packageName + protectionLevel = oldPermission.permissionInfo.protectionLevel + } // Different from the old implementation, which removes the GIDs upon permission // adoption, but adds them back on the next boot, we now just consistently keep the // GIDs. - val newPermission = oldPermission.copy( - permissionInfo = newPermissionInfo, isReconciled = false, appId = 0 - ) - newState.mutateSystemState().mutatePermissions() + val newPermission = + oldPermission.copy( + permissionInfo = newPermissionInfo, + isReconciled = false, + appId = 0 + ) + newState + .mutateSystemState() + .mutatePermissions() .putAt(permissionIndex, newPermission) changedPermissionNames += permissionName } @@ -313,18 +333,20 @@ class AppIdPermissionPolicy : SchemePolicy() { packageName: String, originalPackageName: String ): Boolean { - val originalPackageState = newState.externalState.packageStates[originalPackageName] - ?: return false + val originalPackageState = + newState.externalState.packageStates[originalPackageName] ?: return false if (!originalPackageState.isSystem) { Slog.w( - LOG_TAG, "Unable to adopt permissions from $originalPackageName to $packageName:" + + LOG_TAG, + "Unable to adopt permissions from $originalPackageName to $packageName:" + " original package not in system partition" ) return false } if (originalPackageState.androidPackage != null) { Slog.w( - LOG_TAG, "Unable to adopt permissions from $originalPackageName to $packageName:" + + LOG_TAG, + "Unable to adopt permissions from $originalPackageName to $packageName:" + " original package still exists" ) return false @@ -339,20 +361,25 @@ class AppIdPermissionPolicy : SchemePolicy() { val isInstantApp = packageState.userStates.allIndexed { _, _, it -> it.isInstantApp } if (isInstantApp) { Slog.w( - LOG_TAG, "Ignoring permission groups declared in package" + + LOG_TAG, + "Ignoring permission groups declared in package" + " ${packageState.packageName}: instant apps cannot declare permission groups" ) return } packageState.androidPackage!!.permissionGroups.forEachIndexed { _, parsedPermissionGroup -> - val newPermissionGroup = PackageInfoUtils.generatePermissionGroupInfo( - parsedPermissionGroup, PackageManager.GET_META_DATA.toLong() - )!! + val newPermissionGroup = + PackageInfoUtils.generatePermissionGroupInfo( + parsedPermissionGroup, + PackageManager.GET_META_DATA.toLong() + )!! // TODO: Clear permission state on group take-over? val permissionGroupName = newPermissionGroup.name val oldPermissionGroup = newState.systemState.permissionGroups[permissionGroupName] - if (oldPermissionGroup != null && - newPermissionGroup.packageName != oldPermissionGroup.packageName) { + if ( + oldPermissionGroup != null && + newPermissionGroup.packageName != oldPermissionGroup.packageName + ) { val newPackageName = newPermissionGroup.packageName val oldPackageName = oldPermissionGroup.packageName // Different from the old implementation, which defines permission group on @@ -361,7 +388,8 @@ class AppIdPermissionPolicy : SchemePolicy() { // to permissions so that we no longer need to rely on the scan order. if (!packageState.isSystem) { Slog.w( - LOG_TAG, "Ignoring permission group $permissionGroupName declared in" + + LOG_TAG, + "Ignoring permission group $permissionGroupName declared in" + " package $newPackageName: already declared in another" + " package $oldPackageName" ) @@ -369,14 +397,16 @@ class AppIdPermissionPolicy : SchemePolicy() { } if (newState.externalState.packageStates[oldPackageName]?.isSystem == true) { Slog.w( - LOG_TAG, "Ignoring permission group $permissionGroupName declared in" + + LOG_TAG, + "Ignoring permission group $permissionGroupName declared in" + " system package $newPackageName: already declared in another" + " system package $oldPackageName" ) return@forEachIndexed } Slog.w( - LOG_TAG, "Overriding permission group $permissionGroupName with" + + LOG_TAG, + "Overriding permission group $permissionGroupName with" + " new declaration in system package $newPackageName: originally" + " declared in another package $oldPackageName" ) @@ -393,20 +423,23 @@ class AppIdPermissionPolicy : SchemePolicy() { val androidPackage = packageState.androidPackage!! // This may not be the same package as the old permission because the old permission owner // can be different, hence using this somewhat strange name to prevent misuse. - val oldNewPackage = oldState.externalState.packageStates[packageState.packageName] - ?.androidPackage - val isPackageSigningChanged = oldNewPackage != null && - androidPackage.signingDetails != oldNewPackage.signingDetails + val oldNewPackage = + oldState.externalState.packageStates[packageState.packageName]?.androidPackage + val isPackageSigningChanged = + oldNewPackage != null && androidPackage.signingDetails != oldNewPackage.signingDetails androidPackage.permissions.forEachIndexed { _, parsedPermission -> - val newPermissionInfo = PackageInfoUtils.generatePermissionInfo( - parsedPermission, PackageManager.GET_META_DATA.toLong() - )!! + val newPermissionInfo = + PackageInfoUtils.generatePermissionInfo( + parsedPermission, + PackageManager.GET_META_DATA.toLong() + )!! val permissionName = newPermissionInfo.name - val oldPermission = if (parsedPermission.isTree) { - newState.systemState.permissionTrees[permissionName] - } else { - newState.systemState.permissions[permissionName] - } + val oldPermission = + if (parsedPermission.isTree) { + newState.systemState.permissionTrees[permissionName] + } else { + newState.systemState.permissions[permissionName] + } // Different from the old implementation, which may add an (incomplete) signature // permission inside another package's permission tree, we now consistently ignore such // permissions. @@ -414,128 +447,152 @@ class AppIdPermissionPolicy : SchemePolicy() { val newPackageName = newPermissionInfo.packageName if (permissionTree != null && newPackageName != permissionTree.packageName) { Slog.w( - LOG_TAG, "Ignoring permission $permissionName declared in package" + + LOG_TAG, + "Ignoring permission $permissionName declared in package" + " $newPackageName: base permission tree ${permissionTree.name} is" + " declared in another package ${permissionTree.packageName}" ) return@forEachIndexed } - val newPermission = if (oldPermission != null && - newPackageName != oldPermission.packageName) { - val oldPackageName = oldPermission.packageName - // Only allow system apps to redefine non-system permissions. - if (!packageState.isSystem) { - Slog.w( - LOG_TAG, "Ignoring permission $permissionName declared in package" + - " $newPackageName: already declared in another package" + - " $oldPackageName" - ) - return@forEachIndexed - } - if (oldPermission.type == Permission.TYPE_CONFIG && !oldPermission.isReconciled) { - // It's a config permission and has no owner, take ownership now. - oldPermission.copy( - permissionInfo = newPermissionInfo, isReconciled = true, - type = Permission.TYPE_MANIFEST, appId = packageState.appId - ) - } else if (newState.externalState.packageStates[oldPackageName]?.isSystem != true) { - Slog.w( - LOG_TAG, "Overriding permission $permissionName with new declaration in" + - " system package $newPackageName: originally declared in another" + - " package $oldPackageName" - ) - // Remove permission state on owner change. - newState.externalState.userIds.forEachIndexed { _, userId -> - newState.externalState.appIdPackageNames.forEachIndexed { _, appId, _ -> - setPermissionFlags(appId, userId, permissionName, 0) - } + val newPermission = + if (oldPermission != null && newPackageName != oldPermission.packageName) { + val oldPackageName = oldPermission.packageName + // Only allow system apps to redefine non-system permissions. + if (!packageState.isSystem) { + Slog.w( + LOG_TAG, + "Ignoring permission $permissionName declared in package" + + " $newPackageName: already declared in another package" + + " $oldPackageName" + ) + return@forEachIndexed } - // Different from the old implementation, which removes the GIDs upon permission - // override, but adds them back on the next boot, we now just consistently keep - // the GIDs. - Permission( - newPermissionInfo, true, Permission.TYPE_MANIFEST, packageState.appId, - oldPermission.gids, oldPermission.areGidsPerUser - ) - } else { - Slog.w( - LOG_TAG, "Ignoring permission $permissionName declared in system package" + - " $newPackageName: already declared in another system package" + - " $oldPackageName" - ) - return@forEachIndexed - } - } else { - if (oldPermission != null && oldPermission.isReconciled) { - val isPermissionGroupChanged = newPermissionInfo.isRuntime && - newPermissionInfo.group != null && - newPermissionInfo.group != oldPermission.groupName - val isPermissionProtectionChanged = - oldPermission.type != Permission.TYPE_CONFIG && ( - (newPermissionInfo.isRuntime && !oldPermission.isRuntime) || - (newPermissionInfo.isInternal && !oldPermission.isInternal) + if ( + oldPermission.type == Permission.TYPE_CONFIG && !oldPermission.isReconciled + ) { + // It's a config permission and has no owner, take ownership now. + oldPermission.copy( + permissionInfo = newPermissionInfo, + isReconciled = true, + type = Permission.TYPE_MANIFEST, + appId = packageState.appId ) - if (isPermissionGroupChanged || isPermissionProtectionChanged) { + } else if ( + newState.externalState.packageStates[oldPackageName]?.isSystem != true + ) { + Slog.w( + LOG_TAG, + "Overriding permission $permissionName with new declaration in" + + " system package $newPackageName: originally declared in another" + + " package $oldPackageName" + ) + // Remove permission state on owner change. newState.externalState.userIds.forEachIndexed { _, userId -> newState.externalState.appIdPackageNames.forEachIndexed { _, appId, _ -> - if (isPermissionGroupChanged) { - // We might auto-grant permissions if any permission of - // the group is already granted. Hence if the group of - // a granted permission changes we need to revoke it to - // avoid having permissions of the new group auto-granted. - Slog.w( - LOG_TAG, "Revoking runtime permission $permissionName for" + - " appId $appId and userId $userId as the permission" + - " group changed from ${oldPermission.groupName}" + - " to ${newPermissionInfo.group}" - ) - } - if (isPermissionProtectionChanged) { - Slog.w( - LOG_TAG, "Revoking permission $permissionName for" + - " appId $appId and userId $userId as the permission" + - " protection changed." - ) - } setPermissionFlags(appId, userId, permissionName, 0) } } + // Different from the old implementation, which removes the GIDs upon + // permission + // override, but adds them back on the next boot, we now just consistently + // keep + // the GIDs. + Permission( + newPermissionInfo, + true, + Permission.TYPE_MANIFEST, + packageState.appId, + oldPermission.gids, + oldPermission.areGidsPerUser + ) + } else { + Slog.w( + LOG_TAG, + "Ignoring permission $permissionName declared in system package" + + " $newPackageName: already declared in another system package" + + " $oldPackageName" + ) + return@forEachIndexed } - } - - // Different from the old implementation, which doesn't update the permission - // definition upon app update, but does update it on the next boot, we now - // consistently update the permission definition upon app update. - @Suppress("IfThenToElvis") - if (oldPermission != null) { - oldPermission.copy( - permissionInfo = newPermissionInfo, isReconciled = true, - type = Permission.TYPE_MANIFEST, appId = packageState.appId - ) } else { - Permission( - newPermissionInfo, true, Permission.TYPE_MANIFEST, packageState.appId - ) + if (oldPermission != null && oldPermission.isReconciled) { + val isPermissionGroupChanged = + newPermissionInfo.isRuntime && + newPermissionInfo.group != null && + newPermissionInfo.group != oldPermission.groupName + val isPermissionProtectionChanged = + oldPermission.type != Permission.TYPE_CONFIG && + ((newPermissionInfo.isRuntime && !oldPermission.isRuntime) || + (newPermissionInfo.isInternal && !oldPermission.isInternal)) + if (isPermissionGroupChanged || isPermissionProtectionChanged) { + newState.externalState.userIds.forEachIndexed { _, userId -> + newState.externalState.appIdPackageNames.forEachIndexed { + _, + appId, + _ -> + if (isPermissionGroupChanged) { + // We might auto-grant permissions if any permission of + // the group is already granted. Hence if the group of + // a granted permission changes we need to revoke it to + // avoid having permissions of the new group auto-granted. + Slog.w( + LOG_TAG, + "Revoking runtime permission $permissionName for" + + " appId $appId and userId $userId as the permission" + + " group changed from ${oldPermission.groupName}" + + " to ${newPermissionInfo.group}" + ) + } + if (isPermissionProtectionChanged) { + Slog.w( + LOG_TAG, + "Revoking permission $permissionName for" + + " appId $appId and userId $userId as the permission" + + " protection changed." + ) + } + setPermissionFlags(appId, userId, permissionName, 0) + } + } + } + } + + // Different from the old implementation, which doesn't update the permission + // definition upon app update, but does update it on the next boot, we now + // consistently update the permission definition upon app update. + @Suppress("IfThenToElvis") + if (oldPermission != null) { + oldPermission.copy( + permissionInfo = newPermissionInfo, + isReconciled = true, + type = Permission.TYPE_MANIFEST, + appId = packageState.appId + ) + } else { + Permission( + newPermissionInfo, + true, + Permission.TYPE_MANIFEST, + packageState.appId + ) + } } - } if (parsedPermission.isTree) { newState.mutateSystemState().mutatePermissionTrees()[permissionName] = newPermission } else { newState.mutateSystemState().mutatePermissions()[permissionName] = newPermission - val isPermissionChanged = oldPermission == null || - newPackageName != oldPermission.packageName || - newPermission.protectionLevel != oldPermission.protectionLevel || ( - oldPermission.isReconciled && ( - (newPermission.isSignature && isPackageSigningChanged) || ( - newPermission.isKnownSigner && - newPermission.knownCerts != oldPermission.knownCerts - ) || ( - newPermission.isRuntime && newPermission.groupName != null && - newPermission.groupName != oldPermission.groupName - ) - ) - ) + val isPermissionChanged = + oldPermission == null || + newPackageName != oldPermission.packageName || + newPermission.protectionLevel != oldPermission.protectionLevel || + (oldPermission.isReconciled && + ((newPermission.isSignature && isPackageSigningChanged) || + (newPermission.isKnownSigner && + newPermission.knownCerts != oldPermission.knownCerts) || + (newPermission.isRuntime && + newPermission.groupName != null && + newPermission.groupName != oldPermission.groupName))) if (isPermissionChanged) { changedPermissionNames += permissionName } @@ -552,39 +609,47 @@ class AppIdPermissionPolicy : SchemePolicy() { if (packageState != null && androidPackage == null) { return } - val disabledSystemPackage = newState.externalState.disabledSystemPackageStates[packageName] - ?.androidPackage + val disabledSystemPackage = + newState.externalState.disabledSystemPackageStates[packageName]?.androidPackage // Unlike in the previous implementation, we now also retain permission trees defined by // disabled system packages for consistency with permissions. newState.systemState.permissionTrees.forEachReversedIndexed { - permissionTreeIndex, permissionTreeName, permissionTree -> - if (permissionTree.packageName == packageName && ( - packageState == null || androidPackage!!.permissions.noneIndexed { _, it -> - it.isTree && it.name == permissionTreeName - } - ) && ( - disabledSystemPackage?.permissions?.anyIndexed { _, it -> - it.isTree && it.name == permissionTreeName - } != true - )) { + permissionTreeIndex, + permissionTreeName, + permissionTree -> + if ( + permissionTree.packageName == packageName && + (packageState == null || + androidPackage!!.permissions.noneIndexed { _, it -> + it.isTree && it.name == permissionTreeName + }) && + (disabledSystemPackage?.permissions?.anyIndexed { _, it -> + it.isTree && it.name == permissionTreeName + } != true) + ) { newState.mutateSystemState().mutatePermissionTrees().removeAt(permissionTreeIndex) } } newState.systemState.permissions.forEachReversedIndexed { - permissionIndex, permissionName, permission -> + permissionIndex, + permissionName, + permission -> val updatedPermission = updatePermissionIfDynamic(permission) - newState.mutateSystemState().mutatePermissions() + newState + .mutateSystemState() + .mutatePermissions() .putAt(permissionIndex, updatedPermission) - if (updatedPermission.packageName == packageName && ( - packageState == null || androidPackage!!.permissions.noneIndexed { _, it -> - !it.isTree && it.name == permissionName - } - ) && ( - disabledSystemPackage?.permissions?.anyIndexed { _, it -> - !it.isTree && it.name == permissionName - } != true - )) { + if ( + updatedPermission.packageName == packageName && + (packageState == null || + androidPackage!!.permissions.noneIndexed { _, it -> + !it.isTree && it.name == permissionName + }) && + (disabledSystemPackage?.permissions?.anyIndexed { _, it -> + !it.isTree && it.name == permissionName + } != true) + ) { // Different from the old implementation where we keep the permission state if the // permission is declared by a disabled system package (ag/15189282), we now // shouldn't be notified when the updated system package is removed but the disabled @@ -608,9 +673,12 @@ class AppIdPermissionPolicy : SchemePolicy() { val permissionTree = findPermissionTree(permission.name) ?: return permission @Suppress("DEPRECATION") return permission.copy( - permissionInfo = PermissionInfo(permission.permissionInfo).apply { - packageName = permissionTree.packageName - }, appId = permissionTree.appId, isReconciled = true + permissionInfo = + PermissionInfo(permission.permissionInfo).apply { + packageName = permissionTree.packageName + }, + appId = permissionTree.appId, + isReconciled = true ) } @@ -636,8 +704,9 @@ class AppIdPermissionPolicy : SchemePolicy() { } private fun MutateStateScope.revokePermissionsOnPackageUpdate(appId: Int) { - val hasOldPackage = appId in oldState.externalState.appIdPackageNames && - anyPackageInAppId(appId, oldState) { true } + val hasOldPackage = + appId in oldState.externalState.appIdPackageNames && + anyPackageInAppId(appId, oldState) { true } if (!hasOldPackage) { // Don't revoke anything if this isn't a package update, i.e. if information about the // old package isn't available. Notably, this also means skipping packages changed via @@ -650,46 +719,58 @@ class AppIdPermissionPolicy : SchemePolicy() { // app updated in an attempt to get unscoped storage. If so, revoke all storage permissions. val oldTargetSdkVersion = reducePackageInAppId(appId, Build.VERSION_CODES.CUR_DEVELOPMENT, oldState) { - targetSdkVersion, packageState -> + targetSdkVersion, + packageState -> targetSdkVersion.coerceAtMost(packageState.androidPackage!!.targetSdkVersion) } val newTargetSdkVersion = reducePackageInAppId(appId, Build.VERSION_CODES.CUR_DEVELOPMENT, newState) { - targetSdkVersion, packageState -> + targetSdkVersion, + packageState -> targetSdkVersion.coerceAtMost(packageState.androidPackage!!.targetSdkVersion) } @Suppress("ConvertTwoComparisonsToRangeCheck") - val isTargetSdkVersionDowngraded = oldTargetSdkVersion >= Build.VERSION_CODES.Q && - newTargetSdkVersion < Build.VERSION_CODES.Q + val isTargetSdkVersionDowngraded = + oldTargetSdkVersion >= Build.VERSION_CODES.Q && + newTargetSdkVersion < Build.VERSION_CODES.Q @Suppress("ConvertTwoComparisonsToRangeCheck") - val isTargetSdkVersionUpgraded = oldTargetSdkVersion < Build.VERSION_CODES.Q && - newTargetSdkVersion >= Build.VERSION_CODES.Q - val oldIsRequestLegacyExternalStorage = anyPackageInAppId(appId, oldState) { - it.androidPackage!!.isRequestLegacyExternalStorage - } - val newIsRequestLegacyExternalStorage = anyPackageInAppId(appId, newState) { - it.androidPackage!!.isRequestLegacyExternalStorage - } - val isNewlyRequestingLegacyExternalStorage = !isTargetSdkVersionUpgraded && - !oldIsRequestLegacyExternalStorage && newIsRequestLegacyExternalStorage - val shouldRevokeStorageAndMediaPermissions = isNewlyRequestingLegacyExternalStorage || - isTargetSdkVersionDowngraded + val isTargetSdkVersionUpgraded = + oldTargetSdkVersion < Build.VERSION_CODES.Q && + newTargetSdkVersion >= Build.VERSION_CODES.Q + val oldIsRequestLegacyExternalStorage = + anyPackageInAppId(appId, oldState) { + it.androidPackage!!.isRequestLegacyExternalStorage + } + val newIsRequestLegacyExternalStorage = + anyPackageInAppId(appId, newState) { + it.androidPackage!!.isRequestLegacyExternalStorage + } + val isNewlyRequestingLegacyExternalStorage = + !isTargetSdkVersionUpgraded && + !oldIsRequestLegacyExternalStorage && + newIsRequestLegacyExternalStorage + val shouldRevokeStorageAndMediaPermissions = + isNewlyRequestingLegacyExternalStorage || isTargetSdkVersionDowngraded if (shouldRevokeStorageAndMediaPermissions) { newState.userStates.forEachIndexed { _, userId, userState -> userState.appIdPermissionFlags[appId]?.forEachReversedIndexed { - _, permissionName, oldFlags -> + _, + permissionName, + oldFlags -> // Do not revoke the permission during an upgrade if it's POLICY_FIXED or // SYSTEM_FIXED. Otherwise the user cannot grant back the permission. - if (permissionName in STORAGE_AND_MEDIA_PERMISSIONS && - oldFlags.hasBits(PermissionFlags.RUNTIME_GRANTED) && - !oldFlags.hasAnyBit(SYSTEM_OR_POLICY_FIXED_MASK)) { + if ( + permissionName in STORAGE_AND_MEDIA_PERMISSIONS && + oldFlags.hasBits(PermissionFlags.RUNTIME_GRANTED) && + !oldFlags.hasAnyBit(SYSTEM_OR_POLICY_FIXED_MASK) + ) { Slog.v( - LOG_TAG, "Revoking storage permission: $permissionName for appId: " + + LOG_TAG, + "Revoking storage permission: $permissionName for appId: " + " $appId and user: $userId" ) - val newFlags = oldFlags andInv ( - PermissionFlags.RUNTIME_GRANTED or USER_SETTABLE_MASK - ) + val newFlags = + oldFlags andInv (PermissionFlags.RUNTIME_GRANTED or USER_SETTABLE_MASK) setPermissionFlags(appId, userId, permissionName, newFlags) } } @@ -704,9 +785,10 @@ class AppIdPermissionPolicy : SchemePolicy() { val externalState = newState.externalState externalState.userIds.forEachIndexed { _, userId -> externalState.appIdPackageNames.forEachIndexed { _, appId, _ -> - val isPermissionRequested = anyPackageInAppId(appId) { - permissionName in it.androidPackage!!.requestedPermissions - } + val isPermissionRequested = + anyPackageInAppId(appId) { + permissionName in it.androidPackage!!.requestedPermissions + } if (isPermissionRequested) { evaluatePermissionState(appId, userId, permissionName, installedPackageState) } @@ -720,7 +802,9 @@ class AppIdPermissionPolicy : SchemePolicy() { ) { newState.externalState.userIds.forEachIndexed { _, userId -> evaluateAllPermissionStatesForPackageAndUser( - packageState, userId, installedPackageState + packageState, + userId, + installedPackageState ) } } @@ -732,7 +816,10 @@ class AppIdPermissionPolicy : SchemePolicy() { ) { packageState.androidPackage?.requestedPermissions?.forEach { permissionName -> evaluatePermissionState( - packageState.appId, userId, permissionName, installedPackageState + packageState.appId, + userId, + permissionName, + installedPackageState ) } } @@ -779,57 +866,71 @@ class AppIdPermissionPolicy : SchemePolicy() { val wasGranted = oldFlags.hasBits(PermissionFlags.INSTALL_GRANTED) if (!wasGranted) { val wasRevoked = oldFlags.hasBits(PermissionFlags.INSTALL_REVOKED) - val isRequestedByInstalledPackage = installedPackageState != null && - permissionName in installedPackageState.androidPackage!!.requestedPermissions + val isRequestedByInstalledPackage = + installedPackageState != null && + permissionName in + installedPackageState.androidPackage!!.requestedPermissions val isRequestedBySystemPackage = requestingPackageStates.anyIndexed { _, it -> it.isSystem } - val isCompatibilityPermission = requestingPackageStates.anyIndexed { _, it -> - isCompatibilityPermissionForPackage(it.androidPackage!!, permissionName) - } + val isCompatibilityPermission = + requestingPackageStates.anyIndexed { _, it -> + isCompatibilityPermissionForPackage(it.androidPackage!!, permissionName) + } // If this is an existing, non-system package, // then we can't add any new permissions to it. // Except if this is a permission that was added to the platform - var newFlags = if (!wasRevoked || isRequestedByInstalledPackage || - isRequestedBySystemPackage || isCompatibilityPermission) { - PermissionFlags.INSTALL_GRANTED - } else { - PermissionFlags.INSTALL_REVOKED - } + var newFlags = + if ( + !wasRevoked || + isRequestedByInstalledPackage || + isRequestedBySystemPackage || + isCompatibilityPermission + ) { + PermissionFlags.INSTALL_GRANTED + } else { + PermissionFlags.INSTALL_REVOKED + } if (permission.isAppOp) { - newFlags = newFlags or ( - oldFlags and (PermissionFlags.ROLE or PermissionFlags.USER_SET) - ) + newFlags = + newFlags or + (oldFlags and (PermissionFlags.ROLE or PermissionFlags.USER_SET)) } setPermissionFlags(appId, userId, permissionName, newFlags) } } else if (permission.isSignature || permission.isInternal) { val wasProtectionGranted = oldFlags.hasBits(PermissionFlags.PROTECTION_GRANTED) - var newFlags = if (hasMissingPackage && wasProtectionGranted) { - // Keep the non-runtime permission grants for shared UID with missing androidPackage - PermissionFlags.PROTECTION_GRANTED - } else { - val mayGrantByPrivileged = !permission.isPrivileged || - requestingPackageStates.anyIndexed { _, it -> - checkPrivilegedPermissionAllowlist(it, permission) - } - val shouldGrantBySignature = permission.isSignature && - requestingPackageStates.anyIndexed { _, it -> - shouldGrantPermissionBySignature(it, permission) - } - val shouldGrantByProtectionFlags = requestingPackageStates.anyIndexed { _, it -> - shouldGrantPermissionByProtectionFlags(it, permission) - } - if (mayGrantByPrivileged && - (shouldGrantBySignature || shouldGrantByProtectionFlags)) { + var newFlags = + if (hasMissingPackage && wasProtectionGranted) { + // Keep the non-runtime permission grants for shared UID with missing + // androidPackage PermissionFlags.PROTECTION_GRANTED } else { - 0 + val mayGrantByPrivileged = + !permission.isPrivileged || + requestingPackageStates.anyIndexed { _, it -> + checkPrivilegedPermissionAllowlist(it, permission) + } + val shouldGrantBySignature = + permission.isSignature && + requestingPackageStates.anyIndexed { _, it -> + shouldGrantPermissionBySignature(it, permission) + } + val shouldGrantByProtectionFlags = + requestingPackageStates.anyIndexed { _, it -> + shouldGrantPermissionByProtectionFlags(it, permission) + } + if ( + mayGrantByPrivileged && + (shouldGrantBySignature || shouldGrantByProtectionFlags) + ) { + PermissionFlags.PROTECTION_GRANTED + } else { + 0 + } } - } if (permission.isAppOp) { - newFlags = newFlags or ( - oldFlags and (PermissionFlags.ROLE or PermissionFlags.USER_SET) - ) + newFlags = + newFlags or (oldFlags and (PermissionFlags.ROLE or PermissionFlags.USER_SET)) } // Different from the old implementation, which seemingly allows granting an // unallowlisted privileged permission via development or role but revokes it upon next @@ -840,9 +941,9 @@ class AppIdPermissionPolicy : SchemePolicy() { newFlags = newFlags or (oldFlags and PermissionFlags.RUNTIME_GRANTED) } if (permission.isRole) { - newFlags = newFlags or ( - oldFlags and (PermissionFlags.ROLE or PermissionFlags.RUNTIME_GRANTED) - ) + newFlags = + newFlags or + (oldFlags and (PermissionFlags.ROLE or PermissionFlags.RUNTIME_GRANTED)) } setPermissionFlags(appId, userId, permissionName, newFlags) } else if (permission.isRuntime) { @@ -850,7 +951,9 @@ class AppIdPermissionPolicy : SchemePolicy() { val wasRevoked = newFlags != 0 && !PermissionFlags.isPermissionGranted(newFlags) val targetSdkVersion = requestingPackageStates.reduceIndexed(Build.VERSION_CODES.CUR_DEVELOPMENT) { - targetSdkVersion, _, packageState -> + targetSdkVersion, + _, + packageState -> targetSdkVersion.coerceAtMost(packageState.androidPackage!!.targetSdkVersion) } if (targetSdkVersion < Build.VERSION_CODES.M) { @@ -883,23 +986,27 @@ class AppIdPermissionPolicy : SchemePolicy() { } } val wasGrantedByImplicit = newFlags.hasBits(PermissionFlags.IMPLICIT_GRANTED) - val isLeanbackNotificationsPermission = newState.externalState.isLeanback && - permissionName in NOTIFICATIONS_PERMISSIONS - val isImplicitPermission = requestingPackageStates.anyIndexed { _, it -> - permissionName in it.androidPackage!!.implicitPermissions - } - val sourcePermissions = newState.externalState - .implicitToSourcePermissions[permissionName] - val isAnySourcePermissionNonRuntime = sourcePermissions?.anyIndexed { - _, sourcePermissionName -> - val sourcePermission = newState.systemState.permissions[sourcePermissionName] - checkNotNull(sourcePermission) { - "Unknown source permission $sourcePermissionName in split permissions" + val isLeanbackNotificationsPermission = + newState.externalState.isLeanback && permissionName in NOTIFICATIONS_PERMISSIONS + val isImplicitPermission = + requestingPackageStates.anyIndexed { _, it -> + permissionName in it.androidPackage!!.implicitPermissions + } + val sourcePermissions = + newState.externalState.implicitToSourcePermissions[permissionName] + val isAnySourcePermissionNonRuntime = + sourcePermissions?.anyIndexed { _, sourcePermissionName -> + val sourcePermission = + newState.systemState.permissions[sourcePermissionName] + checkNotNull(sourcePermission) { + "Unknown source permission $sourcePermissionName in split permissions" + } + !sourcePermission.isRuntime } - !sourcePermission.isRuntime - } ?: false - val shouldGrantByImplicit = isLeanbackNotificationsPermission || - (isImplicitPermission && isAnySourcePermissionNonRuntime) + ?: false + val shouldGrantByImplicit = + isLeanbackNotificationsPermission || + (isImplicitPermission && isAnySourcePermissionNonRuntime) if (shouldGrantByImplicit) { newFlags = newFlags or PermissionFlags.IMPLICIT_GRANTED if (wasRevoked) { @@ -907,26 +1014,31 @@ class AppIdPermissionPolicy : SchemePolicy() { } } else { newFlags = newFlags andInv PermissionFlags.IMPLICIT_GRANTED - if ((wasGrantedByLegacy || wasGrantedByImplicit) && - newFlags.hasBits(PermissionFlags.APP_OP_REVOKED)) { + if ( + (wasGrantedByLegacy || wasGrantedByImplicit) && + newFlags.hasBits(PermissionFlags.APP_OP_REVOKED) + ) { // The permission was granted from a compatibility grant or an implicit // grant, however this flag might still be set if the user denied this // permission in the settings. Hence upon app upgrade and when this // permission is no longer LEGACY_GRANTED or IMPLICIT_GRANTED and we revoke // the permission, we want to remove this flag so that the app can request // the permission again. - newFlags = newFlags andInv ( - PermissionFlags.RUNTIME_GRANTED or PermissionFlags.APP_OP_REVOKED - ) + newFlags = + newFlags andInv + (PermissionFlags.RUNTIME_GRANTED or PermissionFlags.APP_OP_REVOKED) } } if (!isImplicitPermission && hasImplicitFlag) { newFlags = newFlags andInv PermissionFlags.IMPLICIT var shouldRetainAsNearbyDevices = false if (permissionName in NEARBY_DEVICES_PERMISSIONS) { - val accessBackgroundLocationFlags = getPermissionFlags( - appId, userId, Manifest.permission.ACCESS_BACKGROUND_LOCATION - ) + val accessBackgroundLocationFlags = + getPermissionFlags( + appId, + userId, + Manifest.permission.ACCESS_BACKGROUND_LOCATION + ) shouldRetainAsNearbyDevices = PermissionFlags.isAppOpGranted(accessBackgroundLocationFlags) && !accessBackgroundLocationFlags.hasBits(PermissionFlags.IMPLICIT) @@ -937,46 +1049,57 @@ class AppIdPermissionPolicy : SchemePolicy() { newFlags = newFlags or PermissionFlags.RUNTIME_GRANTED } } else { - newFlags = newFlags andInv ( - PermissionFlags.RUNTIME_GRANTED or PermissionFlags.USER_SET or - PermissionFlags.USER_FIXED - ) + newFlags = + newFlags andInv + (PermissionFlags.RUNTIME_GRANTED or + PermissionFlags.USER_SET or + PermissionFlags.USER_FIXED) } } } val wasExempt = newFlags.hasAnyBit(PermissionFlags.MASK_EXEMPT) val wasRestricted = newFlags.hasAnyBit(PermissionFlags.MASK_RESTRICTED) - val isExempt = if (permission.isHardOrSoftRestricted && !wasExempt && !wasRestricted) { - // All restricted permissions start as exempt. If there's an installer for the - // package, we will drop this UPGRADE_EXEMPT flag when we receive the - // onPackageInstalled() callback and set up the INSTALLER_EXEMPT flags. - // UPGRADE_EXEMPT is chosen instead of other flags because it is the same flag that - // was assigned to pre-installed apps in RuntimePermissionsUpgradeController, and to - // apps with missing permission state. - // This way we make sure both pre-installed apps, and apps updated/installed after - // a rollback snapshot is taken, can get the allowlist for permissions that won't be - // allowlisted otherwise. - newFlags = newFlags or PermissionFlags.UPGRADE_EXEMPT - true - } else { - wasExempt - } - newFlags = if (permission.isHardRestricted && !isExempt) { - newFlags or PermissionFlags.RESTRICTION_REVOKED - } else { - newFlags andInv PermissionFlags.RESTRICTION_REVOKED - } - newFlags = if (permission.isSoftRestricted && !isExempt) { - newFlags or PermissionFlags.SOFT_RESTRICTED - } else { - newFlags andInv PermissionFlags.SOFT_RESTRICTED - } + val isExempt = + if (permission.isHardOrSoftRestricted && !wasExempt && !wasRestricted) { + // All restricted permissions start as exempt. If there's an installer for the + // package, we will drop this UPGRADE_EXEMPT flag when we receive the + // onPackageInstalled() callback and set up the INSTALLER_EXEMPT flags. + // UPGRADE_EXEMPT is chosen instead of other flags because it is the same flag + // that + // was assigned to pre-installed apps in RuntimePermissionsUpgradeController, + // and to + // apps with missing permission state. + // This way we make sure both pre-installed apps, and apps updated/installed + // after + // a rollback snapshot is taken, can get the allowlist for permissions that + // won't be + // allowlisted otherwise. + newFlags = newFlags or PermissionFlags.UPGRADE_EXEMPT + true + } else { + wasExempt + } + newFlags = + if (permission.isHardRestricted && !isExempt) { + newFlags or PermissionFlags.RESTRICTION_REVOKED + } else { + newFlags andInv PermissionFlags.RESTRICTION_REVOKED + } + newFlags = + if (permission.isSoftRestricted && !isExempt) { + newFlags or PermissionFlags.SOFT_RESTRICTED + } else { + newFlags andInv PermissionFlags.SOFT_RESTRICTED + } setPermissionFlags(appId, userId, permissionName, newFlags) } else { - Slog.e(LOG_TAG, "Unknown protection level ${permission.protectionLevel}" + - "for permission ${permission.name} while evaluating permission state" + - "for appId $appId and userId $userId") + Slog.e( + LOG_TAG, + "Unknown protection level ${permission.protectionLevel}" + + "for permission ${permission.name} while evaluating permission state" + + "for appId $appId and userId $userId" + ) } } @@ -985,7 +1108,7 @@ class AppIdPermissionPolicy : SchemePolicy() { forEachPackageInAppId(appId) { implicitPermissions += it.androidPackage!!.implicitPermissions } - implicitPermissions.forEachIndexed implicitPermissions@ { _, implicitPermissionName -> + implicitPermissions.forEachIndexed implicitPermissions@{ _, implicitPermissionName -> val implicitPermission = newState.systemState.permissions[implicitPermissionName] checkNotNull(implicitPermission) { "Unknown implicit permission $implicitPermissionName in split permissions" @@ -999,10 +1122,11 @@ class AppIdPermissionPolicy : SchemePolicy() { if (!isNewPermission) { return@implicitPermissions } - val sourcePermissions = newState.externalState - .implicitToSourcePermissions[implicitPermissionName] ?: return@implicitPermissions + val sourcePermissions = + newState.externalState.implicitToSourcePermissions[implicitPermissionName] + ?: return@implicitPermissions var newFlags = getPermissionFlags(appId, userId, implicitPermissionName) - sourcePermissions.forEachIndexed sourcePermissions@ { _, sourcePermissionName -> + sourcePermissions.forEachIndexed sourcePermissions@{ _, sourcePermissionName -> val sourcePermission = newState.systemState.permissions[sourcePermissionName] checkNotNull(sourcePermission) { "Unknown source permission $sourcePermissionName in split permissions" @@ -1032,11 +1156,14 @@ class AppIdPermissionPolicy : SchemePolicy() { permissionName: String ): Boolean { for (compatibilityPermission in CompatibilityPermissionInfo.COMPAT_PERMS) { - if (compatibilityPermission.name == permissionName && - androidPackage.targetSdkVersion < compatibilityPermission.sdkVersion) { + if ( + compatibilityPermission.name == permissionName && + androidPackage.targetSdkVersion < compatibilityPermission.sdkVersion + ) { Slog.i( - LOG_TAG, "Auto-granting $permissionName to old package" + - " ${androidPackage.packageName}" + LOG_TAG, + "Auto-granting $permissionName to old package" + + " ${androidPackage.packageName}" ) return true } @@ -1058,15 +1185,23 @@ class AppIdPermissionPolicy : SchemePolicy() { // and the defining package still trusts the old certificate for permissions // - or it shares the above relationships with the system package val packageSigningDetails = packageState.androidPackage!!.signingDetails - val sourceSigningDetails = newState.externalState - .packageStates[permission.packageName]?.androidPackage?.signingDetails - val platformSigningDetails = newState.externalState - .packageStates[PLATFORM_PACKAGE_NAME]!!.androidPackage!!.signingDetails - return sourceSigningDetails?.hasCommonSignerWithCapability(packageSigningDetails, - SigningDetails.CertCapabilities.PERMISSION) == true || + val sourceSigningDetails = + newState.externalState.packageStates[permission.packageName] + ?.androidPackage + ?.signingDetails + val platformSigningDetails = + newState.externalState.packageStates[PLATFORM_PACKAGE_NAME]!! + .androidPackage!! + .signingDetails + return sourceSigningDetails?.hasCommonSignerWithCapability( + packageSigningDetails, + SigningDetails.CertCapabilities.PERMISSION + ) == true || packageSigningDetails.hasAncestorOrSelf(platformSigningDetails) || - platformSigningDetails.checkCapability(packageSigningDetails, - SigningDetails.CertCapabilities.PERMISSION) + platformSigningDetails.checkCapability( + packageSigningDetails, + SigningDetails.CertCapabilities.PERMISSION + ) } private fun MutateStateScope.checkPrivilegedPermissionAllowlist( @@ -1082,8 +1217,9 @@ class AppIdPermissionPolicy : SchemePolicy() { if (!(packageState.isSystem && packageState.isPrivileged)) { return true } - if (permission.packageName !in - newState.externalState.privilegedPermissionAllowlistPackages) { + if ( + permission.packageName !in newState.externalState.privilegedPermissionAllowlistPackages + ) { return true } val allowlistState = getPrivilegedPermissionAllowlistState(packageState, permission.name) @@ -1099,13 +1235,15 @@ class AppIdPermissionPolicy : SchemePolicy() { // Apps that are in updated apex's do not need to be allowlisted if (!packageState.isApkInUpdatedApex) { Slog.w( - LOG_TAG, "Privileged permission ${permission.name} for package" + - " ${packageState.packageName} (${packageState.path}) not in" + - " privileged permission allowlist" + LOG_TAG, + "Privileged permission ${permission.name} for package" + + " ${packageState.packageName} (${packageState.path}) not in" + + " privileged permission allowlist" ) if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) { - privilegedPermissionAllowlistViolations += "${packageState.packageName}" + - " (${packageState.path}): ${permission.name}" + privilegedPermissionAllowlistViolations += + "${packageState.packageName}" + + " (${packageState.path}): ${permission.name}" } } } @@ -1124,32 +1262,40 @@ class AppIdPermissionPolicy : SchemePolicy() { val apexModuleName = packageState.apexModuleName val packageName = packageState.packageName return when { - packageState.isVendor -> permissionAllowlist.getVendorPrivilegedAppAllowlistState( - packageName, permissionName - ) - packageState.isProduct -> permissionAllowlist.getProductPrivilegedAppAllowlistState( - packageName, permissionName - ) + packageState.isVendor -> + permissionAllowlist.getVendorPrivilegedAppAllowlistState( + packageName, + permissionName + ) + packageState.isProduct -> + permissionAllowlist.getProductPrivilegedAppAllowlistState( + packageName, + permissionName + ) packageState.isSystemExt -> permissionAllowlist.getSystemExtPrivilegedAppAllowlistState( - packageName, permissionName + packageName, + permissionName ) apexModuleName != null -> { - val nonApexAllowlistState = permissionAllowlist.getPrivilegedAppAllowlistState( - packageName, permissionName - ) + val nonApexAllowlistState = + permissionAllowlist.getPrivilegedAppAllowlistState(packageName, permissionName) if (nonApexAllowlistState != null) { // TODO(andreionea): Remove check as soon as all apk-in-apex // permission allowlists are migrated. Slog.w( - LOG_TAG, "Package $packageName is an APK in APEX but has permission" + + LOG_TAG, + "Package $packageName is an APK in APEX but has permission" + " allowlist on the system image, please bundle the allowlist in the" + " $apexModuleName APEX instead" ) } - val apexAllowlistState = permissionAllowlist.getApexPrivilegedAppAllowlistState( - apexModuleName, packageName, permissionName - ) + val apexAllowlistState = + permissionAllowlist.getApexPrivilegedAppAllowlistState( + apexModuleName, + packageName, + permissionName + ) apexAllowlistState ?: nonApexAllowlistState } else -> permissionAllowlist.getPrivilegedAppAllowlistState(packageName, permissionName) @@ -1208,18 +1354,19 @@ class AppIdPermissionPolicy : SchemePolicy() { val knownPackages = newState.externalState.knownPackages val packageName = packageState.packageName if ((permission.isPrivileged || permission.isOem) && packageState.isSystem) { - val shouldGrant = if (packageState.isUpdatedSystemApp) { - // For updated system applications, a privileged/oem permission - // is granted only if it had been defined by the original application. - val disabledSystemPackageState = newState.externalState - .disabledSystemPackageStates[packageState.packageName] - val disabledSystemPackage = disabledSystemPackageState?.androidPackage - disabledSystemPackage != null && - permission.name in disabledSystemPackage.requestedPermissions && - shouldGrantPrivilegedOrOemPermission(disabledSystemPackageState, permission) - } else { - shouldGrantPrivilegedOrOemPermission(packageState, permission) - } + val shouldGrant = + if (packageState.isUpdatedSystemApp) { + // For updated system applications, a privileged/oem permission + // is granted only if it had been defined by the original application. + val disabledSystemPackageState = + newState.externalState.disabledSystemPackageStates[packageState.packageName] + val disabledSystemPackage = disabledSystemPackageState?.androidPackage + disabledSystemPackage != null && + permission.name in disabledSystemPackage.requestedPermissions && + shouldGrantPrivilegedOrOemPermission(disabledSystemPackageState, permission) + } else { + shouldGrantPrivilegedOrOemPermission(packageState, permission) + } if (shouldGrant) { return true } @@ -1230,16 +1377,18 @@ class AppIdPermissionPolicy : SchemePolicy() { // we still want to blindly grant it to old apps. return true } - if (permission.isInstaller && ( - packageName in knownPackages[KnownPackages.PACKAGE_INSTALLER]!! || - packageName in knownPackages[KnownPackages.PACKAGE_PERMISSION_CONTROLLER]!! - )) { + if ( + permission.isInstaller && + (packageName in knownPackages[KnownPackages.PACKAGE_INSTALLER]!! || + packageName in knownPackages[KnownPackages.PACKAGE_PERMISSION_CONTROLLER]!!) + ) { // If this permission is to be granted to the system installer and // this app is an installer or permission controller, then it gets the permission. return true } - if (permission.isVerifier && - packageName in knownPackages[KnownPackages.PACKAGE_VERIFIER]!!) { + if ( + permission.isVerifier && packageName in knownPackages[KnownPackages.PACKAGE_VERIFIER]!! + ) { // If this permission is to be granted to the system verifier and // this app is a verifier, then it gets the permission. return true @@ -1248,53 +1397,67 @@ class AppIdPermissionPolicy : SchemePolicy() { // Any pre-installed system app is allowed to get this permission. return true } - if (permission.isKnownSigner && - androidPackage.signingDetails.hasAncestorOrSelfWithDigest(permission.knownCerts)) { + if ( + permission.isKnownSigner && + androidPackage.signingDetails.hasAncestorOrSelfWithDigest(permission.knownCerts) + ) { // If the permission is to be granted to a known signer then check if any of this // app's signing certificates are in the trusted certificate digest Set. return true } - if (permission.isSetup && - packageName in knownPackages[KnownPackages.PACKAGE_SETUP_WIZARD]!!) { + if ( + permission.isSetup && packageName in knownPackages[KnownPackages.PACKAGE_SETUP_WIZARD]!! + ) { // If this permission is to be granted to the system setup wizard and // this app is a setup wizard, then it gets the permission. return true } - if (permission.isSystemTextClassifier && - packageName in knownPackages[KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER]!!) { + if ( + permission.isSystemTextClassifier && + packageName in knownPackages[KnownPackages.PACKAGE_SYSTEM_TEXT_CLASSIFIER]!! + ) { // Special permissions for the system default text classifier. return true } - if (permission.isConfigurator && - packageName in knownPackages[KnownPackages.PACKAGE_CONFIGURATOR]!!) { + if ( + permission.isConfigurator && + packageName in knownPackages[KnownPackages.PACKAGE_CONFIGURATOR]!! + ) { // Special permissions for the device configurator. return true } - if (permission.isIncidentReportApprover && - packageName in knownPackages[KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER]!!) { + if ( + permission.isIncidentReportApprover && + packageName in knownPackages[KnownPackages.PACKAGE_INCIDENT_REPORT_APPROVER]!! + ) { // If this permission is to be granted to the incident report approver and // this app is the incident report approver, then it gets the permission. return true } - if (permission.isAppPredictor && - packageName in knownPackages[KnownPackages.PACKAGE_APP_PREDICTOR]!!) { + if ( + permission.isAppPredictor && + packageName in knownPackages[KnownPackages.PACKAGE_APP_PREDICTOR]!! + ) { // Special permissions for the system app predictor. return true } - if (permission.isCompanion && - packageName in knownPackages[KnownPackages.PACKAGE_COMPANION]!!) { + if ( + permission.isCompanion && + packageName in knownPackages[KnownPackages.PACKAGE_COMPANION]!! + ) { // Special permissions for the system companion device manager. return true } - if (permission.isRetailDemo && - packageName in knownPackages[KnownPackages.PACKAGE_RETAIL_DEMO]!!) { + if ( + permission.isRetailDemo && + packageName in knownPackages[KnownPackages.PACKAGE_RETAIL_DEMO]!! + ) { // Special permission granted only to the OEM specified retail demo app. // Note that the original code was passing app ID as UID, so this behavior is kept // unchanged. return true } - if (permission.isRecents && - packageName in knownPackages[KnownPackages.PACKAGE_RECENTS]!!) { + if (permission.isRecents && packageName in knownPackages[KnownPackages.PACKAGE_RECENTS]!!) { // Special permission for the recents app. return true } @@ -1319,9 +1482,10 @@ class AppIdPermissionPolicy : SchemePolicy() { // flag. if (packageState.isVendor && !permission.isVendorPrivileged) { Slog.w( - LOG_TAG, "Permission $permissionName cannot be granted to privileged" + - " vendor app $packageName because it isn't a vendorPrivileged" + - " permission" + LOG_TAG, + "Permission $permissionName cannot be granted to privileged" + + " vendor app $packageName because it isn't a vendorPrivileged" + + " permission" ) return false } @@ -1330,8 +1494,11 @@ class AppIdPermissionPolicy : SchemePolicy() { } permission.isOem -> { if (packageState.isOem) { - val allowlistState = newState.externalState.permissionAllowlist - .getOemAppAllowlistState(packageName, permissionName) + val allowlistState = + newState.externalState.permissionAllowlist.getOemAppAllowlistState( + packageName, + permissionName + ) checkNotNull(allowlistState) { "OEM permission $permissionName requested by package" + " $packageName must be explicitly declared granted or not" @@ -1358,13 +1525,18 @@ class AppIdPermissionPolicy : SchemePolicy() { val appId = externalState.packageStates[packageName]?.appId ?: continue newState.userStates.forEachIndexed { _, userId, _ -> evaluatePermissionState( - appId, userId, Manifest.permission.PACKAGE_USAGE_STATS, null + appId, + userId, + Manifest.permission.PACKAGE_USAGE_STATS, + null ) } } if (!privilegedPermissionAllowlistViolations.isEmpty()) { - throw IllegalStateException("Signature|privileged permissions not in privileged" + - " permission allowlist: $privilegedPermissionAllowlistViolations") + throw IllegalStateException( + "Signature|privileged permissions not in privileged" + + " permission allowlist: $privilegedPermissionAllowlistViolations" + ) } } @@ -1389,10 +1561,14 @@ class AppIdPermissionPolicy : SchemePolicy() { fun GetStateScope.findPermissionTree(permissionName: String): Permission? = state.systemState.permissionTrees.firstNotNullOfOrNullIndexed { - _, permissionTreeName, permissionTree -> - if (permissionName.startsWith(permissionTreeName) && - permissionName.length > permissionTreeName.length && - permissionName[permissionTreeName.length] == '.') { + _, + permissionTreeName, + permissionTree -> + if ( + permissionName.startsWith(permissionTreeName) && + permissionName.length > permissionTreeName.length && + permissionName[permissionTreeName.length] == '.' + ) { permissionTree } else { null @@ -1403,15 +1579,11 @@ class AppIdPermissionPolicy : SchemePolicy() { newState.mutateSystemState().mutatePermissionTrees()[permission.name] = permission } - /** - * returns all permission group definitions available in the system - */ + /** returns all permission group definitions available in the system */ fun GetStateScope.getPermissionGroups(): IndexedMap<String, PermissionGroupInfo> = state.systemState.permissionGroups - /** - * returns all permission definitions available in the system - */ + /** returns all permission definitions available in the system */ fun GetStateScope.getPermissions(): IndexedMap<String, Permission> = state.systemState.permissions @@ -1430,11 +1602,8 @@ class AppIdPermissionPolicy : SchemePolicy() { fun GetStateScope.getUidPermissionFlags(appId: Int, userId: Int): IndexedMap<String, Int>? = state.userStates[userId]?.appIdPermissionFlags?.get(appId) - fun GetStateScope.getPermissionFlags( - appId: Int, - userId: Int, - permissionName: String - ): Int = getPermissionFlags(state, appId, userId, permissionName) + fun GetStateScope.getPermissionFlags(appId: Int, userId: Int, permissionName: String): Int = + getPermissionFlags(state, appId, userId, permissionName) private fun MutateStateScope.getOldStatePermissionFlags( appId: Int, @@ -1465,8 +1634,10 @@ class AppIdPermissionPolicy : SchemePolicy() { flagMask: Int, flagValues: Int ): Boolean { - val oldFlags = newState.userStates[userId]!!.appIdPermissionFlags[appId] - .getWithDefault(permissionName, 0) + val oldFlags = + newState.userStates[userId]!! + .appIdPermissionFlags[appId] + .getWithDefault(permissionName, 0) val newFlags = (oldFlags andInv flagMask) or (flagValues and flagMask) if (oldFlags == newFlags) { return false @@ -1517,38 +1688,37 @@ class AppIdPermissionPolicy : SchemePolicy() { private const val PLATFORM_PACKAGE_NAME = "android" // A set of permissions that we don't want to revoke when they are no longer implicit. - private val RETAIN_IMPLICIT_FLAGS_PERMISSIONS = indexedSetOf( - Manifest.permission.ACCESS_MEDIA_LOCATION, - Manifest.permission.ACTIVITY_RECOGNITION, - Manifest.permission.READ_MEDIA_AUDIO, - Manifest.permission.READ_MEDIA_IMAGES, - Manifest.permission.READ_MEDIA_VIDEO, - ) - - private val NEARBY_DEVICES_PERMISSIONS = indexedSetOf( - Manifest.permission.BLUETOOTH_ADVERTISE, - Manifest.permission.BLUETOOTH_CONNECT, - Manifest.permission.BLUETOOTH_SCAN, - Manifest.permission.NEARBY_WIFI_DEVICES - ) + private val RETAIN_IMPLICIT_FLAGS_PERMISSIONS = + indexedSetOf( + Manifest.permission.ACCESS_MEDIA_LOCATION, + Manifest.permission.ACTIVITY_RECOGNITION, + Manifest.permission.READ_MEDIA_AUDIO, + Manifest.permission.READ_MEDIA_IMAGES, + Manifest.permission.READ_MEDIA_VIDEO, + ) - private val NOTIFICATIONS_PERMISSIONS = indexedSetOf( - Manifest.permission.POST_NOTIFICATIONS - ) + private val NEARBY_DEVICES_PERMISSIONS = + indexedSetOf( + Manifest.permission.BLUETOOTH_ADVERTISE, + Manifest.permission.BLUETOOTH_CONNECT, + Manifest.permission.BLUETOOTH_SCAN, + Manifest.permission.NEARBY_WIFI_DEVICES + ) - private val STORAGE_AND_MEDIA_PERMISSIONS = indexedSetOf( - Manifest.permission.READ_EXTERNAL_STORAGE, - Manifest.permission.WRITE_EXTERNAL_STORAGE, - Manifest.permission.READ_MEDIA_AUDIO, - Manifest.permission.READ_MEDIA_VIDEO, - Manifest.permission.READ_MEDIA_IMAGES, - Manifest.permission.ACCESS_MEDIA_LOCATION, - Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED - ) + private val NOTIFICATIONS_PERMISSIONS = indexedSetOf(Manifest.permission.POST_NOTIFICATIONS) + + private val STORAGE_AND_MEDIA_PERMISSIONS = + indexedSetOf( + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.READ_MEDIA_AUDIO, + Manifest.permission.READ_MEDIA_VIDEO, + Manifest.permission.READ_MEDIA_IMAGES, + Manifest.permission.ACCESS_MEDIA_LOCATION, + Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED + ) - /** - * Mask for all permission flags that can be set by the user - */ + /** Mask for all permission flags that can be set by the user */ private const val USER_SETTABLE_MASK = PermissionFlags.USER_SET or PermissionFlags.USER_FIXED or @@ -1558,16 +1728,14 @@ class AppIdPermissionPolicy : SchemePolicy() { PermissionFlags.USER_SELECTED /** - * Mask for all permission flags that imply we shouldn't automatically modify the - * permission grant state. + * Mask for all permission flags that imply we shouldn't automatically modify the permission + * grant state. */ private const val SYSTEM_OR_POLICY_FIXED_MASK = PermissionFlags.SYSTEM_FIXED or PermissionFlags.POLICY_FIXED } - /** - * Listener for permission flags changes. - */ + /** Listener for permission flags changes. */ abstract class OnPermissionFlagsChangedListener { /** * Called when a permission flags change has been made to the upcoming new state. diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt index b644d8fe7388..edacda03f277 100644 --- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt +++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionUpgrade.kt @@ -32,7 +32,6 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { * Upgrade the package permissions, if needed. * * @param version package version - * * @see [com.android.server.permission.access.util.PackageVersionMigration.getVersion] */ fun MutateStateScope.upgradePackageState( @@ -43,7 +42,8 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { val packageName = packageState.packageName if (version <= 3) { Slog.v( - LOG_TAG, "Allowlisting and upgrading background location permission for " + + LOG_TAG, + "Allowlisting and upgrading background location permission for " + "package: $packageName, version: $version, user:$userId" ) allowlistRestrictedPermissions(packageState, userId) @@ -51,7 +51,8 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { } if (version <= 10) { Slog.v( - LOG_TAG, "Upgrading access media location permission for package: $packageName" + + LOG_TAG, + "Upgrading access media location permission for package: $packageName" + ", version: $version, user: $userId" ) upgradeAccessMediaLocationPermission(packageState, userId) @@ -59,7 +60,8 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { // TODO Enable isAtLeastT check, when moving subsystem to mainline. if (version <= 12 /*&& SdkLevel.isAtLeastT()*/) { Slog.v( - LOG_TAG, "Upgrading scoped permissions for package: $packageName" + + LOG_TAG, + "Upgrading scoped permissions for package: $packageName" + ", version: $version, user: $userId" ) upgradeAuralVisualMediaPermissions(packageState, userId) @@ -67,7 +69,8 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { // TODO Enable isAtLeastU check, when moving subsystem to mainline. if (version <= 14 /*&& SdkLevel.isAtLeastU()*/) { Slog.v( - LOG_TAG, "Upgrading visual media permission for package: $packageName" + + LOG_TAG, + "Upgrading visual media permission for package: $packageName" + ", version: $version, user: $userId" ) upgradeUserSelectedVisualMediaPermission(packageState, userId) @@ -84,8 +87,11 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { if (permissionName in LEGACY_RESTRICTED_PERMISSIONS) { with(policy) { updatePermissionFlags( - packageState.appId, userId, permissionName, - PermissionFlags.UPGRADE_EXEMPT, PermissionFlags.UPGRADE_EXEMPT + packageState.appId, + userId, + permissionName, + PermissionFlags.UPGRADE_EXEMPT, + PermissionFlags.UPGRADE_EXEMPT ) } } @@ -96,21 +102,27 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { packageState: PackageState, userId: Int ) { - if (Manifest.permission.ACCESS_BACKGROUND_LOCATION in - packageState.androidPackage!!.requestedPermissions) { + if ( + Manifest.permission.ACCESS_BACKGROUND_LOCATION in + packageState.androidPackage!!.requestedPermissions + ) { val appId = packageState.appId - val accessFineLocationFlags = with(policy) { - getPermissionFlags(appId, userId, Manifest.permission.ACCESS_FINE_LOCATION) - } - val accessCoarseLocationFlags = with(policy) { - getPermissionFlags(appId, userId, Manifest.permission.ACCESS_COARSE_LOCATION) - } + val accessFineLocationFlags = + with(policy) { + getPermissionFlags(appId, userId, Manifest.permission.ACCESS_FINE_LOCATION) + } + val accessCoarseLocationFlags = + with(policy) { + getPermissionFlags(appId, userId, Manifest.permission.ACCESS_COARSE_LOCATION) + } val isForegroundLocationGranted = PermissionFlags.isAppOpGranted(accessFineLocationFlags) || PermissionFlags.isAppOpGranted(accessCoarseLocationFlags) if (isForegroundLocationGranted) { grantRuntimePermission( - packageState, userId, Manifest.permission.ACCESS_BACKGROUND_LOCATION + packageState, + userId, + Manifest.permission.ACCESS_BACKGROUND_LOCATION ) } } @@ -120,24 +132,29 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { packageState: PackageState, userId: Int ) { - if (Manifest.permission.ACCESS_MEDIA_LOCATION in - packageState.androidPackage!!.requestedPermissions) { - val flags = with(policy) { - getPermissionFlags( - packageState.appId, userId, Manifest.permission.READ_EXTERNAL_STORAGE - ) - } + if ( + Manifest.permission.ACCESS_MEDIA_LOCATION in + packageState.androidPackage!!.requestedPermissions + ) { + val flags = + with(policy) { + getPermissionFlags( + packageState.appId, + userId, + Manifest.permission.READ_EXTERNAL_STORAGE + ) + } if (PermissionFlags.isAppOpGranted(flags)) { grantRuntimePermission( - packageState, userId, Manifest.permission.ACCESS_MEDIA_LOCATION + packageState, + userId, + Manifest.permission.ACCESS_MEDIA_LOCATION ) } } } - /** - * Upgrade permissions based on storage permissions grant - */ + /** Upgrade permissions based on storage permissions grant */ private fun MutateStateScope.upgradeAuralVisualMediaPermissions( packageState: PackageState, userId: Int @@ -147,15 +164,15 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { return } val requestedPermissionNames = androidPackage.requestedPermissions - val isStorageUserGranted = STORAGE_PERMISSIONS.anyIndexed { _, permissionName -> - if (permissionName !in requestedPermissionNames) { - return@anyIndexed false - } - val flags = with(policy) { - getPermissionFlags(packageState.appId, userId, permissionName) + val isStorageUserGranted = + STORAGE_PERMISSIONS.anyIndexed { _, permissionName -> + if (permissionName !in requestedPermissionNames) { + return@anyIndexed false + } + val flags = + with(policy) { getPermissionFlags(packageState.appId, userId, permissionName) } + PermissionFlags.isAppOpGranted(flags) && flags.hasBits(PermissionFlags.USER_SET) } - PermissionFlags.isAppOpGranted(flags) && flags.hasBits(PermissionFlags.USER_SET) - } if (isStorageUserGranted) { AURAL_VISUAL_MEDIA_PERMISSIONS.forEachIndexed { _, permissionName -> if (permissionName in requestedPermissionNames) { @@ -165,9 +182,7 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { } } - /** - * Upgrade permission based on the grant in [Manifest.permission_group.READ_MEDIA_VISUAL] - */ + /** Upgrade permission based on the grant in [Manifest.permission_group.READ_MEDIA_VISUAL] */ private fun MutateStateScope.upgradeUserSelectedVisualMediaPermission( packageState: PackageState, userId: Int @@ -177,19 +192,21 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { return } val requestedPermissionNames = androidPackage.requestedPermissions - val isVisualMediaUserGranted = VISUAL_MEDIA_PERMISSIONS.anyIndexed { _, permissionName -> - if (permissionName !in requestedPermissionNames) { - return@anyIndexed false - } - val flags = with(policy) { - getPermissionFlags(packageState.appId, userId, permissionName) + val isVisualMediaUserGranted = + VISUAL_MEDIA_PERMISSIONS.anyIndexed { _, permissionName -> + if (permissionName !in requestedPermissionNames) { + return@anyIndexed false + } + val flags = + with(policy) { getPermissionFlags(packageState.appId, userId, permissionName) } + PermissionFlags.isAppOpGranted(flags) && flags.hasBits(PermissionFlags.USER_SET) } - PermissionFlags.isAppOpGranted(flags) && flags.hasBits(PermissionFlags.USER_SET) - } if (isVisualMediaUserGranted) { if (Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED in requestedPermissionNames) { grantRuntimePermission( - packageState, userId, Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED + packageState, + userId, + Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED ) } } @@ -201,7 +218,8 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { permissionName: String ) { Slog.v( - LOG_TAG, "Granting runtime permission for package: ${packageState.packageName}, " + + LOG_TAG, + "Granting runtime permission for package: ${packageState.packageName}, " + "permission: $permissionName, userId: $userId" ) val permission = newState.systemState.permissions[permissionName]!! @@ -220,13 +238,13 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { } flags = flags or PermissionFlags.RUNTIME_GRANTED - flags = flags andInv ( - PermissionFlags.APP_OP_REVOKED or - PermissionFlags.IMPLICIT or - PermissionFlags.LEGACY_GRANTED or - PermissionFlags.HIBERNATION or - PermissionFlags.ONE_TIME - ) + flags = + flags andInv + (PermissionFlags.APP_OP_REVOKED or + PermissionFlags.IMPLICIT or + PermissionFlags.LEGACY_GRANTED or + PermissionFlags.HIBERNATION or + PermissionFlags.ONE_TIME) with(policy) { setPermissionFlags(appId, userId, permissionName, flags) } } @@ -234,39 +252,45 @@ class AppIdPermissionUpgrade(private val policy: AppIdPermissionPolicy) { private val LOG_TAG = AppIdPermissionUpgrade::class.java.simpleName private const val MASK_ANY_FIXED = - PermissionFlags.USER_SET or PermissionFlags.USER_FIXED or - PermissionFlags.POLICY_FIXED or PermissionFlags.SYSTEM_FIXED + PermissionFlags.USER_SET or + PermissionFlags.USER_FIXED or + PermissionFlags.POLICY_FIXED or + PermissionFlags.SYSTEM_FIXED - private val LEGACY_RESTRICTED_PERMISSIONS = indexedSetOf( - Manifest.permission.ACCESS_BACKGROUND_LOCATION, - Manifest.permission.READ_EXTERNAL_STORAGE, - Manifest.permission.WRITE_EXTERNAL_STORAGE, - Manifest.permission.SEND_SMS, - Manifest.permission.RECEIVE_SMS, - Manifest.permission.RECEIVE_WAP_PUSH, - Manifest.permission.RECEIVE_MMS, - Manifest.permission.READ_CELL_BROADCASTS, - Manifest.permission.READ_CALL_LOG, - Manifest.permission.WRITE_CALL_LOG, - Manifest.permission.PROCESS_OUTGOING_CALLS - ) + private val LEGACY_RESTRICTED_PERMISSIONS = + indexedSetOf( + Manifest.permission.ACCESS_BACKGROUND_LOCATION, + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.SEND_SMS, + Manifest.permission.RECEIVE_SMS, + Manifest.permission.RECEIVE_WAP_PUSH, + Manifest.permission.RECEIVE_MMS, + Manifest.permission.READ_CELL_BROADCASTS, + Manifest.permission.READ_CALL_LOG, + Manifest.permission.WRITE_CALL_LOG, + Manifest.permission.PROCESS_OUTGOING_CALLS + ) - private val STORAGE_PERMISSIONS = indexedSetOf( - Manifest.permission.READ_EXTERNAL_STORAGE, - Manifest.permission.WRITE_EXTERNAL_STORAGE - ) - private val AURAL_VISUAL_MEDIA_PERMISSIONS = indexedSetOf( - Manifest.permission.READ_MEDIA_AUDIO, - Manifest.permission.READ_MEDIA_IMAGES, - Manifest.permission.READ_MEDIA_VIDEO, - Manifest.permission.ACCESS_MEDIA_LOCATION, - Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED - ) + private val STORAGE_PERMISSIONS = + indexedSetOf( + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE + ) + private val AURAL_VISUAL_MEDIA_PERMISSIONS = + indexedSetOf( + Manifest.permission.READ_MEDIA_AUDIO, + Manifest.permission.READ_MEDIA_IMAGES, + Manifest.permission.READ_MEDIA_VIDEO, + Manifest.permission.ACCESS_MEDIA_LOCATION, + Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED + ) // Visual media permissions in T - private val VISUAL_MEDIA_PERMISSIONS = indexedSetOf( - Manifest.permission.READ_MEDIA_IMAGES, - Manifest.permission.READ_MEDIA_VIDEO, - Manifest.permission.ACCESS_MEDIA_LOCATION - ) + private val VISUAL_MEDIA_PERMISSIONS = + indexedSetOf( + Manifest.permission.READ_MEDIA_IMAGES, + Manifest.permission.READ_MEDIA_VIDEO, + Manifest.permission.ACCESS_MEDIA_LOCATION + ) } } diff --git a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPersistence.kt b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPersistence.kt index 37a4a90f8f80..1bee356dfbf3 100644 --- a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPersistence.kt +++ b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPersistence.kt @@ -135,9 +135,7 @@ class DevicePermissionPersistence { ) { tag(TAG_DEVICE) { attributeInterned(ATTR_ID, deviceId) - permissionFlags.forEachIndexed { _, name, flags -> - serializePermission(name, flags) - } + permissionFlags.forEachIndexed { _, name, flags -> serializePermission(name, flags) } } } @@ -145,11 +143,12 @@ class DevicePermissionPersistence { tag(TAG_PERMISSION) { attributeInterned(ATTR_NAME, name) // Never serialize one-time permissions as granted. - val serializedFlags = if (flags.hasBits(PermissionFlags.ONE_TIME)) { - flags andInv PermissionFlags.RUNTIME_GRANTED - } else { - flags - } + val serializedFlags = + if (flags.hasBits(PermissionFlags.ONE_TIME)) { + flags andInv PermissionFlags.RUNTIME_GRANTED + } else { + flags + } attributeInt(ATTR_FLAGS, serializedFlags) } } @@ -166,4 +165,4 @@ class DevicePermissionPersistence { private const val ATTR_NAME = "name" private const val ATTR_FLAGS = "flags" } -}
\ No newline at end of file +} diff --git a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt index 240585c000fd..15a58593432e 100644 --- a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt @@ -53,8 +53,8 @@ class DevicePermissionPolicy : SchemePolicy() { override fun MutateStateScope.onAppIdRemoved(appId: Int) { newState.userStates.forEachIndexed { userStateIndex, _, userState -> if (appId in userState.appIdDevicePermissionFlags) { - newState.mutateUserStateAt(userStateIndex) - .mutateAppIdDevicePermissionFlags() -= appId + newState.mutateUserStateAt(userStateIndex).mutateAppIdDevicePermissionFlags() -= + appId } } } @@ -85,10 +85,10 @@ class DevicePermissionPolicy : SchemePolicy() { appId: Int, userId: Int ) { - resetPermissionStates(packageName, userId) + resetRuntimePermissions(packageName, userId) } - private fun MutateStateScope.resetPermissionStates(packageName: String, userId: Int) { + fun MutateStateScope.resetRuntimePermissions(packageName: String, userId: Int) { // It's okay to skip resetting permissions for packages that are removed, // because their states will be trimmed in onPackageRemoved()/onAppIdRemoved() val packageState = newState.externalState.packageStates[packageName] ?: return @@ -96,10 +96,11 @@ class DevicePermissionPolicy : SchemePolicy() { val appId = packageState.appId val appIdPermissionFlags = newState.userStates[userId]!!.appIdDevicePermissionFlags androidPackage.requestedPermissions.forEach { permissionName -> - val isRequestedByOtherPackages = anyPackageInAppId(appId) { - it.packageName != packageName && - permissionName in it.androidPackage!!.requestedPermissions - } + val isRequestedByOtherPackages = + anyPackageInAppId(appId) { + it.packageName != packageName && + permissionName in it.androidPackage!!.requestedPermissions + } if (isRequestedByOtherPackages) { return@forEach } @@ -116,7 +117,9 @@ class DevicePermissionPolicy : SchemePolicy() { } newState.userStates.forEachIndexed { _, userId, userState -> userState.appIdDevicePermissionFlags[appId]?.forEachReversedIndexed { - _, deviceId, permissionFlags -> + _, + deviceId, + permissionFlags -> permissionFlags.forEachReversedIndexed { _, permissionName, _ -> if (permissionName !in requestedPermissions) { setPermissionFlags(appId, deviceId, userId, permissionName, 0) @@ -166,11 +169,17 @@ class DevicePermissionPolicy : SchemePolicy() { userId: Int, permissionName: String ): Int { - val flags = state.userStates[userId]?.appIdDevicePermissionFlags?.get(appId)?.get(deviceId) - ?.getWithDefault(permissionName, 0) ?: 0 + val flags = + state.userStates[userId] + ?.appIdDevicePermissionFlags + ?.get(appId) + ?.get(deviceId) + ?.getWithDefault(permissionName, 0) + ?: 0 if (PermissionManager.DEBUG_DEVICE_PERMISSIONS) { Slog.i( - LOG_TAG, "getPermissionFlags: appId=$appId, userId=$userId," + + LOG_TAG, + "getPermissionFlags: appId=$appId, userId=$userId," + " deviceId=$deviceId, permissionName=$permissionName," + " flags=${PermissionFlags.toString(flags)}" ) @@ -186,7 +195,12 @@ class DevicePermissionPolicy : SchemePolicy() { flags: Int ): Boolean = updatePermissionFlags( - appId, deviceId, userId, permissionName, PermissionFlags.MASK_ALL, flags + appId, + deviceId, + userId, + permissionName, + PermissionFlags.MASK_ALL, + flags ) private fun MutateStateScope.updatePermissionFlags( @@ -201,20 +215,23 @@ class DevicePermissionPolicy : SchemePolicy() { Slog.w(LOG_TAG, "$permissionName is not a device aware permission.") return false } - val oldFlags = newState.userStates[userId]!!.appIdDevicePermissionFlags[appId] - ?.get(deviceId).getWithDefault(permissionName, 0) + val oldFlags = + newState.userStates[userId]!! + .appIdDevicePermissionFlags[appId] + ?.get(deviceId) + .getWithDefault(permissionName, 0) val newFlags = (oldFlags andInv flagMask) or (flagValues and flagMask) if (oldFlags == newFlags) { return false } val appIdDevicePermissionFlags = newState.mutateUserState(userId)!!.mutateAppIdDevicePermissionFlags() - val devicePermissionFlags = appIdDevicePermissionFlags.mutateOrPut(appId) { - MutableIndexedReferenceMap() - } + val devicePermissionFlags = + appIdDevicePermissionFlags.mutateOrPut(appId) { MutableIndexedReferenceMap() } if (PermissionManager.DEBUG_DEVICE_PERMISSIONS) { Slog.i( - LOG_TAG, "setPermissionFlags(): appId=$appId, userId=$userId," + + LOG_TAG, + "setPermissionFlags(): appId=$appId, userId=$userId," + " deviceId=$deviceId, permissionName=$permissionName," + " newFlags=${PermissionFlags.toString(newFlags)}" ) @@ -229,40 +246,39 @@ class DevicePermissionPolicy : SchemePolicy() { } listeners.forEachIndexed { _, it -> it.onDevicePermissionFlagsChanged( - appId, userId, deviceId, permissionName, oldFlags, newFlags + appId, + userId, + deviceId, + permissionName, + oldFlags, + newFlags ) } return true } fun addOnPermissionFlagsChangedListener(listener: OnDevicePermissionFlagsChangedListener) { - synchronized(listenersLock) { - listeners = listeners + listener - } + synchronized(listenersLock) { listeners = listeners + listener } } fun removeOnPermissionFlagsChangedListener(listener: OnDevicePermissionFlagsChangedListener) { - synchronized(listenersLock) { - listeners = listeners - listener - } + synchronized(listenersLock) { listeners = listeners - listener } } private fun isDeviceAwarePermission(permissionName: String): Boolean = - DEVICE_AWARE_PERMISSIONS.contains(permissionName) + DEVICE_AWARE_PERMISSIONS.contains(permissionName) companion object { private val LOG_TAG = DevicePermissionPolicy::class.java.simpleName - /** - * These permissions are supported for virtual devices. - */ + /** These permissions are supported for virtual devices. */ // TODO: b/298661870 - Use new API to get the list of device aware permissions. val DEVICE_AWARE_PERMISSIONS = emptySet<String>() } /** - * TODO: b/289355341 - implement listener for permission changes - * Listener for permission flags changes. + * TODO: b/289355341 - implement listener for permission changes Listener for permission flags + * changes. */ abstract class OnDevicePermissionFlagsChangedListener { /** @@ -288,4 +304,4 @@ class DevicePermissionPolicy : SchemePolicy() { */ abstract fun onStateMutated() } -}
\ No newline at end of file +} diff --git a/services/permission/java/com/android/server/permission/access/permission/Permission.kt b/services/permission/java/com/android/server/permission/access/permission/Permission.kt index c7fe1a9e744e..aa569280eadf 100644 --- a/services/permission/java/com/android/server/permission/access/permission/Permission.kt +++ b/services/permission/java/com/android/server/permission/access/permission/Permission.kt @@ -26,8 +26,7 @@ data class Permission( val isReconciled: Boolean, val type: Int, val appId: Int, - @Suppress("ArrayInDataClass") - val gids: IntArray = EmptyArray.INT, + @Suppress("ArrayInDataClass") val gids: IntArray = EmptyArray.INT, val areGidsPerUser: Boolean = false ) { inline val name: String @@ -43,8 +42,7 @@ data class Permission( get() = type == TYPE_DYNAMIC inline val protectionLevel: Int - @Suppress("DEPRECATION") - get() = permissionInfo.protectionLevel + @Suppress("DEPRECATION") get() = permissionInfo.protectionLevel inline val protection: Int get() = permissionInfo.protection diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt index 550d1480fc81..b9d89c2184b7 100644 --- a/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt +++ b/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt @@ -32,15 +32,12 @@ import com.android.server.permission.access.util.hasBits * * The old binary permission state is now tracked by multiple `*_GRANTED` and `*_REVOKED` flags, so * that: - * * - With [INSTALL_GRANTED] and [INSTALL_REVOKED], we can now get rid of the old per-package * `areInstallPermissionsFixed` attribute and correctly track it per-permission, finally fixing * edge cases during module rollbacks. - * * - With [LEGACY_GRANTED] and [IMPLICIT_GRANTED], we can now ensure that legacy permissions and * implicit permissions split from non-runtime permissions are never revoked, without checking * split permissions and package state everywhere slowly and in slightly different ways. - * * - With [RESTRICTION_REVOKED], we can now get rid of the error-prone logic about revoking and * potentially re-granting permissions upon restriction state changes. * @@ -55,9 +52,7 @@ import com.android.server.permission.access.util.hasBits * don't have any effect on the binary permission state. */ object PermissionFlags { - /** - * Permission flag for a normal permission that is granted at package installation. - */ + /** Permission flag for a normal permission that is granted at package installation. */ const val INSTALL_GRANTED = 1 shl 0 /** @@ -97,8 +92,8 @@ object PermissionFlags { /** * Permission flag for a runtime permission whose state is set by the user. * - * For example, this flag may be set when the permission is allowed by the user in the - * request permission dialog, or managed in the permission settings. + * For example, this flag may be set when the permission is allowed by the user in the request + * permission dialog, or managed in the permission settings. * * @see PackageManager.FLAG_PERMISSION_USER_SET */ @@ -290,8 +285,8 @@ object PermissionFlags { /** * Permission flag for a runtime permission that is selected by the user. * - * For example, this flag may be set when one of the coarse/fine location accuracies is - * selected by the user. + * For example, this flag may be set when one of the coarse/fine location accuracies is selected + * by the user. * * This flag is informational and managed by PermissionController. * @@ -299,28 +294,37 @@ object PermissionFlags { */ const val USER_SELECTED = 1 shl 23 - /** - * Mask for all permission flags. - */ + /** Mask for all permission flags. */ const val MASK_ALL = 0.inv() - /** - * Mask for all permission flags that may be applied to a runtime permission. - */ - const val MASK_RUNTIME = ROLE or RUNTIME_GRANTED or USER_SET or USER_FIXED or POLICY_FIXED or - SYSTEM_FIXED or PREGRANT or LEGACY_GRANTED or IMPLICIT_GRANTED or IMPLICIT or - USER_SENSITIVE_WHEN_GRANTED or USER_SENSITIVE_WHEN_REVOKED or INSTALLER_EXEMPT or - SYSTEM_EXEMPT or UPGRADE_EXEMPT or RESTRICTION_REVOKED or SOFT_RESTRICTED or - APP_OP_REVOKED or ONE_TIME or HIBERNATION or USER_SELECTED - - /** - * Mask for all permission flags about permission exemption. - */ + /** Mask for all permission flags that may be applied to a runtime permission. */ + const val MASK_RUNTIME = + ROLE or + RUNTIME_GRANTED or + USER_SET or + USER_FIXED or + POLICY_FIXED or + SYSTEM_FIXED or + PREGRANT or + LEGACY_GRANTED or + IMPLICIT_GRANTED or + IMPLICIT or + USER_SENSITIVE_WHEN_GRANTED or + USER_SENSITIVE_WHEN_REVOKED or + INSTALLER_EXEMPT or + SYSTEM_EXEMPT or + UPGRADE_EXEMPT or + RESTRICTION_REVOKED or + SOFT_RESTRICTED or + APP_OP_REVOKED or + ONE_TIME or + HIBERNATION or + USER_SELECTED + + /** Mask for all permission flags about permission exemption. */ const val MASK_EXEMPT = INSTALLER_EXEMPT or SYSTEM_EXEMPT or UPGRADE_EXEMPT - /** - * Mask for all permission flags about permission restriction. - */ + /** Mask for all permission flags about permission restriction. */ const val MASK_RESTRICTED = RESTRICTION_REVOKED or SOFT_RESTRICTED fun isPermissionGranted(flags: Int): Boolean { @@ -363,11 +367,13 @@ object PermissionFlags { apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT } if (flags.hasBits(IMPLICIT)) { - apiFlags = apiFlags or if (flags.hasBits(LEGACY_GRANTED)) { - PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED - } else { - PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED - } + apiFlags = + apiFlags or + if (flags.hasBits(LEGACY_GRANTED)) { + PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED + } else { + PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED + } } if (flags.hasBits(USER_SENSITIVE_WHEN_GRANTED)) { apiFlags = apiFlags or PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED @@ -440,8 +446,10 @@ object PermissionFlags { } flags = flags or (oldFlags and LEGACY_GRANTED) flags = flags or (oldFlags and IMPLICIT_GRANTED) - if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) || - apiFlags.hasBits(PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED)) { + if ( + apiFlags.hasBits(PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) || + apiFlags.hasBits(PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED) + ) { flags = flags or IMPLICIT } if (apiFlags.hasBits(PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED)) { diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt index bb24d514e14b..ab3d78c9958c 100644 --- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt +++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt @@ -41,10 +41,10 @@ import android.os.RemoteException import android.os.ServiceManager import android.os.UserHandle import android.os.UserManager -import android.permission.flags.Flags import android.permission.IOnPermissionsChangeListener import android.permission.PermissionControllerManager import android.permission.PermissionManager +import android.permission.flags.Flags import android.provider.Settings import android.util.ArrayMap import android.util.ArraySet @@ -88,28 +88,25 @@ import com.android.server.pm.UserManagerInternal import com.android.server.pm.UserManagerService import com.android.server.pm.parsing.pkg.AndroidPackageUtils import com.android.server.pm.permission.LegacyPermission -import com.android.server.pm.permission.Permission as LegacyPermission2 import com.android.server.pm.permission.LegacyPermissionSettings import com.android.server.pm.permission.LegacyPermissionState +import com.android.server.pm.permission.Permission as LegacyPermission2 import com.android.server.pm.permission.PermissionManagerServiceInterface import com.android.server.pm.permission.PermissionManagerServiceInternal import com.android.server.pm.pkg.AndroidPackage import com.android.server.pm.pkg.PackageState import com.android.server.policy.SoftRestrictedPermissionPolicy -import libcore.util.EmptyArray import java.io.FileDescriptor import java.io.PrintWriter import java.util.concurrent.CompletableFuture import java.util.concurrent.ExecutionException import java.util.concurrent.TimeUnit import java.util.concurrent.TimeoutException +import libcore.util.EmptyArray -/** - * Modern implementation of [PermissionManagerServiceInterface]. - */ -class PermissionService( - private val service: AccessCheckingService -) : PermissionManagerServiceInterface { +/** Modern implementation of [PermissionManagerServiceInterface]. */ +class PermissionService(private val service: AccessCheckingService) : + PermissionManagerServiceInterface { private val policy = service.getSchemePolicy(UidUri.SCHEME, PermissionUri.SCHEME) as AppIdPermissionPolicy @@ -131,8 +128,7 @@ class PermissionService( private lateinit var onPermissionFlagsChangedListener: OnPermissionFlagsChangedListener private val storageVolumeLock = Any() - @GuardedBy("storageVolumeLock") - private val mountedStorageVolumes = ArraySet<String?>() + @GuardedBy("storageVolumeLock") private val mountedStorageVolumes = ArraySet<String?>() @GuardedBy("storageVolumeLock") private val storageVolumePackageNames = ArrayMap<String?, MutableList<String>>() @@ -144,8 +140,8 @@ class PermissionService( * A permission backup might contain apps that are not installed. In this case we delay the * restoration until the app is installed. * - * This array (`userId -> noDelayedBackupLeft`) is `true` for all the users where - * there is **no more** delayed backup left. + * This array (`userId -> noDelayedBackupLeft`) is `true` for all the users where there is **no + * more** delayed backup left. */ private val isDelayedPermissionBackupFinished = SparseBooleanArray() @@ -154,9 +150,10 @@ class PermissionService( packageManagerInternal = LocalServices.getService(PackageManagerInternal::class.java) packageManagerLocal = LocalManagerRegistry.getManagerOrThrow(PackageManagerLocal::class.java) - platformCompat = IPlatformCompat.Stub.asInterface( - ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE) - ) + platformCompat = + IPlatformCompat.Stub.asInterface( + ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE) + ) systemConfig = SystemConfig.getInstance() userManagerInternal = LocalServices.getService(UserManagerInternal::class.java) userManagerService = UserManagerService.getInstance() @@ -166,8 +163,8 @@ class PermissionService( PackageManager.invalidatePackageInfoCache() PermissionManager.disablePackageNamePermissionCache() - handlerThread = ServiceThread(LOG_TAG, Process.THREAD_PRIORITY_BACKGROUND, true) - .apply { start() } + handlerThread = + ServiceThread(LOG_TAG, Process.THREAD_PRIORITY_BACKGROUND, true).apply { start() } handler = Handler(handlerThread.looper) onPermissionsChangeListeners = OnPermissionsChangeListeners(FgThread.get().looper) onPermissionFlagsChangedListener = OnPermissionFlagsChangedListener() @@ -181,9 +178,7 @@ class PermissionService( return emptyList() } - val permissionGroups = service.getState { - with(policy) { getPermissionGroups() } - } + val permissionGroups = service.getState { with(policy) { getPermissionGroups() } } return permissionGroups.mapNotNullIndexedTo(ArrayList()) { _, _, permissionGroup -> if (snapshot.isPackageVisibleToUid(permissionGroup.packageName, callingUid)) { @@ -206,9 +201,9 @@ class PermissionService( return null } - permissionGroup = service.getState { - with(policy) { getPermissionGroups()[permissionGroupName] } - } ?: return null + permissionGroup = + service.getState { with(policy) { getPermissionGroups()[permissionGroupName] } } + ?: return null if (!snapshot.isPackageVisibleToUid(permissionGroup.packageName, callingUid)) { return null @@ -242,29 +237,28 @@ class PermissionService( return null } - permission = service.getState { - with(policy) { getPermissions()[permissionName] } - } ?: return null + permission = + service.getState { with(policy) { getPermissions()[permissionName] } } + ?: return null if (!snapshot.isPackageVisibleToUid(permission.packageName, callingUid)) { return null } val opPackage = snapshot.getPackageState(opPackageName)?.androidPackage - targetSdkVersion = when { - // System sees all flags. - isRootOrSystemOrShellUid(callingUid) -> Build.VERSION_CODES.CUR_DEVELOPMENT - opPackage != null -> opPackage.targetSdkVersion - else -> Build.VERSION_CODES.CUR_DEVELOPMENT - } + targetSdkVersion = + when { + // System sees all flags. + isRootOrSystemOrShellUid(callingUid) -> Build.VERSION_CODES.CUR_DEVELOPMENT + opPackage != null -> opPackage.targetSdkVersion + else -> Build.VERSION_CODES.CUR_DEVELOPMENT + } } return permission.generatePermissionInfo(flags, targetSdkVersion) } - /** - * Generate a new [PermissionInfo] from [Permission] and adjust it accordingly. - */ + /** Generate a new [PermissionInfo] from [Permission] and adjust it accordingly. */ private fun Permission.generatePermissionInfo( flags: Int, targetSdkVersion: Int = Build.VERSION_CODES.CUR_DEVELOPMENT @@ -296,22 +290,27 @@ class PermissionService( return null } - val permissions = service.getState { - if (permissionGroupName != null) { - val permissionGroup = - with(policy) { getPermissionGroups()[permissionGroupName] } ?: return null + val permissions = + service.getState { + if (permissionGroupName != null) { + val permissionGroup = + with(policy) { getPermissionGroups()[permissionGroupName] } + ?: return null - if (!snapshot.isPackageVisibleToUid(permissionGroup.packageName, callingUid)) { - return null + if ( + !snapshot.isPackageVisibleToUid(permissionGroup.packageName, callingUid) + ) { + return null + } } - } - with(policy) { getPermissions() } - } + with(policy) { getPermissions() } + } return permissions.mapNotNullIndexedTo(ArrayList()) { _, _, permission -> - if (permission.groupName == permissionGroupName && - snapshot.isPackageVisibleToUid(permission.packageName, callingUid) + if ( + permission.groupName == permissionGroupName && + snapshot.isPackageVisibleToUid(permission.packageName, callingUid) ) { permission.generatePermissionInfo(flags) } else { @@ -334,9 +333,7 @@ class PermissionService( private inline fun getPermissionsWithProtectionOrProtectionFlags( predicate: (Permission) -> Boolean ): List<PermissionInfo> { - val permissions = service.getState { - with(policy) { getPermissions() } - } + val permissions = service.getState { with(policy) { getPermissions() } } return permissions.mapNotNullIndexedTo(ArrayList()) { _, _, permission -> if (predicate(permission)) { @@ -348,18 +345,16 @@ class PermissionService( } override fun getPermissionGids(permissionName: String, userId: Int): IntArray { - val permission = service.getState { - with(policy) { getPermissions()[permissionName] } - } ?: return EmptyArray.INT + val permission = + service.getState { with(policy) { getPermissions()[permissionName] } } + ?: return EmptyArray.INT return permission.getGidsForUser(userId) } override fun getInstalledPermissions(packageName: String): Set<String> { requireNotNull(packageName) { "packageName cannot be null" } - val permissions = service.getState { - with(policy) { getPermissions() } - } + val permissions = service.getState { with(policy) { getPermissions() } } return permissions.mapNotNullIndexedTo(ArraySet()) { _, _, permission -> if (permission.packageName == packageName) { @@ -398,9 +393,8 @@ class PermissionService( permissionInfo.protectionLevel = PermissionInfo.fixProtectionLevel(permissionInfo.protectionLevel) - val newPermission = Permission( - permissionInfo, true, Permission.TYPE_DYNAMIC, permissionTree.appId - ) + val newPermission = + Permission(permissionInfo, true, Permission.TYPE_DYNAMIC, permissionTree.appId) with(policy) { addPermission(newPermission, !async) } } @@ -431,7 +425,7 @@ class PermissionService( val callingUid = Binder.getCallingUid() val permissionTree = with(policy) { findPermissionTree(permissionName) } if (permissionTree != null && permissionTree.appId == UserHandle.getAppId(callingUid)) { - return permissionTree + return permissionTree } throw SecurityException( @@ -447,8 +441,9 @@ class PermissionService( // if that plus the size of 'info' would exceed our stated maximum. if (permissionTree.appId != Process.SYSTEM_UID) { val permissionTreeFootprint = calculatePermissionTreeFootprint(permissionTree) - if (permissionTreeFootprint + permissionInfo.calculateFootprint() > - MAX_PERMISSION_TREE_FOOTPRINT + if ( + permissionTreeFootprint + permissionInfo.calculateFootprint() > + MAX_PERMISSION_TREE_FOOTPRINT ) { throw SecurityException("Permission tree size cap exceeded") } @@ -483,14 +478,16 @@ class PermissionService( packageManagerInternal.getPackageStateInternal(androidPackage.packageName) if (packageState == null) { Slog.e( - LOG_TAG, "checkUidPermission: PackageState not found for AndroidPackage" + + LOG_TAG, + "checkUidPermission: PackageState not found for AndroidPackage" + " $androidPackage" ) return PackageManager.PERMISSION_DENIED } - val isPermissionGranted = service.getState { - isPermissionGranted(packageState, userId, permissionName, deviceId) - } + val isPermissionGranted = + service.getState { + isPermissionGranted(packageState, userId, permissionName, deviceId) + } return if (isPermissionGranted) { PackageManager.PERMISSION_GRANTED } else { @@ -505,9 +502,7 @@ class PermissionService( } } - /** - * Internal implementation that should only be called by [checkUidPermission]. - */ + /** Internal implementation that should only be called by [checkUidPermission]. */ private fun isSystemUidPermissionGranted(uid: Int, permissionName: String): Boolean { val uidPermissions = systemConfig.systemPermissions[uid] ?: return false if (permissionName in uidPermissions) { @@ -532,12 +527,14 @@ class PermissionService( return PackageManager.PERMISSION_DENIED } - val packageState = packageManagerLocal.withFilteredSnapshot(Binder.getCallingUid(), userId) - .use { it.getPackageState(packageName) } ?: return PackageManager.PERMISSION_DENIED + val packageState = + packageManagerLocal.withFilteredSnapshot(Binder.getCallingUid(), userId).use { + it.getPackageState(packageName) + } + ?: return PackageManager.PERMISSION_DENIED - val isPermissionGranted = service.getState { - isPermissionGranted(packageState, userId, permissionName, deviceId) - } + val isPermissionGranted = + service.getState { isPermissionGranted(packageState, userId, permissionName, deviceId) } return if (isPermissionGranted) { PackageManager.PERMISSION_GRANTED } else { @@ -566,8 +563,15 @@ class PermissionService( } val fullerPermissionName = FULLER_PERMISSIONS[permissionName] - if (fullerPermissionName != null && - isSinglePermissionGranted(appId, userId, isInstantApp, fullerPermissionName, deviceId) + if ( + fullerPermissionName != null && + isSinglePermissionGranted( + appId, + userId, + isInstantApp, + fullerPermissionName, + deviceId + ) ) { return true } @@ -575,9 +579,7 @@ class PermissionService( return false } - /** - * Internal implementation that should only be called by [isPermissionGranted]. - */ + /** Internal implementation that should only be called by [isPermissionGranted]. */ private fun GetStateScope.isSinglePermissionGranted( appId: Int, userId: Int, @@ -604,20 +606,27 @@ class PermissionService( requireNotNull(packageName) { "packageName cannot be null" } Preconditions.checkArgumentNonnegative(userId, "userId") - val packageState = packageManagerLocal.withUnfilteredSnapshot() - .use { it.getPackageState(packageName) } + val packageState = + packageManagerLocal.withUnfilteredSnapshot().use { it.getPackageState(packageName) } if (packageState == null) { Slog.w(LOG_TAG, "getGrantedPermissions: Unknown package $packageName") return emptySet() } service.getState { - val permissionFlags = with(policy) { getUidPermissionFlags(packageState.appId, userId) } - ?: return emptySet() + val permissionFlags = + with(policy) { getUidPermissionFlags(packageState.appId, userId) } + ?: return emptySet() return permissionFlags.mapNotNullIndexedTo(ArraySet()) { _, permissionName, _ -> - if (isPermissionGranted( - packageState, userId, permissionName, Context.DEVICE_ID_DEFAULT)) { + if ( + isPermissionGranted( + packageState, + userId, + permissionName, + Context.DEVICE_ID_DEFAULT + ) + ) { permissionName } else { null @@ -635,8 +644,8 @@ class PermissionService( // permission state is not found, now we always return at least global GIDs. This is // more consistent with the pre-S-refactor behavior. This is also because we are now // actively trimming the per-UID objects when empty. - val permissionFlags = with(policy) { getUidPermissionFlags(appId, userId) } - ?: return globalGids.copyOf() + val permissionFlags = + with(policy) { getUidPermissionFlags(appId, userId) } ?: return globalGids.copyOf() val gids = GrowingIntArray.wrap(globalGids) permissionFlags.forEachIndexed { _, permissionName, flags -> @@ -644,8 +653,8 @@ class PermissionService( return@forEachIndexed } - val permission = with(policy) { getPermissions()[permissionName] } - ?: return@forEachIndexed + val permission = + with(policy) { getPermissions()[permissionName] } ?: return@forEachIndexed val permissionGids = permission.getGidsForUser(userId) if (permissionGids.isEmpty()) { return@forEachIndexed @@ -662,9 +671,7 @@ class PermissionService( deviceId: Int, userId: Int ) { - setRuntimePermissionGranted( - packageName, userId, permissionName, deviceId, isGranted = true - ) + setRuntimePermissionGranted(packageName, userId, permissionName, deviceId, isGranted = true) } override fun revokeRuntimePermission( @@ -675,7 +682,12 @@ class PermissionService( reason: String? ) { setRuntimePermissionGranted( - packageName, userId, permissionName, deviceId, isGranted = false, revokeReason = reason + packageName, + userId, + permissionName, + deviceId, + isGranted = false, + revokeReason = reason ) } @@ -684,8 +696,12 @@ class PermissionService( userId: Int ) { setRuntimePermissionGranted( - packageName, userId, Manifest.permission.POST_NOTIFICATIONS, Context.DEVICE_ID_DEFAULT, - isGranted = false, skipKillUid = true + packageName, + userId, + Manifest.permission.POST_NOTIFICATIONS, + Context.DEVICE_ID_DEFAULT, + isGranted = false, + skipKillUid = true ) } @@ -704,19 +720,24 @@ class PermissionService( ) { val methodName = if (isGranted) "grantRuntimePermission" else "revokeRuntimePermission" val callingUid = Binder.getCallingUid() - val isDebugEnabled = if (isGranted) { - PermissionManager.DEBUG_TRACE_GRANTS - } else { - PermissionManager.DEBUG_TRACE_PERMISSION_UPDATES - } - if (isDebugEnabled && - PermissionManager.shouldTraceGrant(packageName, permissionName, userId)) { + val isDebugEnabled = + if (isGranted) { + PermissionManager.DEBUG_TRACE_GRANTS + } else { + PermissionManager.DEBUG_TRACE_PERMISSION_UPDATES + } + if ( + isDebugEnabled && + PermissionManager.shouldTraceGrant(packageName, permissionName, userId) + ) { val callingUidName = packageManagerInternal.getNameForUid(callingUid) Slog.i( - LOG_TAG, "$methodName(packageName = $packageName," + + LOG_TAG, + "$methodName(packageName = $packageName," + " permissionName = $permissionName" + (if (isGranted) "" else "skipKillUid = $skipKillUid, reason = $revokeReason") + - ", userId = $userId," + " callingUid = $callingUidName ($callingUid))", + ", userId = $userId," + + " callingUid = $callingUidName ($callingUid))", RuntimeException() ) } @@ -727,23 +748,31 @@ class PermissionService( } enforceCallingOrSelfCrossUserPermission( - userId, enforceFullPermission = true, enforceShellRestriction = true, methodName + userId, + enforceFullPermission = true, + enforceShellRestriction = true, + methodName ) - val enforcedPermissionName = if (isGranted) { - Manifest.permission.GRANT_RUNTIME_PERMISSIONS - } else { - Manifest.permission.REVOKE_RUNTIME_PERMISSIONS - } + val enforcedPermissionName = + if (isGranted) { + Manifest.permission.GRANT_RUNTIME_PERMISSIONS + } else { + Manifest.permission.REVOKE_RUNTIME_PERMISSIONS + } context.enforceCallingOrSelfPermission(enforcedPermissionName, methodName) val packageState: PackageState? - val permissionControllerPackageName = packageManagerInternal.getKnownPackageNames( - KnownPackages.PACKAGE_PERMISSION_CONTROLLER, UserHandle.USER_SYSTEM - ).first() + val permissionControllerPackageName = + packageManagerInternal + .getKnownPackageNames( + KnownPackages.PACKAGE_PERMISSION_CONTROLLER, + UserHandle.USER_SYSTEM + ) + .first() val permissionControllerPackageState: PackageState? packageManagerLocal.withUnfilteredSnapshot().use { snapshot -> - packageState = snapshot.filtered(callingUid, userId) - .use { it.getPackageState(packageName) } + packageState = + snapshot.filtered(callingUid, userId).use { it.getPackageState(packageName) } permissionControllerPackageState = snapshot.getPackageState(permissionControllerPackageName) } @@ -756,11 +785,13 @@ class PermissionService( return } - val canManageRolePermission = isRootOrSystemUid(callingUid) || - UserHandle.getAppId(callingUid) == permissionControllerPackageState!!.appId - val overridePolicyFixed = context.checkCallingOrSelfPermission( - Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY - ) == PackageManager.PERMISSION_GRANTED + val canManageRolePermission = + isRootOrSystemUid(callingUid) || + UserHandle.getAppId(callingUid) == permissionControllerPackageState!!.appId + val overridePolicyFixed = + context.checkCallingOrSelfPermission( + Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY + ) == PackageManager.PERMISSION_GRANTED service.mutateState { with(onPermissionFlagsChangedListener) { @@ -773,8 +804,15 @@ class PermissionService( } setRuntimePermissionGranted( - packageState, userId, permissionName, deviceId, isGranted, canManageRolePermission, - overridePolicyFixed, reportError = true, methodName + packageState, + userId, + permissionName, + deviceId, + isGranted, + canManageRolePermission, + overridePolicyFixed, + reportError = true, + methodName ) } } @@ -791,8 +829,9 @@ class PermissionService( PackageInstaller.SessionParams.PERMISSION_STATE_DENIED -> {} else -> { Slog.w( - LOG_TAG, "setRequestedPermissionStates: Unknown permission state" + - " $permissionState for permission $permissionName" + LOG_TAG, + "setRequestedPermissionStates: Unknown permission state" + + " $permissionState for permission $permissionName" ) return@forEachIndexed } @@ -800,35 +839,50 @@ class PermissionService( if (permissionName !in packageState.androidPackage!!.requestedPermissions) { return@forEachIndexed } - val permission = with(policy) { getPermissions()[permissionName] } - ?: return@forEachIndexed + val permission = + with(policy) { getPermissions()[permissionName] } ?: return@forEachIndexed when { permission.isDevelopment || permission.isRuntime -> { - if (permissionState == - PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED) { + if ( + permissionState == + PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED + ) { setRuntimePermissionGranted( - packageState, userId, permissionName, Context.DEVICE_ID_DEFAULT, - isGranted = true, canManageRolePermission = false, - overridePolicyFixed = false, reportError = false, + packageState, + userId, + permissionName, + Context.DEVICE_ID_DEFAULT, + isGranted = true, + canManageRolePermission = false, + overridePolicyFixed = false, + reportError = false, "setRequestedPermissionStates" ) updatePermissionFlags( - packageState.appId, userId, permissionName, + packageState.appId, + userId, + permissionName, Context.DEVICE_ID_DEFAULT, PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED or - PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, 0, + PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, + 0, canUpdateSystemFlags = false, reportErrorForUnknownPermission = false, - isPermissionRequested = true, "setRequestedPermissionStates", + isPermissionRequested = true, + "setRequestedPermissionStates", packageState.packageName ) } } - permission.isAppOp && permissionName in + permission.isAppOp && + permissionName in PackageInstallerService.INSTALLER_CHANGEABLE_APP_OP_PERMISSIONS -> setAppOpPermissionGranted( - packageState, userId, permissionName, permissionState == - PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED + packageState, + userId, + permissionName, + permissionState == + PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED ) else -> {} } @@ -836,9 +890,7 @@ class PermissionService( } } - /** - * Set whether a runtime permission is granted, without any validation on caller. - */ + /** Set whether a runtime permission is granted, without any validation on caller. */ private fun MutateStateScope.setRuntimePermissionGranted( packageState: PackageState, userId: Int, @@ -876,8 +928,11 @@ class PermissionService( // their permissions as always granted return } - if (isGranted && packageState.getUserStateOrDefault(userId).isInstantApp && - !permission.isInstant) { + if ( + isGranted && + packageState.getUserStateOrDefault(userId).isInstantApp && + !permission.isInstant + ) { if (reportError) { throw SecurityException( "Cannot grant non-instant permission $permissionName to package" + @@ -913,7 +968,8 @@ class PermissionService( if (oldFlags.hasBits(PermissionFlags.SYSTEM_FIXED)) { if (reportError) { Slog.e( - LOG_TAG, "$methodName: Cannot change system fixed permission $permissionName" + + LOG_TAG, + "$methodName: Cannot change system fixed permission $permissionName" + " for package $packageName" ) } @@ -923,7 +979,8 @@ class PermissionService( if (oldFlags.hasBits(PermissionFlags.POLICY_FIXED) && !overridePolicyFixed) { if (reportError) { Slog.e( - LOG_TAG, "$methodName: Cannot change policy fixed permission $permissionName" + + LOG_TAG, + "$methodName: Cannot change policy fixed permission $permissionName" + " for package $packageName" ) } @@ -933,7 +990,8 @@ class PermissionService( if (isGranted && oldFlags.hasBits(PermissionFlags.RESTRICTION_REVOKED)) { if (reportError) { Slog.e( - LOG_TAG, "$methodName: Cannot grant hard-restricted non-exempt permission" + + LOG_TAG, + "$methodName: Cannot grant hard-restricted non-exempt permission" + " $permissionName to package $packageName" ) } @@ -942,14 +1000,19 @@ class PermissionService( if (isGranted && oldFlags.hasBits(PermissionFlags.SOFT_RESTRICTED)) { // TODO: Refactor SoftRestrictedPermissionPolicy. - val softRestrictedPermissionPolicy = SoftRestrictedPermissionPolicy.forPermission( - context, AndroidPackageUtils.generateAppInfoWithoutState(androidPackage), - androidPackage, UserHandle.of(userId), permissionName - ) + val softRestrictedPermissionPolicy = + SoftRestrictedPermissionPolicy.forPermission( + context, + AndroidPackageUtils.generateAppInfoWithoutState(androidPackage), + androidPackage, + UserHandle.of(userId), + permissionName + ) if (!softRestrictedPermissionPolicy.mayGrantPermission()) { if (reportError) { Slog.e( - LOG_TAG, "$methodName: Cannot grant soft-restricted non-exempt permission" + + LOG_TAG, + "$methodName: Cannot grant soft-restricted non-exempt permission" + " $permissionName to package $packageName" ) } @@ -965,15 +1028,17 @@ class PermissionService( setPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId, newFlags) if (permission.isRuntime) { - val action = if (isGranted) { - MetricsProto.MetricsEvent.ACTION_PERMISSION_GRANTED - } else { - MetricsProto.MetricsEvent.ACTION_PERMISSION_REVOKED - } - val log = LogMaker(action).apply { - setPackageName(packageName) - addTaggedData(MetricsProto.MetricsEvent.FIELD_PERMISSION, permissionName) - } + val action = + if (isGranted) { + MetricsProto.MetricsEvent.ACTION_PERMISSION_GRANTED + } else { + MetricsProto.MetricsEvent.ACTION_PERMISSION_REVOKED + } + val log = + LogMaker(action).apply { + setPackageName(packageName) + addTaggedData(MetricsProto.MetricsEvent.FIELD_PERMISSION, permissionName) + } metricsLogger.write(log) } } @@ -984,8 +1049,8 @@ class PermissionService( permissionName: String, isGranted: Boolean ) { - val appOpPolicy = service.getSchemePolicy(UidUri.SCHEME, AppOpUri.SCHEME) as - AppIdAppOpPolicy + val appOpPolicy = + service.getSchemePolicy(UidUri.SCHEME, AppOpUri.SCHEME) as AppIdAppOpPolicy val appOpName = AppOpsManager.permissionToOp(permissionName)!! val mode = if (isGranted) AppOpsManager.MODE_ALLOWED else AppOpsManager.MODE_ERRORED with(appOpPolicy) { setAppOpMode(packageState.appId, userId, appOpName, mode) } @@ -1003,17 +1068,20 @@ class PermissionService( } enforceCallingOrSelfCrossUserPermission( - userId, enforceFullPermission = true, enforceShellRestriction = false, + userId, + enforceFullPermission = true, + enforceShellRestriction = false, "getPermissionFlags" ) enforceCallingOrSelfAnyPermission( - "getPermissionFlags", Manifest.permission.GRANT_RUNTIME_PERMISSIONS, + "getPermissionFlags", + Manifest.permission.GRANT_RUNTIME_PERMISSIONS, Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, Manifest.permission.GET_RUNTIME_PERMISSIONS ) - val packageState = packageManagerLocal.withFilteredSnapshot() - .use { it.getPackageState(packageName) } + val packageState = + packageManagerLocal.withFilteredSnapshot().use { it.getPackageState(packageName) } if (packageState == null) { Slog.w(LOG_TAG, "getPermissionFlags: Unknown package $packageName") return 0 @@ -1045,12 +1113,17 @@ class PermissionService( } enforceCallingOrSelfCrossUserPermission( - userId, enforceFullPermission = true, enforceShellRestriction = false, + userId, + enforceFullPermission = true, + enforceShellRestriction = false, "isPermissionRevokedByPolicy" ) - val packageState = packageManagerLocal.withFilteredSnapshot(Binder.getCallingUid(), userId) - .use { it.getPackageState(packageName) } ?: return false + val packageState = + packageManagerLocal.withFilteredSnapshot(Binder.getCallingUid(), userId).use { + it.getPackageState(packageName) + } + ?: return false service.getState { if (isPermissionGranted(packageState, userId, permissionName, deviceId)) { @@ -1069,12 +1142,13 @@ class PermissionService( // TODO(b/173235285): Some caller may pass USER_ALL as userId. // Preconditions.checkArgumentNonnegative(userId, "userId") - val packageState = packageManagerLocal.withUnfilteredSnapshot() - .use { it.getPackageState(packageName) } ?: return false + val packageState = + packageManagerLocal.withUnfilteredSnapshot().use { it.getPackageState(packageName) } + ?: return false - val permissionFlags = service.getState { - with(policy) { getUidPermissionFlags(packageState.appId, userId) } - } ?: return false + val permissionFlags = + service.getState { with(policy) { getUidPermissionFlags(packageState.appId, userId) } } + ?: return false return permissionFlags.anyIndexed { _, _, it -> it.hasBits(REVIEW_REQUIRED_FLAGS) } } @@ -1090,13 +1164,18 @@ class PermissionService( } enforceCallingOrSelfCrossUserPermission( - userId, enforceFullPermission = true, enforceShellRestriction = false, + userId, + enforceFullPermission = true, + enforceShellRestriction = false, "shouldShowRequestPermissionRationale" ) val callingUid = Binder.getCallingUid() - val packageState = packageManagerLocal.withFilteredSnapshot(callingUid, userId) - .use { it.getPackageState(packageName) } ?: return false + val packageState = + packageManagerLocal.withFilteredSnapshot(callingUid, userId).use { + it.getPackageState(packageName) + } + ?: return false val appId = packageState.appId if (UserHandle.getAppId(callingUid) != appId) { return false @@ -1115,17 +1194,24 @@ class PermissionService( } if (permissionName == Manifest.permission.ACCESS_BACKGROUND_LOCATION) { - val isBackgroundRationaleChangeEnabled = Binder::class.withClearedCallingIdentity { - try { - platformCompat.isChangeEnabledByPackageName( - BACKGROUND_RATIONALE_CHANGE_ID, packageName, userId - ) - } catch (e: RemoteException) { - Slog.e(LOG_TAG, "shouldShowRequestPermissionRationale: Unable to check if" + - " compatibility change is enabled", e) - false + val isBackgroundRationaleChangeEnabled = + Binder::class.withClearedCallingIdentity { + try { + platformCompat.isChangeEnabledByPackageName( + BACKGROUND_RATIONALE_CHANGE_ID, + packageName, + userId + ) + } catch (e: RemoteException) { + Slog.e( + LOG_TAG, + "shouldShowRequestPermissionRationale: Unable to check if" + + " compatibility change is enabled", + e + ) + false + } } - } if (isBackgroundRationaleChangeEnabled) { return true } @@ -1144,20 +1230,30 @@ class PermissionService( userId: Int ) { val callingUid = Binder.getCallingUid() - if (PermissionManager.DEBUG_TRACE_PERMISSION_UPDATES && - PermissionManager.shouldTraceGrant(packageName, permissionName, userId)) { - val flagMaskString = DebugUtils.flagsToString( - PackageManager::class.java, "FLAG_PERMISSION_", flagMask.toLong() - ) - val flagValuesString = DebugUtils.flagsToString( - PackageManager::class.java, "FLAG_PERMISSION_", flagValues.toLong() - ) + if ( + PermissionManager.DEBUG_TRACE_PERMISSION_UPDATES && + PermissionManager.shouldTraceGrant(packageName, permissionName, userId) + ) { + val flagMaskString = + DebugUtils.flagsToString( + PackageManager::class.java, + "FLAG_PERMISSION_", + flagMask.toLong() + ) + val flagValuesString = + DebugUtils.flagsToString( + PackageManager::class.java, + "FLAG_PERMISSION_", + flagValues.toLong() + ) val callingUidName = packageManagerInternal.getNameForUid(callingUid) Slog.i( - LOG_TAG, "updatePermissionFlags(packageName = $packageName," + + LOG_TAG, + "updatePermissionFlags(packageName = $packageName," + " permissionName = $permissionName, flagMask = $flagMaskString," + " flagValues = $flagValuesString, userId = $userId," + - " callingUid = $callingUidName ($callingUid))", RuntimeException() + " callingUid = $callingUidName ($callingUid))", + RuntimeException() ) } @@ -1167,11 +1263,14 @@ class PermissionService( } enforceCallingOrSelfCrossUserPermission( - userId, enforceFullPermission = true, enforceShellRestriction = true, + userId, + enforceFullPermission = true, + enforceShellRestriction = true, "updatePermissionFlags" ) enforceCallingOrSelfAnyPermission( - "updatePermissionFlags", Manifest.permission.GRANT_RUNTIME_PERMISSIONS, + "updatePermissionFlags", + Manifest.permission.GRANT_RUNTIME_PERMISSIONS, Manifest.permission.REVOKE_RUNTIME_PERMISSIONS ) @@ -1208,8 +1307,10 @@ class PermissionService( // Different from the old implementation, which returns when package doesn't exist but // throws when package exists but isn't visible, we now return in both cases to avoid // leaking the package existence. - if (androidPackage == null || - packageManagerInternal.filterAppAccess(packageName, callingUid, userId, false)) { + if ( + androidPackage == null || + packageManagerInternal.filterAppAccess(packageName, callingUid, userId, false) + ) { Slog.w(LOG_TAG, "updatePermissionFlags: Unknown package $packageName") return } @@ -1219,26 +1320,35 @@ class PermissionService( // permissions. val canUpdateSystemFlags = isRootOrSystemUid(callingUid) - val isPermissionRequested = if (permissionName in androidPackage.requestedPermissions) { - // Fast path, the current package has requested the permission. - true - } else { - // Slow path, go through all shared user packages. - val sharedUserPackageNames = - packageManagerInternal.getSharedUserPackagesForPackage(packageName, userId) - sharedUserPackageNames.any { sharedUserPackageName -> - val sharedUserPackage = packageManagerInternal.getPackage(sharedUserPackageName) - sharedUserPackage != null && - permissionName in sharedUserPackage.requestedPermissions + val isPermissionRequested = + if (permissionName in androidPackage.requestedPermissions) { + // Fast path, the current package has requested the permission. + true + } else { + // Slow path, go through all shared user packages. + val sharedUserPackageNames = + packageManagerInternal.getSharedUserPackagesForPackage(packageName, userId) + sharedUserPackageNames.any { sharedUserPackageName -> + val sharedUserPackage = packageManagerInternal.getPackage(sharedUserPackageName) + sharedUserPackage != null && + permissionName in sharedUserPackage.requestedPermissions + } } - } val appId = packageState.appId service.mutateState { updatePermissionFlags( - appId, userId, permissionName, deviceId, flagMask, flagValues, canUpdateSystemFlags, - reportErrorForUnknownPermission = true, isPermissionRequested, - "updatePermissionFlags", packageName + appId, + userId, + permissionName, + deviceId, + flagMask, + flagValues, + canUpdateSystemFlags, + reportErrorForUnknownPermission = true, + isPermissionRequested, + "updatePermissionFlags", + packageName ) } } @@ -1246,17 +1356,25 @@ class PermissionService( override fun updatePermissionFlagsForAllApps(flagMask: Int, flagValues: Int, userId: Int) { val callingUid = Binder.getCallingUid() if (PermissionManager.DEBUG_TRACE_PERMISSION_UPDATES) { - val flagMaskString = DebugUtils.flagsToString( - PackageManager::class.java, "FLAG_PERMISSION_", flagMask.toLong() - ) - val flagValuesString = DebugUtils.flagsToString( - PackageManager::class.java, "FLAG_PERMISSION_", flagValues.toLong() - ) + val flagMaskString = + DebugUtils.flagsToString( + PackageManager::class.java, + "FLAG_PERMISSION_", + flagMask.toLong() + ) + val flagValuesString = + DebugUtils.flagsToString( + PackageManager::class.java, + "FLAG_PERMISSION_", + flagValues.toLong() + ) val callingUidName = packageManagerInternal.getNameForUid(callingUid) Slog.i( - LOG_TAG, "updatePermissionFlagsForAllApps(flagMask = $flagMaskString," + + LOG_TAG, + "updatePermissionFlagsForAllApps(flagMask = $flagMaskString," + " flagValues = $flagValuesString, userId = $userId," + - " callingUid = $callingUidName ($callingUid))", RuntimeException() + " callingUid = $callingUidName ($callingUid))", + RuntimeException() ) } @@ -1266,11 +1384,14 @@ class PermissionService( } enforceCallingOrSelfCrossUserPermission( - userId, enforceFullPermission = true, enforceShellRestriction = true, + userId, + enforceFullPermission = true, + enforceShellRestriction = true, "updatePermissionFlagsForAllApps" ) enforceCallingOrSelfAnyPermission( - "updatePermissionFlagsForAllApps", Manifest.permission.GRANT_RUNTIME_PERMISSIONS, + "updatePermissionFlagsForAllApps", + Manifest.permission.GRANT_RUNTIME_PERMISSIONS, Manifest.permission.REVOKE_RUNTIME_PERMISSIONS ) @@ -1278,26 +1399,30 @@ class PermissionService( // flag, we now properly sanitize all flags as in updatePermissionFlags(). val canUpdateSystemFlags = isRootOrSystemUid(callingUid) - val packageStates = packageManagerLocal.withUnfilteredSnapshot() - .use { it.packageStates } + val packageStates = packageManagerLocal.withUnfilteredSnapshot().use { it.packageStates } service.mutateState { packageStates.forEach { (packageName, packageState) -> val androidPackage = packageState.androidPackage ?: return@forEach androidPackage.requestedPermissions.forEach { permissionName -> updatePermissionFlags( - packageState.appId, userId, permissionName, Context.DEVICE_ID_DEFAULT, - flagMask, flagValues, canUpdateSystemFlags, + packageState.appId, + userId, + permissionName, + Context.DEVICE_ID_DEFAULT, + flagMask, + flagValues, + canUpdateSystemFlags, reportErrorForUnknownPermission = false, - isPermissionRequested = true, "updatePermissionFlagsForAllApps", packageName + isPermissionRequested = true, + "updatePermissionFlagsForAllApps", + packageName ) } } } } - /** - * Update flags for a permission, without any validation on caller. - */ + /** Update flags for a permission, without any validation on caller. */ private fun MutateStateScope.updatePermissionFlags( appId: Int, userId: Int, @@ -1311,20 +1436,19 @@ class PermissionService( methodName: String, packageName: String ) { - @Suppress("NAME_SHADOWING") - var flagMask = flagMask - @Suppress("NAME_SHADOWING") - var flagValues = flagValues + @Suppress("NAME_SHADOWING") var flagMask = flagMask + @Suppress("NAME_SHADOWING") var flagValues = flagValues // Only the system can change these flags and nothing else. if (!canUpdateSystemFlags) { // Different from the old implementation, which allowed non-system UIDs to remove (but // not add) permission restriction flags, we now consistently ignore them altogether. - val ignoredMask = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED or - PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT or - PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT or - PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT or - PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT or - PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION + val ignoredMask = + PackageManager.FLAG_PERMISSION_SYSTEM_FIXED or + PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT or + PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT or + PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT or + PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT or + PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION flagMask = flagMask andInv ignoredMask flagValues = flagValues andInv ignoredMask } @@ -1340,7 +1464,8 @@ class PermissionService( val oldFlags = getPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId) if (!isPermissionRequested && oldFlags == 0) { Slog.w( - LOG_TAG, "$methodName: Permission $permissionName isn't requested by package" + + LOG_TAG, + "$methodName: Permission $permissionName isn't requested by package" + " $packageName" ) return @@ -1365,21 +1490,29 @@ class PermissionService( } enforceCallingOrSelfCrossUserPermission( - userId, enforceFullPermission = false, enforceShellRestriction = false, + userId, + enforceFullPermission = false, + enforceShellRestriction = false, "getAllowlistedRestrictedPermissions" ) val callingUid = Binder.getCallingUid() - val packageState = packageManagerLocal.withFilteredSnapshot(callingUid, userId) - .use { it.getPackageState(packageName) } ?: return null + val packageState = + packageManagerLocal.withFilteredSnapshot(callingUid, userId).use { + it.getPackageState(packageName) + } + ?: return null val androidPackage = packageState.androidPackage ?: return null - val isCallerPrivileged = context.checkCallingOrSelfPermission( - Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS - ) == PackageManager.PERMISSION_GRANTED + val isCallerPrivileged = + context.checkCallingOrSelfPermission( + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS + ) == PackageManager.PERMISSION_GRANTED - if (allowlistedFlags.hasBits(PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) && - !isCallerPrivileged) { + if ( + allowlistedFlags.hasBits(PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) && + !isCallerPrivileged + ) { throw SecurityException( "Querying system allowlist requires " + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS @@ -1389,8 +1522,12 @@ class PermissionService( val isCallerInstallerOnRecord = packageManagerInternal.isCallerInstallerOfRecord(androidPackage, callingUid) - if (allowlistedFlags.hasAnyBit(PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE or - PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER)) { + if ( + allowlistedFlags.hasAnyBit( + PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE or + PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER + ) + ) { if (!isCallerPrivileged && !isCallerInstallerOnRecord) { throw SecurityException( "Querying upgrade or installer allowlist requires being installer on record" + @@ -1400,7 +1537,9 @@ class PermissionService( } return getAllowlistedRestrictedPermissionsUnchecked( - packageState.appId, allowlistedFlags, userId + packageState.appId, + allowlistedFlags, + userId ) } @@ -1414,8 +1553,11 @@ class PermissionService( with(policy) { getPermissionFlags(appId, userId, permissionName) } } else { if (permissionName !in DevicePermissionPolicy.DEVICE_AWARE_PERMISSIONS) { - Slog.i(LOG_TAG, "$permissionName is not device aware permission, " + - " get the flags for default device.") + Slog.i( + LOG_TAG, + "$permissionName is not device aware permission, " + + " get the flags for default device." + ) return with(policy) { getPermissionFlags(appId, userId, permissionName) } } val virtualDeviceManagerInternal = virtualDeviceManagerInternal @@ -1423,8 +1565,7 @@ class PermissionService( Slog.e(LOG_TAG, "Virtual device manager service is not available.") return 0 } - val persistentDeviceId = - virtualDeviceManagerInternal.getPersistentIdForDevice(deviceId) + val persistentDeviceId = virtualDeviceManagerInternal.getPersistentIdForDevice(deviceId) if (persistentDeviceId != null) { with(devicePolicy) { getPermissionFlags(appId, persistentDeviceId, userId, permissionName) @@ -1444,13 +1585,14 @@ class PermissionService( flags: Int ): Boolean { return if (!Flags.deviceAwarePermissionApis() || deviceId == Context.DEVICE_ID_DEFAULT) { - with(policy) { - setPermissionFlags(appId, userId, permissionName, flags) - } + with(policy) { setPermissionFlags(appId, userId, permissionName, flags) } } else { if (permissionName !in DevicePermissionPolicy.DEVICE_AWARE_PERMISSIONS) { - Slog.i(LOG_TAG, "$permissionName is not device aware permission, " + - " set the flags for default device.") + Slog.i( + LOG_TAG, + "$permissionName is not device aware permission, " + + " set the flags for default device." + ) return with(policy) { setPermissionFlags(appId, userId, permissionName, flags) } } @@ -1459,8 +1601,7 @@ class PermissionService( Slog.e(LOG_TAG, "Virtual device manager service is not available.") return false } - val persistentDeviceId = - virtualDeviceManagerInternal.getPersistentIdForDevice(deviceId) + val persistentDeviceId = virtualDeviceManagerInternal.getPersistentIdForDevice(deviceId) if (persistentDeviceId != null) { with(devicePolicy) { setPermissionFlags(appId, persistentDeviceId, userId, permissionName, flags) @@ -1473,17 +1614,17 @@ class PermissionService( } /** - * This method does not enforce checks on the caller, should only be called after - * required checks. + * This method does not enforce checks on the caller, should only be called after required + * checks. */ private fun getAllowlistedRestrictedPermissionsUnchecked( appId: Int, allowlistedFlags: Int, userId: Int ): ArrayList<String>? { - val permissionFlags = service.getState { - with(policy) { getUidPermissionFlags(appId, userId) } - } ?: return null + val permissionFlags = + service.getState { with(policy) { getUidPermissionFlags(appId, userId) } } + ?: return null var queryFlags = 0 if (allowlistedFlags.hasBits(PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM)) { @@ -1512,14 +1653,18 @@ class PermissionService( return false } - val permissionNames = getAllowlistedRestrictedPermissions( - packageName, allowlistedFlags, userId - ) ?: ArrayList(1) + val permissionNames = + getAllowlistedRestrictedPermissions(packageName, allowlistedFlags, userId) + ?: ArrayList(1) if (permissionName !in permissionNames) { permissionNames += permissionName return setAllowlistedRestrictedPermissions( - packageName, permissionNames, allowlistedFlags, userId, isAddingPermission = true + packageName, + permissionNames, + allowlistedFlags, + userId, + isAddingPermission = true ) } return false @@ -1531,14 +1676,22 @@ class PermissionService( permissionNames: List<String>, userId: Int ) { - val newPermissionNames = getAllowlistedRestrictedPermissionsUnchecked(appId, - PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER, userId - )?.let { - ArraySet(permissionNames).apply { this += it }.toList() - } ?: permissionNames + val newPermissionNames = + getAllowlistedRestrictedPermissionsUnchecked( + appId, + PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER, + userId + ) + ?.let { ArraySet(permissionNames).apply { this += it }.toList() } + ?: permissionNames - setAllowlistedRestrictedPermissionsUnchecked(androidPackage, appId, newPermissionNames, - PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER, userId) + setAllowlistedRestrictedPermissionsUnchecked( + androidPackage, + appId, + newPermissionNames, + PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER, + userId + ) } override fun removeAllowlistedRestrictedPermission( @@ -1552,13 +1705,17 @@ class PermissionService( return false } - val permissions = getAllowlistedRestrictedPermissions( - packageName, allowlistedFlags, userId - ) ?: return false + val permissions = + getAllowlistedRestrictedPermissions(packageName, allowlistedFlags, userId) + ?: return false if (permissions.remove(permissionName)) { return setAllowlistedRestrictedPermissions( - packageName, permissions, allowlistedFlags, userId, isAddingPermission = false + packageName, + permissions, + allowlistedFlags, + userId, + isAddingPermission = false ) } @@ -1572,16 +1729,22 @@ class PermissionService( return false } - if (packageManagerLocal.withFilteredSnapshot() - .use { it.getPackageState(permission.packageName) } == null) { + if ( + packageManagerLocal.withFilteredSnapshot().use { + it.getPackageState(permission.packageName) + } == null + ) { return false } val isImmutablyRestrictedPermission = permission.isHardOrSoftRestricted && permission.isImmutablyRestricted - if (isImmutablyRestrictedPermission && context.checkCallingOrSelfPermission( - Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS - ) != PackageManager.PERMISSION_GRANTED) { + if ( + isImmutablyRestrictedPermission && + context.checkCallingOrSelfPermission( + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS + ) != PackageManager.PERMISSION_GRANTED + ) { throw SecurityException( "Cannot modify allowlist of an immutably restricted permission: ${permission.name}" ) @@ -1599,13 +1762,16 @@ class PermissionService( ): Boolean { Preconditions.checkArgument(allowlistedFlags.countOneBits() == 1) - val isCallerPrivileged = context.checkCallingOrSelfPermission( - Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS - ) == PackageManager.PERMISSION_GRANTED + val isCallerPrivileged = + context.checkCallingOrSelfPermission( + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS + ) == PackageManager.PERMISSION_GRANTED val callingUid = Binder.getCallingUid() - val packageState = packageManagerLocal.withFilteredSnapshot(callingUid, userId) - .use { snapshot -> snapshot.packageStates[packageName] ?: return false } + val packageState = + packageManagerLocal.withFilteredSnapshot(callingUid, userId).use { snapshot -> + snapshot.packageStates[packageName] ?: return false + } val androidPackage = packageState.androidPackage ?: return false val isCallerInstallerOnRecord = @@ -1627,15 +1793,19 @@ class PermissionService( } setAllowlistedRestrictedPermissionsUnchecked( - androidPackage, packageState.appId, permissionNames, allowlistedFlags, userId + androidPackage, + packageState.appId, + permissionNames, + allowlistedFlags, + userId ) return true } /** - * This method does not enforce checks on the caller, should only be called after - * required checks. + * This method does not enforce checks on the caller, should only be called after required + * checks. */ private fun setAllowlistedRestrictedPermissionsUnchecked( androidPackage: AndroidPackage, @@ -1712,22 +1882,24 @@ class PermissionService( } } - newFlags = if (permission.isHardRestricted && !isExempt) { - newFlags or PermissionFlags.RESTRICTION_REVOKED - } else { - newFlags andInv PermissionFlags.RESTRICTION_REVOKED - } - newFlags = if (permission.isSoftRestricted && !isExempt) { - newFlags or PermissionFlags.SOFT_RESTRICTED - } else { - newFlags andInv PermissionFlags.SOFT_RESTRICTED - } - mask = mask or PermissionFlags.RESTRICTION_REVOKED or - PermissionFlags.SOFT_RESTRICTED + newFlags = + if (permission.isHardRestricted && !isExempt) { + newFlags or PermissionFlags.RESTRICTION_REVOKED + } else { + newFlags andInv PermissionFlags.RESTRICTION_REVOKED + } + newFlags = + if (permission.isSoftRestricted && !isExempt) { + newFlags or PermissionFlags.SOFT_RESTRICTED + } else { + newFlags andInv PermissionFlags.SOFT_RESTRICTED + } + mask = + mask or + PermissionFlags.RESTRICTION_REVOKED or + PermissionFlags.SOFT_RESTRICTED - updatePermissionFlags( - appId, userId, requestedPermission, mask, newFlags - ) + updatePermissionFlags(appId, userId, requestedPermission, mask, newFlags) } } } @@ -1735,9 +1907,8 @@ class PermissionService( override fun resetRuntimePermissions(androidPackage: AndroidPackage, userId: Int) { service.mutateState { - with(policy) { - resetRuntimePermissions(androidPackage.packageName, userId) - } + with(policy) { resetRuntimePermissions(androidPackage.packageName, userId) } + with(devicePolicy) { resetRuntimePermissions(androidPackage.packageName, userId) } } } @@ -1745,9 +1916,8 @@ class PermissionService( packageManagerLocal.withUnfilteredSnapshot().use { snapshot -> service.mutateState { snapshot.packageStates.forEach { (_, packageState) -> - with(policy) { - resetRuntimePermissions(packageState.packageName, userId) - } + with(policy) { resetRuntimePermissions(packageState.packageName, userId) } + with(devicePolicy) { resetRuntimePermissions(packageState.packageName, userId) } } } } @@ -1755,7 +1925,8 @@ class PermissionService( override fun addOnPermissionsChangeListener(listener: IOnPermissionsChangeListener) { context.enforceCallingOrSelfPermission( - Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS, "addOnPermissionsChangeListener" + Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS, + "addOnPermissionsChangeListener" ) onPermissionsChangeListeners.addListener(listener) @@ -1780,9 +1951,7 @@ class PermissionService( requireNotNull(permissionName) { "permissionName cannot be null" } val packageNames = ArraySet<String>() - val permission = service.getState { - with(policy) { getPermissions()[permissionName] } - } + val permission = service.getState { with(policy) { getPermissions()[permissionName] } } if (permission == null || !permission.isAppOp) { packageNames.toTypedArray() } @@ -1808,8 +1977,8 @@ class PermissionService( androidPackage.requestedPermissions.forEach requestedPermissions@{ permissionName -> val permission = permissions[permissionName] ?: return@requestedPermissions if (permission.isAppOp) { - val packageNames = appOpPermissionPackageNames - .getOrPut(permissionName) { ArraySet() } + val packageNames = + appOpPermissionPackageNames.getOrPut(permissionName) { ArraySet() } packageNames += androidPackage.packageName } } @@ -1822,14 +1991,18 @@ class PermissionService( Preconditions.checkArgumentNonnegative(userId, "userId cannot be null") val backup = CompletableFuture<ByteArray>() permissionControllerManager.getRuntimePermissionBackup( - UserHandle.of(userId), PermissionThread.getExecutor(), backup::complete + UserHandle.of(userId), + PermissionThread.getExecutor(), + backup::complete ) return try { backup.get(BACKUP_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS) } catch (e: Exception) { when (e) { - is TimeoutException, is InterruptedException, is ExecutionException -> { + is TimeoutException, + is InterruptedException, + is ExecutionException -> { Slog.e(LOG_TAG, "Cannot create permission backup for user $userId", e) null } @@ -1846,7 +2019,8 @@ class PermissionService( isDelayedPermissionBackupFinished -= userId } permissionControllerManager.stageAndApplyRuntimePermissionsBackup( - backup, UserHandle.of(userId) + backup, + UserHandle.of(userId) ) } @@ -1860,7 +2034,9 @@ class PermissionService( } } permissionControllerManager.applyStagedRuntimePermissionBackup( - packageName, UserHandle.of(userId), PermissionThread.getExecutor() + packageName, + UserHandle.of(userId), + PermissionThread.getExecutor() ) { hasMoreBackup -> if (hasMoreBackup) { return@applyStagedRuntimePermissionBackup @@ -1907,21 +2083,15 @@ class PermissionService( ): IndexedMap<Int, MutableIndexedSet<String>> { val appIds = MutableIndexedSet<Int>() - val packageStates = packageManagerLocal.withUnfilteredSnapshot().use { - it.packageStates - } + val packageStates = packageManagerLocal.withUnfilteredSnapshot().use { it.packageStates } state.userStates.forEachIndexed { _, _, userState -> - userState.appIdPermissionFlags.forEachIndexed { _, appId, _ -> - appIds.add(appId) - } - userState.appIdAppOpModes.forEachIndexed { _, appId, _ -> - appIds.add(appId) - } - userState.packageVersions.forEachIndexed packageVersions@ { _, packageName, _ -> + userState.appIdPermissionFlags.forEachIndexed { _, appId, _ -> appIds.add(appId) } + userState.appIdAppOpModes.forEachIndexed { _, appId, _ -> appIds.add(appId) } + userState.packageVersions.forEachIndexed packageVersions@{ _, packageName, _ -> val appId = packageStates[packageName]?.appId ?: return@packageVersions appIds.add(appId) } - userState.packageAppOpModes.forEachIndexed packageAppOpModes@ { _, packageName, _ -> + userState.packageAppOpModes.forEachIndexed packageAppOpModes@{ _, packageName, _ -> val appId = packageStates[packageName]?.appId ?: return@packageAppOpModes appIds.add(appId) } @@ -1929,7 +2099,8 @@ class PermissionService( val appIdPackageNames = MutableIndexedMap<Int, MutableIndexedSet<String>>() packageStates.forEach { (_, packageState) -> - appIdPackageNames.getOrPut(packageState.appId) { MutableIndexedSet() } + appIdPackageNames + .getOrPut(packageState.appId) { MutableIndexedSet() } .add(packageState.packageName) } // add non-package app IDs which might not be reported by package manager. @@ -1960,10 +2131,7 @@ class PermissionService( println("Permission groups:") withIndent { state.systemState.permissionGroups.forEachIndexed { _, _, permissionGroup -> - println( - "${permissionGroup.name}: " + - "packageName=${permissionGroup.packageName}" - ) + println("${permissionGroup.name}: " + "packageName=${permissionGroup.packageName}") } } @@ -1992,7 +2160,9 @@ class PermissionService( println("Permissions:") withIndent { userState.appIdPermissionFlags[appId]?.forEachIndexed { - _, permissionName, flags -> + _, + permissionName, + flags -> val isGranted = PermissionFlags.isPermissionGranted(flags) println( "$permissionName: granted=$isGranted, flags=" + @@ -2002,7 +2172,9 @@ class PermissionService( } userState.appIdDevicePermissionFlags[appId]?.forEachIndexed { - _, deviceId, devicePermissionFlags -> + _, + deviceId, + devicePermissionFlags -> println("Permissions (Device $deviceId):") withIndent { devicePermissionFlags.forEachIndexed { _, permissionName, flags -> @@ -2017,7 +2189,8 @@ class PermissionService( println("App ops:") withIndent { - userState.appIdAppOpModes[appId]?.forEachIndexed {_, appOpName, appOpMode -> + userState.appIdAppOpModes[appId]?.forEachIndexed { _, appOpName, appOpMode + -> println("$appOpName: mode=${AppOpsManager.modeToName(appOpMode)}") } } @@ -2029,7 +2202,9 @@ class PermissionService( println("App ops:") withIndent { userState.packageAppOpModes[packageName]?.forEachIndexed { - _, appOpName, appOpMode -> + _, + appOpName, + appOpMode -> val modeName = AppOpsManager.modeToName(appOpMode) println("$appOpName: mode=$modeName") } @@ -2048,24 +2223,30 @@ class PermissionService( } override fun getPermissionTEMP(permissionName: String): LegacyPermission2? { - val permission = service.getState { - with(policy) { getPermissions()[permissionName] } - } ?: return null + val permission = + service.getState { with(policy) { getPermissions()[permissionName] } } ?: return null return LegacyPermission2( - permission.permissionInfo, permission.type, permission.isReconciled, permission.appId, - permission.gids, permission.areGidsPerUser + permission.permissionInfo, + permission.type, + permission.isReconciled, + permission.appId, + permission.gids, + permission.areGidsPerUser ) } override fun getLegacyPermissions(): List<LegacyPermission> = - service.getState { - with(policy) { getPermissions() } - }.mapIndexedTo(ArrayList()) { _, _, permission -> - LegacyPermission( - permission.permissionInfo, permission.type, permission.appId, permission.gids - ) - } + service + .getState { with(policy) { getPermissions() } } + .mapIndexedTo(ArrayList()) { _, _, permission -> + LegacyPermission( + permission.permissionInfo, + permission.type, + permission.appId, + permission.gids + ) + } override fun readLegacyPermissionsTEMP(legacyPermissionSettings: LegacyPermissionSettings) { // Package settings has been read when this method is called. @@ -2086,9 +2267,7 @@ class PermissionService( ): List<LegacyPermission> = permissions.mapIndexedTo(ArrayList()) { _, _, permission -> // We don't need to provide UID and GIDs, which are only retrieved when dumping. - LegacyPermission( - permission.permissionInfo, permission.type, 0, EmptyArray.INT - ) + LegacyPermission(permission.permissionInfo, permission.type, 0, EmptyArray.INT) } override fun getLegacyPermissionState(appId: Int): LegacyPermissionState { @@ -2097,17 +2276,18 @@ class PermissionService( service.getState { val permissions = with(policy) { getPermissions() } userIds.forEachIndexed { _, userId -> - val permissionFlags = with(policy) { getUidPermissionFlags(appId, userId) } - ?: return@forEachIndexed + val permissionFlags = + with(policy) { getUidPermissionFlags(appId, userId) } ?: return@forEachIndexed permissionFlags.forEachIndexed permissionFlags@{ _, permissionName, flags -> val permission = permissions[permissionName] ?: return@permissionFlags - val legacyPermissionState = LegacyPermissionState.PermissionState( - permissionName, - permission.isRuntime, - PermissionFlags.isPermissionGranted(flags), - PermissionFlags.toApiFlags(flags) - ) + val legacyPermissionState = + LegacyPermissionState.PermissionState( + permissionName, + permission.isRuntime, + PermissionFlags.isPermissionGranted(flags), + PermissionFlags.toApiFlags(flags) + ) legacyState.putPermissionState(legacyPermissionState, userId) } } @@ -2131,16 +2311,13 @@ class PermissionService( override fun onSystemReady() { service.onSystemReady() virtualDeviceManagerInternal = - LocalServices.getService(VirtualDeviceManagerInternal::class.java) - permissionControllerManager = PermissionControllerManager( - context, PermissionThread.getHandler() - ) + LocalServices.getService(VirtualDeviceManagerInternal::class.java) + permissionControllerManager = + PermissionControllerManager(context, PermissionThread.getHandler()) } override fun onUserCreated(userId: Int) { - withCorkedPackageInfoCache { - service.onUserAdded(userId) - } + withCorkedPackageInfoCache { service.onUserAdded(userId) } } override fun onUserRemoved(userId: Int) { @@ -2170,9 +2347,8 @@ class PermissionService( // of onPackageAdded() and reuse this order in onStorageVolumeAdded(). We need the // packages to be iterated in onStorageVolumeAdded() in the same order so that the // ownership of permissions is consistent. - storageVolumePackageNames.getOrPut(packageState.volumeUuid) { - mutableListOf() - } += packageState.packageName + storageVolumePackageNames.getOrPut(packageState.volumeUuid) { mutableListOf() } += + packageState.packageName if (packageState.volumeUuid !in mountedStorageVolumes) { // Wait for the storage volume to be mounted and batch the state mutation there. return @@ -2212,23 +2388,26 @@ class PermissionService( return } } - val userIds = if (userId == UserHandle.USER_ALL) { - userManagerService.userIdsIncludingPreCreated - } else { - intArrayOf(userId) - } + val userIds = + if (userId == UserHandle.USER_ALL) { + userManagerService.userIdsIncludingPreCreated + } else { + intArrayOf(userId) + } @Suppress("NAME_SHADOWING") - userIds.forEach { userId -> - service.onPackageInstalled(androidPackage.packageName, userId) - } + userIds.forEach { userId -> service.onPackageInstalled(androidPackage.packageName, userId) } @Suppress("NAME_SHADOWING") userIds.forEach { userId -> // TODO: Remove when this callback receives packageState directly. val packageState = packageManagerInternal.getPackageStateInternal(androidPackage.packageName)!! - addAllowlistedRestrictedPermissionsUnchecked(androidPackage, packageState.appId, - params.allowlistedRestrictedPermissions, userId) + addAllowlistedRestrictedPermissionsUnchecked( + androidPackage, + packageState.appId, + params.allowlistedRestrictedPermissions, + userId + ) setRequestedPermissionStates(packageState, userId, params.permissionStates) } } @@ -2241,11 +2420,12 @@ class PermissionService( sharedUserPkgs: List<AndroidPackage>, userId: Int ) { - val userIds = if (userId == UserHandle.USER_ALL) { - userManagerService.userIdsIncludingPreCreated - } else { - intArrayOf(userId) - } + val userIds = + if (userId == UserHandle.USER_ALL) { + userManagerService.userIdsIncludingPreCreated + } else { + intArrayOf(userId) + } userIds.forEach { service.onPackageUninstalled(packageName, appId, it) } val packageState = packageManagerInternal.packageStates[packageName] if (packageState == null) { @@ -2262,31 +2442,26 @@ class PermissionService( } } - /** - * Check whether a UID is root or system UID. - */ + /** Check whether a UID is root or system UID. */ private fun isRootOrSystemUid(uid: Int) = when (UserHandle.getAppId(uid)) { - Process.ROOT_UID, Process.SYSTEM_UID -> true + Process.ROOT_UID, + Process.SYSTEM_UID -> true else -> false } - /** - * Check whether a UID is shell UID. - */ + /** Check whether a UID is shell UID. */ private fun isShellUid(uid: Int) = UserHandle.getAppId(uid) == Process.SHELL_UID - /** - * Check whether a UID is root, system or shell UID. - */ + /** Check whether a UID is root, system or shell UID. */ private fun isRootOrSystemOrShellUid(uid: Int) = isRootOrSystemUid(uid) || isShellUid(uid) /** * This method should typically only be used when granting or revoking permissions, since the * app may immediately restart after this call. * - * If you're doing surgery on app code/data, use [PackageFreezer] to guard your work against - * the app being relaunched. + * If you're doing surgery on app code/data, use [PackageFreezer] to guard your work against the + * app being relaunched. */ private fun killUid(uid: Int, reason: String) { val activityManager = ActivityManager.getService() @@ -2303,9 +2478,7 @@ class PermissionService( } } - /** - * @see PackageManagerLocal.withFilteredSnapshot - */ + /** @see PackageManagerLocal.withFilteredSnapshot */ private fun PackageManagerLocal.withFilteredSnapshot( callingUid: Int, userId: Int @@ -2323,35 +2496,27 @@ class PermissionService( packageName: String ): PackageState? = packageStates[packageName] - /** - * Check whether a UID belongs to an instant app. - */ + /** Check whether a UID belongs to an instant app. */ private fun PackageManagerLocal.UnfilteredSnapshot.isUidInstantApp(uid: Int): Boolean = // Unfortunately we don't have the API for getting the owner UID of an isolated UID or the // API for getting the SharedUserApi object for an app ID yet, so for now we just keep // calling the old API. packageManagerInternal.getInstantAppPackageName(uid) != null - /** - * Check whether a package is visible to a UID within the same user as the UID. - */ + /** Check whether a package is visible to a UID within the same user as the UID. */ private fun PackageManagerLocal.UnfilteredSnapshot.isPackageVisibleToUid( packageName: String, uid: Int ): Boolean = isPackageVisibleToUid(packageName, UserHandle.getUserId(uid), uid) - /** - * Check whether a package in a particular user is visible to a UID. - */ + /** Check whether a package in a particular user is visible to a UID. */ private fun PackageManagerLocal.UnfilteredSnapshot.isPackageVisibleToUid( packageName: String, userId: Int, uid: Int ): Boolean = filtered(uid, userId).use { it.getPackageState(packageName) != null } - /** - * @see PackageManagerLocal.UnfilteredSnapshot.filtered - */ + /** @see PackageManagerLocal.UnfilteredSnapshot.filtered */ private fun PackageManagerLocal.UnfilteredSnapshot.filtered( callingUid: Int, userId: Int @@ -2374,13 +2539,16 @@ class PermissionService( val callingUid = Binder.getCallingUid() val callingUserId = UserHandle.getUserId(callingUid) if (userId != callingUserId) { - val permissionName = if (enforceFullPermission) { - Manifest.permission.INTERACT_ACROSS_USERS_FULL - } else { - Manifest.permission.INTERACT_ACROSS_USERS - } - if (context.checkCallingOrSelfPermission(permissionName) != - PackageManager.PERMISSION_GRANTED) { + val permissionName = + if (enforceFullPermission) { + Manifest.permission.INTERACT_ACROSS_USERS_FULL + } else { + Manifest.permission.INTERACT_ACROSS_USERS + } + if ( + context.checkCallingOrSelfPermission(permissionName) != + PackageManager.PERMISSION_GRANTED + ) { val exceptionMessage = buildString { if (message != null) { append(message) @@ -2397,9 +2565,11 @@ class PermissionService( } } if (enforceShellRestriction && isShellUid(callingUid)) { - val isShellRestricted = userManagerInternal.hasUserRestriction( - UserManager.DISALLOW_DEBUGGING_FEATURES, userId - ) + val isShellRestricted = + userManagerInternal.hasUserRestriction( + UserManager.DISALLOW_DEBUGGING_FEATURES, + userId + ) if (isShellRestricted) { val exceptionMessage = buildString { if (message != null) { @@ -2424,10 +2594,11 @@ class PermissionService( message: String?, vararg permissionNames: String ) { - val hasAnyPermission = permissionNames.any { permissionName -> - context.checkCallingOrSelfPermission(permissionName) == - PackageManager.PERMISSION_GRANTED - } + val hasAnyPermission = + permissionNames.any { permissionName -> + context.checkCallingOrSelfPermission(permissionName) == + PackageManager.PERMISSION_GRANTED + } if (!hasAnyPermission) { val exceptionMessage = buildString { if (message != null) { @@ -2443,9 +2614,7 @@ class PermissionService( } } - /** - * Callback invoked when interesting actions have been taken on a permission. - */ + /** Callback invoked when interesting actions have been taken on a permission. */ private inner class OnPermissionFlagsChangedListener : AppIdPermissionPolicy.OnPermissionFlagsChangedListener() { private var isPermissionFlagsChanged = false @@ -2476,9 +2645,8 @@ class PermissionService( isPermissionFlagsChanged = true val uid = UserHandle.getUid(userId, appId) - val permission = service.getState { - with(policy) { getPermissions()[permissionName] } - } ?: return + val permission = + service.getState { with(policy) { getPermissions()[permissionName] } } ?: return val wasPermissionGranted = PermissionFlags.isPermissionGranted(oldFlags) val isPermissionGranted = PermissionFlags.isPermissionGranted(newFlags) @@ -2512,16 +2680,20 @@ class PermissionService( runtimePermissionChangedUids.clear() if (!isKillRuntimePermissionRevokedUidsSkipped) { - val reason = if (killRuntimePermissionRevokedUidsReasons.isNotEmpty()) { - killRuntimePermissionRevokedUidsReasons.joinToString(", ") - } else { - PermissionManager.KILL_APP_REASON_PERMISSIONS_REVOKED - } + val reason = + if (killRuntimePermissionRevokedUidsReasons.isNotEmpty()) { + killRuntimePermissionRevokedUidsReasons.joinToString(", ") + } else { + PermissionManager.KILL_APP_REASON_PERMISSIONS_REVOKED + } runtimePermissionRevokedUids.forEachIndexed { - _, uid, areOnlyNotificationsPermissionsRevoked -> + _, + uid, + areOnlyNotificationsPermissionsRevoked -> handler.post { - if (areOnlyNotificationsPermissionsRevoked && - isAppBackupAndRestoreRunning(uid) + if ( + areOnlyNotificationsPermissionsRevoked && + isAppBackupAndRestoreRunning(uid) ) { return@post } @@ -2541,19 +2713,27 @@ class PermissionService( } private fun isAppBackupAndRestoreRunning(uid: Int): Boolean { - if (checkUidPermission(uid, Manifest.permission.BACKUP, Context.DEVICE_ID_DEFAULT) != - PackageManager.PERMISSION_GRANTED) { + if ( + checkUidPermission(uid, Manifest.permission.BACKUP, Context.DEVICE_ID_DEFAULT) != + PackageManager.PERMISSION_GRANTED + ) { return false } return try { val contentResolver = context.contentResolver val userId = UserHandle.getUserId(uid) - val isInSetup = Settings.Secure.getIntForUser( - contentResolver, Settings.Secure.USER_SETUP_COMPLETE, userId - ) == 0 - val isInDeferredSetup = Settings.Secure.getIntForUser( - contentResolver, Settings.Secure.USER_SETUP_PERSONALIZATION_STATE, userId - ) == Settings.Secure.USER_SETUP_PERSONALIZATION_STARTED + val isInSetup = + Settings.Secure.getIntForUser( + contentResolver, + Settings.Secure.USER_SETUP_COMPLETE, + userId + ) == 0 + val isInDeferredSetup = + Settings.Secure.getIntForUser( + contentResolver, + Settings.Secure.USER_SETUP_PERSONALIZATION_STATE, + userId + ) == Settings.Secure.USER_SETUP_PERSONALIZATION_STARTED isInSetup || isInDeferredSetup } catch (e: Settings.SettingNotFoundException) { Slog.w(LOG_TAG, "Failed to check if the user is in restore: $e") @@ -2614,31 +2794,34 @@ class PermissionService( @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) private val BACKGROUND_RATIONALE_CHANGE_ID = 147316723L - private val FULLER_PERMISSIONS = ArrayMap<String, String>().apply { - this[Manifest.permission.ACCESS_COARSE_LOCATION] = - Manifest.permission.ACCESS_FINE_LOCATION - this[Manifest.permission.INTERACT_ACROSS_USERS] = - Manifest.permission.INTERACT_ACROSS_USERS_FULL - } + private val FULLER_PERMISSIONS = + ArrayMap<String, String>().apply { + this[Manifest.permission.ACCESS_COARSE_LOCATION] = + Manifest.permission.ACCESS_FINE_LOCATION + this[Manifest.permission.INTERACT_ACROSS_USERS] = + Manifest.permission.INTERACT_ACROSS_USERS_FULL + } - private val NOTIFICATIONS_PERMISSIONS = arraySetOf( - Manifest.permission.POST_NOTIFICATIONS - ) + private val NOTIFICATIONS_PERMISSIONS = arraySetOf(Manifest.permission.POST_NOTIFICATIONS) - private const val REVIEW_REQUIRED_FLAGS = PermissionFlags.LEGACY_GRANTED or - PermissionFlags.IMPLICIT - private const val UNREQUESTABLE_MASK = PermissionFlags.RESTRICTION_REVOKED or - PermissionFlags.SYSTEM_FIXED or PermissionFlags.POLICY_FIXED or - PermissionFlags.USER_FIXED + private const val REVIEW_REQUIRED_FLAGS = + PermissionFlags.LEGACY_GRANTED or PermissionFlags.IMPLICIT + private const val UNREQUESTABLE_MASK = + PermissionFlags.RESTRICTION_REVOKED or + PermissionFlags.SYSTEM_FIXED or + PermissionFlags.POLICY_FIXED or + PermissionFlags.USER_FIXED private val BACKUP_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(60) - /** Cap the size of permission trees that 3rd party apps can define; in characters of text */ + /** + * Cap the size of permission trees that 3rd party apps can define; in characters of text + */ private const val MAX_PERMISSION_TREE_FOOTPRINT = 32768 private const val PERMISSION_ALLOWLIST_MASK = PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE or - PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM or - PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER + PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM or + PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER } } diff --git a/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt b/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt index bd829351941c..6b20ef1d5519 100644 --- a/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt +++ b/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt @@ -25,9 +25,7 @@ import java.io.FileNotFoundException import java.io.FileOutputStream import java.io.IOException -/** - * Read from an [AtomicFile], fallback to reserve file to read the data. - */ +/** Read from an [AtomicFile], fallback to reserve file to read the data. */ @Throws(Exception::class) inline fun AtomicFile.readWithReserveCopy(block: (FileInputStream) -> Unit) { try { @@ -46,9 +44,7 @@ inline fun AtomicFile.readWithReserveCopy(block: (FileInputStream) -> Unit) { } } -/** - * Write to actual file and reserve file. - */ +/** Write to actual file and reserve file. */ @Throws(IOException::class) inline fun AtomicFile.writeWithReserveCopy(block: (FileOutputStream) -> Unit) { val reserveFile = File(baseFile.parentFile, baseFile.name + ".reservecopy") @@ -66,9 +62,7 @@ inline fun AtomicFile.writeWithReserveCopy(block: (FileOutputStream) -> Unit) { } } -/** - * Write to an [AtomicFile] and close everything safely when done. - */ +/** Write to an [AtomicFile] and close everything safely when done. */ @Throws(IOException::class) // Renamed to writeInlined() to avoid conflict with the hidden AtomicFile.write() that isn't inline. inline fun AtomicFile.writeInlined(block: (FileOutputStream) -> Unit) { diff --git a/services/permission/java/com/android/server/permission/access/util/BinaryXmlPullParserExtensions.kt b/services/permission/java/com/android/server/permission/access/util/BinaryXmlPullParserExtensions.kt index 1d27aef39a2c..6ab73c76007e 100644 --- a/services/permission/java/com/android/server/permission/access/util/BinaryXmlPullParserExtensions.kt +++ b/services/permission/java/com/android/server/permission/access/util/BinaryXmlPullParserExtensions.kt @@ -22,9 +22,7 @@ import java.io.InputStream import org.xmlpull.v1.XmlPullParser import org.xmlpull.v1.XmlPullParserException -/** - * Parse content from [InputStream] with [BinaryXmlPullParser]. - */ +/** Parse content from [InputStream] with [BinaryXmlPullParser]. */ @Throws(IOException::class, XmlPullParserException::class) inline fun InputStream.parseBinaryXml(block: BinaryXmlPullParser.() -> Unit) { BinaryXmlPullParser().apply { @@ -35,6 +33,7 @@ inline fun InputStream.parseBinaryXml(block: BinaryXmlPullParser.() -> Unit) { /** * Iterate through child tags of the current tag. + * * <p> * Attributes for the current tag needs to be accessed before this method is called because this * method will advance the parser past the start tag of the current tag. The code inspecting each @@ -50,7 +49,8 @@ inline fun InputStream.parseBinaryXml(block: BinaryXmlPullParser.() -> Unit) { inline fun BinaryXmlPullParser.forEachTag(block: BinaryXmlPullParser.() -> Unit) { when (val eventType = eventType) { // Document start or start tag of the parent tag. - XmlPullParser.START_DOCUMENT, XmlPullParser.START_TAG -> nextTagOrEnd() + XmlPullParser.START_DOCUMENT, + XmlPullParser.START_TAG -> nextTagOrEnd() else -> throw XmlPullParserException("Unexpected event type $eventType") } while (true) { @@ -90,7 +90,8 @@ inline fun BinaryXmlPullParser.forEachTag(block: BinaryXmlPullParser.() -> Unit) nextTagOrEnd() } // End tag of the parent tag, or document end. - XmlPullParser.END_TAG, XmlPullParser.END_DOCUMENT -> break + XmlPullParser.END_TAG, + XmlPullParser.END_DOCUMENT -> break else -> throw XmlPullParserException("Unexpected event type $eventType") } } @@ -107,193 +108,146 @@ inline fun BinaryXmlPullParser.forEachTag(block: BinaryXmlPullParser.() -> Unit) inline fun BinaryXmlPullParser.nextTagOrEnd(): Int { while (true) { when (val eventType = next()) { - XmlPullParser.START_TAG, XmlPullParser.END_TAG, XmlPullParser.END_DOCUMENT -> - return eventType + XmlPullParser.START_TAG, + XmlPullParser.END_TAG, + XmlPullParser.END_DOCUMENT -> return eventType else -> continue } } } -/** - * @see BinaryXmlPullParser.getName - */ +/** @see BinaryXmlPullParser.getName */ inline val BinaryXmlPullParser.tagName: String get() = name -/** - * Check whether an attribute exists for the current tag. - */ +/** Check whether an attribute exists for the current tag. */ @Suppress("NOTHING_TO_INLINE") inline fun BinaryXmlPullParser.hasAttribute(name: String): Boolean = getAttributeIndex(name) != -1 -/** - * @see BinaryXmlPullParser.getAttributeIndex - */ +/** @see BinaryXmlPullParser.getAttributeIndex */ @Suppress("NOTHING_TO_INLINE") inline fun BinaryXmlPullParser.getAttributeIndex(name: String): Int = getAttributeIndex(null, name) -/** - * @see BinaryXmlPullParser.getAttributeIndexOrThrow - */ +/** @see BinaryXmlPullParser.getAttributeIndexOrThrow */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeIndexOrThrow(name: String): Int = getAttributeIndexOrThrow(null, name) -/** - * @see BinaryXmlPullParser.getAttributeValue - */ +/** @see BinaryXmlPullParser.getAttributeValue */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeValue(name: String): String? = getAttributeValue(null, name) -/** - * @see BinaryXmlPullParser.getAttributeValue - */ +/** @see BinaryXmlPullParser.getAttributeValue */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeValueOrThrow(name: String): String = getAttributeValue(getAttributeIndexOrThrow(name)) -/** - * @see BinaryXmlPullParser.getAttributeBytesHex - */ +/** @see BinaryXmlPullParser.getAttributeBytesHex */ @Suppress("NOTHING_TO_INLINE") inline fun BinaryXmlPullParser.getAttributeBytesHex(name: String): ByteArray? = getAttributeBytesHex(null, name, null) -/** - * @see BinaryXmlPullParser.getAttributeBytesHex - */ +/** @see BinaryXmlPullParser.getAttributeBytesHex */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeBytesHexOrThrow(name: String): ByteArray = getAttributeBytesHex(null, name) -/** - * @see BinaryXmlPullParser.getAttributeBytesBase64 - */ +/** @see BinaryXmlPullParser.getAttributeBytesBase64 */ @Suppress("NOTHING_TO_INLINE") inline fun BinaryXmlPullParser.getAttributeBytesBase64(name: String): ByteArray? = getAttributeBytesBase64(null, name, null) -/** - * @see BinaryXmlPullParser.getAttributeBytesBase64 - */ +/** @see BinaryXmlPullParser.getAttributeBytesBase64 */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeBytesBase64OrThrow(name: String): ByteArray = getAttributeBytesBase64(null, name) -/** - * @see BinaryXmlPullParser.getAttributeInt - */ +/** @see BinaryXmlPullParser.getAttributeInt */ @Suppress("NOTHING_TO_INLINE") inline fun BinaryXmlPullParser.getAttributeIntOrDefault(name: String, defaultValue: Int): Int = getAttributeInt(null, name, defaultValue) -/** - * @see BinaryXmlPullParser.getAttributeInt - */ +/** @see BinaryXmlPullParser.getAttributeInt */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeIntOrThrow(name: String): Int = getAttributeInt(null, name) -/** - * @see BinaryXmlPullParser.getAttributeIntHex - */ +/** @see BinaryXmlPullParser.getAttributeIntHex */ @Suppress("NOTHING_TO_INLINE") inline fun BinaryXmlPullParser.getAttributeIntHexOrDefault(name: String, defaultValue: Int): Int = getAttributeIntHex(null, name, defaultValue) -/** - * @see BinaryXmlPullParser.getAttributeIntHex - */ +/** @see BinaryXmlPullParser.getAttributeIntHex */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeIntHexOrThrow(name: String): Int = getAttributeIntHex(null, name) -/** - * @see BinaryXmlPullParser.getAttributeLong - */ +/** @see BinaryXmlPullParser.getAttributeLong */ @Suppress("NOTHING_TO_INLINE") inline fun BinaryXmlPullParser.getAttributeLongOrDefault(name: String, defaultValue: Long): Long = getAttributeLong(null, name, defaultValue) -/** - * @see BinaryXmlPullParser.getAttributeLong - */ +/** @see BinaryXmlPullParser.getAttributeLong */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeLongOrThrow(name: String): Long = getAttributeLong(null, name) -/** - * @see BinaryXmlPullParser.getAttributeLongHex - */ +/** @see BinaryXmlPullParser.getAttributeLongHex */ @Suppress("NOTHING_TO_INLINE") inline fun BinaryXmlPullParser.getAttributeLongHexOrDefault( name: String, defaultValue: Long ): Long = getAttributeLongHex(null, name, defaultValue) -/** - * @see BinaryXmlPullParser.getAttributeLongHex - */ +/** @see BinaryXmlPullParser.getAttributeLongHex */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeLongHexOrThrow(name: String): Long = getAttributeLongHex(null, name) -/** - * @see BinaryXmlPullParser.getAttributeFloat - */ +/** @see BinaryXmlPullParser.getAttributeFloat */ @Suppress("NOTHING_TO_INLINE") inline fun BinaryXmlPullParser.getAttributeFloatOrDefault( name: String, defaultValue: Float ): Float = getAttributeFloat(null, name, defaultValue) -/** - * @see BinaryXmlPullParser.getAttributeFloat - */ +/** @see BinaryXmlPullParser.getAttributeFloat */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeFloatOrThrow(name: String): Float = getAttributeFloat(null, name) -/** - * @see BinaryXmlPullParser.getAttributeDouble - */ +/** @see BinaryXmlPullParser.getAttributeDouble */ @Suppress("NOTHING_TO_INLINE") inline fun BinaryXmlPullParser.getAttributeDoubleOrDefault( name: String, defaultValue: Double ): Double = getAttributeDouble(null, name, defaultValue) -/** - * @see BinaryXmlPullParser.getAttributeDouble - */ +/** @see BinaryXmlPullParser.getAttributeDouble */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeDoubleOrThrow(name: String): Double = getAttributeDouble(null, name) -/** - * @see BinaryXmlPullParser.getAttributeBoolean - */ +/** @see BinaryXmlPullParser.getAttributeBoolean */ @Suppress("NOTHING_TO_INLINE") inline fun BinaryXmlPullParser.getAttributeBooleanOrDefault( name: String, defaultValue: Boolean ): Boolean = getAttributeBoolean(null, name, defaultValue) -/** - * @see BinaryXmlPullParser.getAttributeBoolean - */ +/** @see BinaryXmlPullParser.getAttributeBoolean */ @Suppress("NOTHING_TO_INLINE") @Throws(XmlPullParserException::class) inline fun BinaryXmlPullParser.getAttributeBooleanOrThrow(name: String): Boolean = diff --git a/services/permission/java/com/android/server/permission/access/util/BinaryXmlSerializerExtensions.kt b/services/permission/java/com/android/server/permission/access/util/BinaryXmlSerializerExtensions.kt index c8cd5866adbc..6500a7d2e0a2 100644 --- a/services/permission/java/com/android/server/permission/access/util/BinaryXmlSerializerExtensions.kt +++ b/services/permission/java/com/android/server/permission/access/util/BinaryXmlSerializerExtensions.kt @@ -20,9 +20,7 @@ import com.android.modules.utils.BinaryXmlSerializer import java.io.IOException import java.io.OutputStream -/** - * Serialize content into [OutputStream] with [BinaryXmlSerializer]. - */ +/** Serialize content into [OutputStream] with [BinaryXmlSerializer]. */ @Throws(IOException::class) inline fun OutputStream.serializeBinaryXml(block: BinaryXmlSerializer.() -> Unit) { BinaryXmlSerializer().apply { @@ -57,54 +55,42 @@ inline fun BinaryXmlSerializer.tag(name: String, block: BinaryXmlSerializer.() - endTag(null, name) } -/** - * @see BinaryXmlSerializer.attribute - */ +/** @see BinaryXmlSerializer.attribute */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attribute(name: String, value: String) { attribute(null, name, value) } -/** - * @see BinaryXmlSerializer.attributeInterned - */ +/** @see BinaryXmlSerializer.attributeInterned */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeInterned(name: String, value: String) { attributeInterned(null, name, value) } -/** - * @see BinaryXmlSerializer.attributeBytesHex - */ +/** @see BinaryXmlSerializer.attributeBytesHex */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeBytesHex(name: String, value: ByteArray) { attributeBytesHex(null, name, value) } -/** - * @see BinaryXmlSerializer.attributeBytesBase64 - */ +/** @see BinaryXmlSerializer.attributeBytesBase64 */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeBytesBase64(name: String, value: ByteArray) { attributeBytesBase64(null, name, value) } -/** - * @see BinaryXmlSerializer.attributeInt - */ +/** @see BinaryXmlSerializer.attributeInt */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeInt(name: String, value: Int) { attributeInt(null, name, value) } -/** - * @see BinaryXmlSerializer.attributeInt - */ +/** @see BinaryXmlSerializer.attributeInt */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeIntWithDefault( @@ -117,18 +103,14 @@ inline fun BinaryXmlSerializer.attributeIntWithDefault( } } -/** - * @see BinaryXmlSerializer.attributeIntHex - */ +/** @see BinaryXmlSerializer.attributeIntHex */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeIntHex(name: String, value: Int) { attributeIntHex(null, name, value) } -/** - * @see BinaryXmlSerializer.attributeIntHex - */ +/** @see BinaryXmlSerializer.attributeIntHex */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeIntHexWithDefault( @@ -141,18 +123,14 @@ inline fun BinaryXmlSerializer.attributeIntHexWithDefault( } } -/** - * @see BinaryXmlSerializer.attributeLong - */ +/** @see BinaryXmlSerializer.attributeLong */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeLong(name: String, value: Long) { attributeLong(null, name, value) } -/** - * @see BinaryXmlSerializer.attributeLong - */ +/** @see BinaryXmlSerializer.attributeLong */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeLongWithDefault( @@ -165,18 +143,14 @@ inline fun BinaryXmlSerializer.attributeLongWithDefault( } } -/** - * @see BinaryXmlSerializer.attributeLongHex - */ +/** @see BinaryXmlSerializer.attributeLongHex */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeLongHex(name: String, value: Long) { attributeLongHex(null, name, value) } -/** - * @see BinaryXmlSerializer.attributeLongHex - */ +/** @see BinaryXmlSerializer.attributeLongHex */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeLongHexWithDefault( @@ -189,18 +163,14 @@ inline fun BinaryXmlSerializer.attributeLongHexWithDefault( } } -/** - * @see BinaryXmlSerializer.attributeFloat - */ +/** @see BinaryXmlSerializer.attributeFloat */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeFloat(name: String, value: Float) { attributeFloat(null, name, value) } -/** - * @see BinaryXmlSerializer.attributeFloat - */ +/** @see BinaryXmlSerializer.attributeFloat */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeFloatWithDefault( @@ -213,18 +183,14 @@ inline fun BinaryXmlSerializer.attributeFloatWithDefault( } } -/** - * @see BinaryXmlSerializer.attributeDouble - */ +/** @see BinaryXmlSerializer.attributeDouble */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeDouble(name: String, value: Double) { attributeDouble(null, name, value) } -/** - * @see BinaryXmlSerializer.attributeDouble - */ +/** @see BinaryXmlSerializer.attributeDouble */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeDoubleWithDefault( @@ -237,18 +203,14 @@ inline fun BinaryXmlSerializer.attributeDoubleWithDefault( } } -/** - * @see BinaryXmlSerializer.attributeBoolean - */ +/** @see BinaryXmlSerializer.attributeBoolean */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeBoolean(name: String, value: Boolean) { attributeBoolean(null, name, value) } -/** - * @see BinaryXmlSerializer.attributeBoolean - */ +/** @see BinaryXmlSerializer.attributeBoolean */ @Suppress("NOTHING_TO_INLINE") @Throws(IOException::class) inline fun BinaryXmlSerializer.attributeBooleanWithDefault( diff --git a/services/permission/java/com/android/server/permission/access/util/PackageVersionMigration.kt b/services/permission/java/com/android/server/permission/access/util/PackageVersionMigration.kt index a61489cd21c5..3ef284b05961 100644 --- a/services/permission/java/com/android/server/permission/access/util/PackageVersionMigration.kt +++ b/services/permission/java/com/android/server/permission/access/util/PackageVersionMigration.kt @@ -22,13 +22,13 @@ import com.android.server.pm.permission.PermissionMigrationHelper object PackageVersionMigration { /** - * Maps existing permission and app-op version to a unified version during OTA upgrade. The - * new unified version is used in determining the upgrade steps for a package (for both - * permission and app-ops). + * Maps existing permission and app-op version to a unified version during OTA upgrade. The new + * unified version is used in determining the upgrade steps for a package (for both permission + * and app-ops). * * @return unified permission/app-op version * @throws IllegalStateException if the method is called when there is nothing to migrate i.e. - * permission and app-op file does not exist. + * permission and app-op file does not exist. */ internal fun getVersion(userId: Int): Int { val permissionMigrationHelper = diff --git a/services/permission/java/com/android/server/permission/access/util/PermissionApex.kt b/services/permission/java/com/android/server/permission/access/util/PermissionApex.kt index e6b4e3e67b61..3d835e8c3cf3 100644 --- a/services/permission/java/com/android/server/permission/access/util/PermissionApex.kt +++ b/services/permission/java/com/android/server/permission/access/util/PermissionApex.kt @@ -23,15 +23,11 @@ import java.io.File object PermissionApex { private const val MODULE_NAME = "com.android.permission" - /** - * @see ApexEnvironment.getDeviceProtectedDataDir - */ + /** @see ApexEnvironment.getDeviceProtectedDataDir */ val systemDataDirectory: File get() = apexEnvironment.deviceProtectedDataDir - /** - * @see ApexEnvironment.getDeviceProtectedDataDirForUser - */ + /** @see ApexEnvironment.getDeviceProtectedDataDirForUser */ fun getUserDataDirectory(userId: Int): File = apexEnvironment.getDeviceProtectedDataDirForUser(UserHandle.of(userId)) diff --git a/services/tests/displayservicetests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java index c280349a0559..79222c063989 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java @@ -23,6 +23,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -37,6 +38,7 @@ import androidx.test.InstrumentationRegistry; import com.android.dx.mockito.inline.extended.ExtendedMockito; import com.android.internal.R; +import com.android.server.display.feature.DisplayManagerFlags; import org.junit.After; import org.junit.Before; @@ -54,6 +56,8 @@ public class DisplayWhiteBalanceTintControllerTest { private Resources mMockedResources; @Mock private DisplayManagerInternal mDisplayManagerInternal; + @Mock + private DisplayManagerFlags mDisplayManagerFlagsMock; private MockitoSession mSession; private Resources mResources; @@ -63,10 +67,6 @@ public class DisplayWhiteBalanceTintControllerTest { @Before public void setUp() { DisplayManagerInternal displayManagerInternal = mock(DisplayManagerInternal.class); - mDisplayWhiteBalanceTintController = - new DisplayWhiteBalanceTintController(displayManagerInternal); - mDisplayWhiteBalanceTintController.setUp(InstrumentationRegistry.getContext(), true); - mDisplayWhiteBalanceTintController.setActivated(true); mSession = ExtendedMockito.mockitoSession() .initMocks(this) @@ -74,6 +74,13 @@ public class DisplayWhiteBalanceTintControllerTest { .strictness(Strictness.LENIENT) .startMocking(); + mDisplayWhiteBalanceTintController = + new DisplayWhiteBalanceTintController(displayManagerInternal, + mDisplayManagerFlagsMock); + mDisplayWhiteBalanceTintController.setUp(InstrumentationRegistry.getContext(), true); + mDisplayWhiteBalanceTintController.setActivated(true); + + mResources = InstrumentationRegistry.getContext().getResources(); // These Resources are common to all tests. doReturn(4000) @@ -360,9 +367,47 @@ public class DisplayWhiteBalanceTintControllerTest { 1e-6f /* tolerance */); } + @Test + public void testDisplayWhiteBalance_TransitionTimes() { + when(mDisplayManagerFlagsMock.isAdaptiveTone2Enabled()).thenReturn(false); + setUpTransitionTimes(); + setUpTintController(); + + assertEquals(30L, + mDisplayWhiteBalanceTintController.getTransitionDurationMilliseconds(true)); + assertEquals(30L, + mDisplayWhiteBalanceTintController.getTransitionDurationMilliseconds(false)); + } + + @Test + public void testDisplayWhiteBalance_TransitionTimesDirectional() { + when(mDisplayManagerFlagsMock.isAdaptiveTone2Enabled()).thenReturn(true); + setUpTransitionTimes(); + setUpTintController(); + + assertEquals(400L, + mDisplayWhiteBalanceTintController.getTransitionDurationMilliseconds(true)); + assertEquals(5000L, + mDisplayWhiteBalanceTintController.getTransitionDurationMilliseconds(false)); + } + + + private void setUpTransitionTimes() { + doReturn(mResources.getStringArray(R.array.config_displayWhiteBalanceDisplayPrimaries)) + .when(mMockedResources) + .getStringArray(R.array.config_displayWhiteBalanceDisplayPrimaries); + when(mMockedResources.getInteger( + R.integer.config_displayWhiteBalanceTransitionTime)).thenReturn(30); + when(mMockedResources.getInteger( + R.integer.config_displayWhiteBalanceTransitionTimeIncrease)).thenReturn(400); + when(mMockedResources.getInteger( + R.integer.config_displayWhiteBalanceTransitionTimeDecrease)).thenReturn(5000); + + } + private void setUpTintController() { mDisplayWhiteBalanceTintController = new DisplayWhiteBalanceTintController( - mDisplayManagerInternal); + mDisplayManagerInternal, mDisplayManagerFlagsMock); mDisplayWhiteBalanceTintController.setUp(mMockedContext, true); mDisplayWhiteBalanceTintController.setActivated(true); } 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/job/controllers/PrefetchControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java index 7cc01e1b4292..4329b6fbc8e3 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java @@ -160,6 +160,8 @@ public class PrefetchControllerTest { mPrefetchController = new PrefetchController(mJobSchedulerService); mPcConstants = mPrefetchController.getPcConstants(); + setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_THRESHOLD_MS, 7 * HOUR_IN_MILLIS); + setUidBias(Process.myUid(), JobInfo.BIAS_DEFAULT); verify(mUsageStatsManagerInternal) diff --git a/services/tests/servicestests/res/xml/usertypes_test_profile.xml b/services/tests/servicestests/res/xml/usertypes_test_profile.xml index 0115db63e56b..ef19ba11fb6d 100644 --- a/services/tests/servicestests/res/xml/usertypes_test_profile.xml +++ b/services/tests/servicestests/res/xml/usertypes_test_profile.xml @@ -40,6 +40,7 @@ mediaSharedWithParent='true' credentialShareableWithParent='false' showInSettings='23' + hideInSettingsInQuietMode='true' inheritDevicePolicy='450' deleteAppWithParent='false' alwaysVisible='true' diff --git a/services/tests/servicestests/src/com/android/server/accessibility/FlashNotificationsControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/FlashNotificationsControllerTest.java index 8a057df7e836..0988eeab5913 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/FlashNotificationsControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/FlashNotificationsControllerTest.java @@ -366,6 +366,7 @@ public class FlashNotificationsControllerTest { private void assumeCameraTorchAvailable() { assumeTrue(mCameraManager != null); assumeTrue(!mCameraInfoMap.isEmpty()); + assumeTrue(mCameraInfoMap.values().stream().anyMatch(info -> info.mIsValid)); } private void simulateCallSequence() throws InterruptedException { diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java index 430f600a6288..caa9e7c3aecb 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java @@ -1065,7 +1065,7 @@ public class FullScreenMagnificationGestureHandlerTest { mMgh.clearAndTransitionToStateDetecting(); break; case STATE_ACTIVATED: - if (mMgh.mDetectTripleTap) { + if (mMgh.mDetectSingleFingerTripleTap) { goFromStateIdleTo(STATE_2TAPS); tap(); } else { @@ -1147,7 +1147,7 @@ public class FullScreenMagnificationGestureHandlerTest { break; case STATE_ACTIVATED: case STATE_ZOOMED_OUT_FROM_SERVICE: - if (mMgh.mDetectTripleTap) { + if (mMgh.mDetectSingleFingerTripleTap) { tap(); tap(); returnToNormalFrom(STATE_ACTIVATED_2TAPS); diff --git a/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java b/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java index 70527ce2ad32..44d676052352 100644 --- a/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java @@ -238,7 +238,7 @@ public class AnrTimerTest { } @Override - Handler getHandler(Handler.Callback callback) { + Handler newHandler(Handler.Callback callback) { if (mTestHandler == null) { mTestHandler = new TestHandler(mHandler.getLooper(), callback, mImmediate); } @@ -250,14 +250,18 @@ public class AnrTimerTest { return mTestHandler; } + /** + * This override returns the tracker supplied in the constructor. It does not create a + * new one. + */ @Override - AnrTimer.CpuTracker getTracker() { + AnrTimer.CpuTracker newTracker() { return mTracker; } /** For test purposes, always enable the feature. */ @Override - boolean getFeatureEnabled() { + boolean isFeatureEnabled() { return true; } } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java index 0230d77e8e14..e3e708ec856d 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java @@ -47,6 +47,7 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; 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.verifyNoMoreInteractions; import static org.mockito.Mockito.when; @@ -64,6 +65,7 @@ import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricPrompt; import android.hardware.biometrics.IBiometricAuthenticator; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; +import android.hardware.biometrics.IBiometricPromptStatusListener; import android.hardware.biometrics.IBiometricSensorReceiver; import android.hardware.biometrics.IBiometricService; import android.hardware.biometrics.IBiometricServiceReceiver; @@ -1751,6 +1753,45 @@ public class BiometricServiceTest { verifyNoMoreInteractions(callback); } + @Test + public void testRegisterBiometricPromptOnKeyguardCallback_authenticationAlreadyStarted() + throws Exception { + final IBiometricPromptStatusListener callback = + mock(IBiometricPromptStatusListener.class); + + setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG); + invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, + true /* requireConfirmation */, null /* authenticators */); + mBiometricService.mImpl.registerBiometricPromptStatusListener(callback); + + verify(callback).onBiometricPromptShowing(); + } + + @Test + public void testRegisterBiometricPromptOnKeyguardCallback_startAuth_dismissDialog() + throws Exception { + final IBiometricPromptStatusListener listener = + mock(IBiometricPromptStatusListener.class); + setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); + mBiometricService.mImpl.registerBiometricPromptStatusListener(listener); + waitForIdle(); + + verify(listener).onBiometricPromptIdle(); + + invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, + true /* requireConfirmation */, null /* authenticators */); + waitForIdle(); + + verify(listener).onBiometricPromptShowing(); + + final byte[] hat = generateRandomHAT(); + mBiometricService.mAuthSession.mSysuiReceiver.onDialogDismissed( + BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED, hat); + waitForIdle(); + + verify(listener, times(2)).onBiometricPromptIdle(); + } + // Helper methods private int invokeCanAuthenticate(BiometricService service, int authenticators) 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/contentcapture/ContentCaptureManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java index e7777f75b6df..67b70684eede 100644 --- a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java @@ -436,6 +436,24 @@ public class ContentCaptureManagerServiceTest { verify(mMockRemoteContentProtectionService).onLoginDetected(PARCELED_EVENTS); } + @Test + public void parseContentProtectionGroupsConfig_null() { + ContentCaptureManagerService service = new ContentCaptureManagerService(sContext); + assertThat(service.parseContentProtectionGroupsConfig(null)).isEmpty(); + } + + @Test + public void parseContentProtectionGroupsConfig_empty() { + ContentCaptureManagerService service = new ContentCaptureManagerService(sContext); + assertThat(service.parseContentProtectionGroupsConfig("")).isEmpty(); + } + + @Test + public void parseContentProtectionGroupsConfig_notEmpty() { + ContentCaptureManagerService service = new ContentCaptureManagerService(sContext); + assertThat(service.parseContentProtectionGroupsConfig("a")).isEmpty(); + } + private class TestContentCaptureManagerService extends ContentCaptureManagerService { TestContentCaptureManagerService() { diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java index 7d735632b675..7dcfc88e998c 100644 --- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java @@ -284,6 +284,7 @@ public final class DeviceStateManagerServiceTest { assertEquals(info.currentState, DEFAULT_DEVICE_STATE.getIdentifier()); } + @FlakyTest(bugId = 297949293) @Test public void getDeviceStateInfo_baseStateAndCommittedStateNotSet() throws RemoteException { // Create a provider and a service without an initial base state. 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/net/LockdownVpnTrackerTest.java b/services/tests/servicestests/src/com/android/server/net/LockdownVpnTrackerTest.java new file mode 100644 index 000000000000..949f8e7a6ab0 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/net/LockdownVpnTrackerTest.java @@ -0,0 +1,335 @@ +/* + * 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.net; + +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; +import static android.net.VpnManager.NOTIFICATION_CHANNEL_VPN; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.app.Notification; +import android.app.NotificationManager; +import android.content.Context; +import android.content.ContextWrapper; +import android.net.ConnectivityManager; +import android.net.ConnectivityManager.NetworkCallback; +import android.net.LinkAddress; +import android.net.LinkProperties; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.NetworkInfo; +import android.os.Handler; +import android.os.HandlerThread; +import android.text.TextUtils; + +import androidx.test.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.internal.R; +import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; +import com.android.internal.net.VpnConfig; +import com.android.internal.net.VpnProfile; +import com.android.server.connectivity.Vpn; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class LockdownVpnTrackerTest { + private static final NetworkCapabilities TEST_CELL_NC = new NetworkCapabilities.Builder() + .addTransportType(TRANSPORT_CELLULAR) + .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED) + .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED) + .build(); + private static final LinkProperties TEST_CELL_LP = new LinkProperties(); + + static { + TEST_CELL_LP.setInterfaceName("rmnet0"); + TEST_CELL_LP.addLinkAddress(new LinkAddress("192.0.2.2/25")); + } + + // Use a context wrapper instead of a mock since LockdownVpnTracker builds notifications which + // is tedious and currently unnecessary to mock. + private final Context mContext = new ContextWrapper(InstrumentationRegistry.getContext()) { + @Override + public Object getSystemService(String name) { + if (Context.CONNECTIVITY_SERVICE.equals(name)) return mCm; + if (Context.NOTIFICATION_SERVICE.equals(name)) return mNotificationManager; + + return super.getSystemService(name); + } + }; + @Mock private ConnectivityManager mCm; + @Mock private Vpn mVpn; + @Mock private NotificationManager mNotificationManager; + @Mock private NetworkInfo mVpnNetworkInfo; + @Mock private VpnConfig mVpnConfig; + @Mock private Network mNetwork; + @Mock private Network mNetwork2; + @Mock private Network mVpnNetwork; + + private HandlerThread mHandlerThread; + private Handler mHandler; + private VpnProfile mProfile; + + private VpnProfile createTestVpnProfile() { + final String profileName = "testVpnProfile"; + final VpnProfile profile = new VpnProfile(profileName); + profile.name = "My VPN"; + profile.server = "192.0.2.1"; + profile.dnsServers = "8.8.8.8"; + profile.ipsecIdentifier = "My ipsecIdentifier"; + profile.ipsecSecret = "My PSK"; + profile.type = VpnProfile.TYPE_IKEV2_IPSEC_PSK; + + return profile; + } + + private NetworkCallback getDefaultNetworkCallback() { + final ArgumentCaptor<NetworkCallback> callbackCaptor = + ArgumentCaptor.forClass(NetworkCallback.class); + verify(mCm).registerSystemDefaultNetworkCallback(callbackCaptor.capture(), eq(mHandler)); + return callbackCaptor.getValue(); + } + + private NetworkCallback getVpnNetworkCallback() { + final ArgumentCaptor<NetworkCallback> callbackCaptor = + ArgumentCaptor.forClass(NetworkCallback.class); + verify(mCm).registerNetworkCallback(any(), callbackCaptor.capture(), eq(mHandler)); + return callbackCaptor.getValue(); + } + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mHandlerThread = new HandlerThread("LockdownVpnTrackerTest"); + mHandlerThread.start(); + mHandler = mHandlerThread.getThreadHandler(); + + doReturn(mVpnNetworkInfo).when(mVpn).getNetworkInfo(); + doReturn(false).when(mVpnNetworkInfo).isConnectedOrConnecting(); + doReturn(mVpnConfig).when(mVpn).getLegacyVpnConfig(); + // mVpnConfig is a mock but the production code will try to add addresses in this array + // assuming it's non-null, so it needs to be initialized. + mVpnConfig.addresses = new ArrayList<>(); + + mProfile = createTestVpnProfile(); + } + + @After + public void tearDown() throws Exception { + if (mHandlerThread != null) { + mHandlerThread.quitSafely(); + mHandlerThread.join(); + } + } + + private LockdownVpnTracker initAndVerifyLockdownVpnTracker() { + final LockdownVpnTracker lockdownVpnTracker = + new LockdownVpnTracker(mContext, mHandler, mVpn, mProfile); + lockdownVpnTracker.init(); + verify(mVpn).setEnableTeardown(false); + verify(mVpn).setLockdown(true); + verify(mCm).setLegacyLockdownVpnEnabled(true); + verify(mVpn).stopVpnRunnerPrivileged(); + verify(mNotificationManager).cancel(any(), eq(SystemMessage.NOTE_VPN_STATUS)); + + return lockdownVpnTracker; + } + + private void callCallbacksForNetworkConnect(NetworkCallback callback, Network network, + NetworkCapabilities nc, LinkProperties lp, boolean blocked) { + callback.onAvailable(network); + callback.onCapabilitiesChanged(network, nc); + callback.onLinkPropertiesChanged(network, lp); + callback.onBlockedStatusChanged(network, blocked); + } + + private void callCallbacksForNetworkConnect(NetworkCallback callback, Network network) { + callCallbacksForNetworkConnect( + callback, network, TEST_CELL_NC, TEST_CELL_LP, true /* blocked */); + } + + private boolean isExpectedNotification(Notification notification, int titleRes, int iconRes) { + if (!NOTIFICATION_CHANNEL_VPN.equals(notification.getChannelId())) { + return false; + } + final CharSequence expectedTitle = mContext.getString(titleRes); + final CharSequence actualTitle = notification.extras.getCharSequence( + Notification.EXTRA_TITLE); + if (!TextUtils.equals(expectedTitle, actualTitle)) { + return false; + } + return notification.getSmallIcon().getResId() == iconRes; + } + + @Test + public void testShutdown() { + final LockdownVpnTracker lockdownVpnTracker = initAndVerifyLockdownVpnTracker(); + final NetworkCallback defaultCallback = getDefaultNetworkCallback(); + final NetworkCallback vpnCallback = getVpnNetworkCallback(); + clearInvocations(mVpn, mCm, mNotificationManager); + + lockdownVpnTracker.shutdown(); + verify(mVpn).stopVpnRunnerPrivileged(); + verify(mVpn).setLockdown(false); + verify(mCm).setLegacyLockdownVpnEnabled(false); + verify(mNotificationManager).cancel(any(), eq(SystemMessage.NOTE_VPN_STATUS)); + verify(mVpn).setEnableTeardown(true); + verify(mCm).unregisterNetworkCallback(defaultCallback); + verify(mCm).unregisterNetworkCallback(vpnCallback); + } + + @Test + public void testDefaultNetworkConnected() { + initAndVerifyLockdownVpnTracker(); + final NetworkCallback defaultCallback = getDefaultNetworkCallback(); + clearInvocations(mVpn, mCm, mNotificationManager); + + // mNetwork connected and available. + callCallbacksForNetworkConnect(defaultCallback, mNetwork); + + // Vpn is starting + verify(mVpn).startLegacyVpnPrivileged(mProfile, mNetwork, TEST_CELL_LP); + verify(mNotificationManager).notify(any(), eq(SystemMessage.NOTE_VPN_STATUS), + argThat(notification -> isExpectedNotification(notification, + R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected))); + } + + private void doTestDefaultLpChanged(LinkProperties startingLp, LinkProperties newLp) { + initAndVerifyLockdownVpnTracker(); + final NetworkCallback defaultCallback = getDefaultNetworkCallback(); + callCallbacksForNetworkConnect( + defaultCallback, mNetwork, TEST_CELL_NC, startingLp, true /* blocked */); + clearInvocations(mVpn, mCm, mNotificationManager); + + // LockdownVpnTracker#handleStateChangedLocked() is not called on the same network even if + // the LinkProperties change. + defaultCallback.onLinkPropertiesChanged(mNetwork, newLp); + + // Ideally the VPN should start if it hasn't already, but it doesn't because nothing calls + // LockdownVpnTracker#handleStateChangedLocked. This is a bug. + // TODO: consider fixing this. + verify(mVpn, never()).stopVpnRunnerPrivileged(); + verify(mVpn, never()).startLegacyVpnPrivileged(any(), any(), any()); + verify(mNotificationManager, never()).cancel(any(), eq(SystemMessage.NOTE_VPN_STATUS)); + } + + @Test + public void testDefaultLPChanged_V4AddLinkAddressV4() { + final LinkProperties lp = new LinkProperties(TEST_CELL_LP); + lp.setInterfaceName("rmnet0"); + lp.addLinkAddress(new LinkAddress("192.0.2.3/25")); + doTestDefaultLpChanged(TEST_CELL_LP, lp); + } + + @Test + public void testDefaultLPChanged_V4AddLinkAddressV6() { + final LinkProperties lp = new LinkProperties(); + lp.setInterfaceName("rmnet0"); + lp.addLinkAddress(new LinkAddress("192.0.2.3/25")); + final LinkProperties newLp = new LinkProperties(lp); + newLp.addLinkAddress(new LinkAddress("2001:db8::1/64")); + doTestDefaultLpChanged(lp, newLp); + } + + @Test + public void testDefaultLPChanged_V6AddLinkAddressV4() { + final LinkProperties lp = new LinkProperties(); + lp.setInterfaceName("rmnet0"); + lp.addLinkAddress(new LinkAddress("2001:db8::1/64")); + final LinkProperties newLp = new LinkProperties(lp); + newLp.addLinkAddress(new LinkAddress("192.0.2.3/25")); + doTestDefaultLpChanged(lp, newLp); + } + + @Test + public void testDefaultLPChanged_AddLinkAddressV4() { + final LinkProperties lp = new LinkProperties(); + lp.setInterfaceName("rmnet0"); + doTestDefaultLpChanged(lp, TEST_CELL_LP); + } + + @Test + public void testDefaultNetworkChanged() { + initAndVerifyLockdownVpnTracker(); + final NetworkCallback defaultCallback = getDefaultNetworkCallback(); + final NetworkCallback vpnCallback = getVpnNetworkCallback(); + callCallbacksForNetworkConnect(defaultCallback, mNetwork); + clearInvocations(mVpn, mCm, mNotificationManager); + + // New network and LinkProperties received + final NetworkCapabilities wifiNc = new NetworkCapabilities.Builder() + .addTransportType(TRANSPORT_WIFI) + .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED) + .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED) + .build(); + final LinkProperties wifiLp = new LinkProperties(); + wifiLp.setInterfaceName("wlan0"); + callCallbacksForNetworkConnect( + defaultCallback, mNetwork2, wifiNc, wifiLp, true /* blocked */); + + // Vpn is restarted. + verify(mVpn).stopVpnRunnerPrivileged(); + verify(mVpn).startLegacyVpnPrivileged(mProfile, mNetwork2, wifiLp); + verify(mNotificationManager, never()).cancel(any(), eq(SystemMessage.NOTE_VPN_STATUS)); + verify(mNotificationManager).notify(any(), eq(SystemMessage.NOTE_VPN_STATUS), + argThat(notification -> isExpectedNotification(notification, + R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected))); + + // Vpn is Connected + doReturn(true).when(mVpnNetworkInfo).isConnectedOrConnecting(); + doReturn(true).when(mVpnNetworkInfo).isConnected(); + vpnCallback.onAvailable(mVpnNetwork); + verify(mNotificationManager).notify(any(), eq(SystemMessage.NOTE_VPN_STATUS), + argThat(notification -> isExpectedNotification(notification, + R.string.vpn_lockdown_connected, R.drawable.vpn_connected))); + + } + + @Test + public void testSystemDefaultLost() { + initAndVerifyLockdownVpnTracker(); + final NetworkCallback defaultCallback = getDefaultNetworkCallback(); + // mNetwork connected + callCallbacksForNetworkConnect(defaultCallback, mNetwork); + clearInvocations(mVpn, mCm, mNotificationManager); + + defaultCallback.onLost(mNetwork); + + // Vpn is stopped + verify(mVpn).stopVpnRunnerPrivileged(); + verify(mNotificationManager).cancel(any(), eq(SystemMessage.NOTE_VPN_STATUS)); + } +} diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java index a54bc914f686..c684a7bbf884 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java @@ -60,6 +60,7 @@ public class UserManagerServiceUserPropertiesTest { .setShowInLauncher(21) .setStartWithParent(false) .setShowInSettings(45) + .setHideInSettingsInQuietMode(false) .setInheritDevicePolicy(67) .setUseParentsContacts(false) .setCrossProfileIntentFilterAccessControl(10) @@ -72,6 +73,7 @@ public class UserManagerServiceUserPropertiesTest { final UserProperties actualProps = new UserProperties(defaultProps); actualProps.setShowInLauncher(14); actualProps.setShowInSettings(32); + actualProps.setHideInSettingsInQuietMode(true); actualProps.setInheritDevicePolicy(51); actualProps.setUseParentsContacts(true); actualProps.setCrossProfileIntentFilterAccessControl(20); @@ -228,6 +230,8 @@ public class UserManagerServiceUserPropertiesTest { assertThat(expected.getShowInLauncher()).isEqualTo(actual.getShowInLauncher()); assertThat(expected.getStartWithParent()).isEqualTo(actual.getStartWithParent()); assertThat(expected.getShowInSettings()).isEqualTo(actual.getShowInSettings()); + assertThat(expected.getHideInSettingsInQuietMode()) + .isEqualTo(actual.getHideInSettingsInQuietMode()); assertThat(expected.getInheritDevicePolicy()).isEqualTo(actual.getInheritDevicePolicy()); assertThat(expected.getUseParentsContacts()).isEqualTo(actual.getUseParentsContacts()); assertThat(expected.getCrossProfileIntentFilterAccessControl()) diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java index e3579b4b9ef6..20270a817831 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java @@ -90,6 +90,7 @@ public class UserManagerServiceUserTypeTest { .setMediaSharedWithParent(true) .setCredentialShareableWithParent(false) .setShowInSettings(900) + .setHideInSettingsInQuietMode(true) .setInheritDevicePolicy(340) .setDeleteAppWithParent(true) .setAlwaysVisible(true); @@ -160,6 +161,7 @@ public class UserManagerServiceUserTypeTest { assertTrue(type.getDefaultUserPropertiesReference().isMediaSharedWithParent()); assertFalse(type.getDefaultUserPropertiesReference().isCredentialShareableWithParent()); assertEquals(900, type.getDefaultUserPropertiesReference().getShowInSettings()); + assertTrue(type.getDefaultUserPropertiesReference().getHideInSettingsInQuietMode()); assertEquals(340, type.getDefaultUserPropertiesReference() .getInheritDevicePolicy()); assertTrue(type.getDefaultUserPropertiesReference().getDeleteAppWithParent()); @@ -217,6 +219,7 @@ public class UserManagerServiceUserTypeTest { assertFalse(props.isCredentialShareableWithParent()); assertFalse(props.getDeleteAppWithParent()); assertFalse(props.getAlwaysVisible()); + assertFalse(props.getHideInSettingsInQuietMode()); assertFalse(type.hasBadge()); } @@ -304,6 +307,7 @@ public class UserManagerServiceUserTypeTest { .setMediaSharedWithParent(false) .setCredentialShareableWithParent(true) .setShowInSettings(20) + .setHideInSettingsInQuietMode(false) .setInheritDevicePolicy(21) .setDeleteAppWithParent(true) .setAlwaysVisible(false); @@ -344,6 +348,7 @@ public class UserManagerServiceUserTypeTest { assertTrue(aospType.getDefaultUserPropertiesReference() .isCredentialShareableWithParent()); assertEquals(20, aospType.getDefaultUserPropertiesReference().getShowInSettings()); + assertFalse(aospType.getDefaultUserPropertiesReference().getHideInSettingsInQuietMode()); assertEquals(21, aospType.getDefaultUserPropertiesReference() .getInheritDevicePolicy()); assertTrue(aospType.getDefaultUserPropertiesReference().getDeleteAppWithParent()); @@ -390,6 +395,7 @@ public class UserManagerServiceUserTypeTest { assertFalse(aospType.getDefaultUserPropertiesReference() .isCredentialShareableWithParent()); assertEquals(23, aospType.getDefaultUserPropertiesReference().getShowInSettings()); + assertTrue(aospType.getDefaultUserPropertiesReference().getHideInSettingsInQuietMode()); assertEquals(450, aospType.getDefaultUserPropertiesReference() .getInheritDevicePolicy()); assertFalse(aospType.getDefaultUserPropertiesReference().getDeleteAppWithParent()); diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp index d1f4961ab7e5..8891413dd964 100644 --- a/services/tests/uiservicestests/Android.bp +++ b/services/tests/uiservicestests/Android.bp @@ -43,6 +43,8 @@ android_test { // TODO: remove once Android migrates to JUnit 4.12, // which provides assertThrows "testng", + "flag-junit", + "notification_flags_lib", ], libs: [ diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java index 974238427587..42ad73a23f0e 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java @@ -32,6 +32,7 @@ import static junit.framework.Assert.assertTrue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; +import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyInt; @@ -147,6 +148,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { private static final int CUSTOM_LIGHT_ON = 10000; private static final int CUSTOM_LIGHT_OFF = 10000; private static final int MAX_VIBRATION_DELAY = 1000; + private static final float DEFAULT_VOLUME = 1.0f; @Before public void setUp() throws Exception { @@ -397,19 +399,22 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { // private void verifyNeverBeep() throws RemoteException { - verify(mRingtonePlayer, never()).playAsync(any(), any(), anyBoolean(), any()); + verify(mRingtonePlayer, never()).playAsync(any(), any(), anyBoolean(), any(), anyFloat()); } private void verifyBeepUnlooped() throws RemoteException { - verify(mRingtonePlayer, times(1)).playAsync(any(), any(), eq(false), any()); + verify(mRingtonePlayer, times(1)).playAsync(any(), any(), eq(false), any(), + eq(DEFAULT_VOLUME)); } private void verifyBeepLooped() throws RemoteException { - verify(mRingtonePlayer, times(1)).playAsync(any(), any(), eq(true), any()); + verify(mRingtonePlayer, times(1)).playAsync(any(), any(), eq(true), any(), + eq(DEFAULT_VOLUME)); } private void verifyBeep(int times) throws RemoteException { - verify(mRingtonePlayer, times(times)).playAsync(any(), any(), anyBoolean(), any()); + verify(mRingtonePlayer, times(times)).playAsync(any(), any(), anyBoolean(), any(), + eq(DEFAULT_VOLUME)); } private void verifyNeverStopAudio() throws RemoteException { @@ -905,7 +910,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { verifyDelayedVibrate( mService.getVibratorHelper().createFallbackVibration(/* insistent= */ false)); verify(mRingtonePlayer, never()).playAsync - (anyObject(), anyObject(), anyBoolean(), anyObject()); + (anyObject(), anyObject(), anyBoolean(), anyObject(), anyFloat()); assertTrue(r.isInterruptive()); assertNotEquals(-1, r.getLastAudiblyAlertedMs()); } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java index 81867df74abd..9bd938f2e0a7 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java @@ -57,6 +57,7 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.pm.PackageManager; +import android.content.pm.UserInfo; import android.graphics.Color; import android.graphics.drawable.Icon; import android.media.AudioAttributes; @@ -66,9 +67,11 @@ import android.os.Handler; import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; +import android.os.UserManager; import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.Vibrator; +import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; @@ -87,8 +90,11 @@ import com.android.server.UiServiceTestCase; import com.android.server.lights.LightsManager; import com.android.server.lights.LogicalLight; import com.android.server.pm.PackageManagerService; + +import java.util.List; import java.util.Objects; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -102,6 +108,8 @@ import org.mockito.verification.VerificationMode; @RunWith(AndroidJUnit4.class) @SuppressLint("GuardedBy") public class NotificationAttentionHelperTest extends UiServiceTestCase { + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Mock AudioManager mAudioManager; @Mock Vibrator mVibrator; @@ -115,6 +123,8 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { IAccessibilityManager mAccessibilityService; @Mock KeyguardManager mKeyguardManager; + @Mock + private UserManager mUserManager; NotificationRecordLoggerFake mNotificationRecordLogger = new NotificationRecordLoggerFake(); private InstanceIdSequence mNotificationInstanceIdSequence = new InstanceIdSequenceFake( 1 << 30); @@ -134,6 +144,8 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { private AccessibilityManager mAccessibilityManager; private static final NotificationAttentionHelper.Signals DEFAULT_SIGNALS = new NotificationAttentionHelper.Signals(false, 0); + private static final NotificationAttentionHelper.Signals WORK_PROFILE_SIGNALS = + new NotificationAttentionHelper.Signals(true, 0); private VibrateRepeatMatcher mVibrateOnceMatcher = new VibrateRepeatMatcher(-1); private VibrateRepeatMatcher mVibrateLoopMatcher = new VibrateRepeatMatcher(0); @@ -151,6 +163,7 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { private static final int CUSTOM_LIGHT_ON = 10000; private static final int CUSTOM_LIGHT_OFF = 10000; private static final int MAX_VIBRATION_DELAY = 1000; + private static final float DEFAULT_VOLUME = 1.0f; @Before public void setUp() throws Exception { @@ -178,6 +191,8 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { // TODO (b/291907312): remove feature flag mTestFlagResolver.setFlagOverride(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR, true); + // Disable feature flags by default. Tests should enable as needed. + mSetFlagsRule.disableFlags(Flags.FLAG_POLITE_NOTIFICATIONS, Flags.FLAG_EXPIRE_BITMAPS); mService = spy(new NotificationManagerService(getContext(), mNotificationRecordLogger, mNotificationInstanceIdSequence)); @@ -189,9 +204,9 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { private void initAttentionHelper(TestableFlagResolver flagResolver) { mAttentionHelper = new NotificationAttentionHelper(getContext(), mock(LightsManager.class), - mAccessibilityManager, getContext().getPackageManager(), mUsageStats, + mAccessibilityManager, getContext().getPackageManager(), mUserManager, mUsageStats, mService.mNotificationManagerPrivate, mock(ZenModeHelper.class), flagResolver); - mAttentionHelper.setVibratorHelper(new VibratorHelper(getContext())); + mAttentionHelper.setVibratorHelper(spy(new VibratorHelper(getContext()))); mAttentionHelper.setAudioManager(mAudioManager); mAttentionHelper.setSystemReady(true); mAttentionHelper.setLights(mLight); @@ -282,6 +297,11 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { true /* noisy */, true /* buzzy*/, false /* lights */); } + private NotificationRecord getBuzzyBeepyNotification(UserHandle userHandle) { + return getNotificationRecord(mId, false /* insistent */, false /* once */, + true /* noisy */, true /* buzzy*/, false /* lights */, userHandle); + } + private NotificationRecord getLightsNotification() { return getNotificationRecord(mId, false /* insistent */, false /* once */, false /* noisy */, false /* buzzy*/, true /* lights */); @@ -312,7 +332,13 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { private NotificationRecord getNotificationRecord(int id, boolean insistent, boolean once, boolean noisy, boolean buzzy, boolean lights) { return getNotificationRecord(id, insistent, once, noisy, buzzy, lights, buzzy, noisy, - lights, null, Notification.GROUP_ALERT_ALL, false); + lights, null, Notification.GROUP_ALERT_ALL, false, mUser); + } + + private NotificationRecord getNotificationRecord(int id, boolean insistent, boolean once, + boolean noisy, boolean buzzy, boolean lights, UserHandle userHandle) { + return getNotificationRecord(id, insistent, once, noisy, buzzy, lights, buzzy, noisy, + lights, null, Notification.GROUP_ALERT_ALL, false, userHandle); } private NotificationRecord getLeanbackNotificationRecord(int id, boolean insistent, @@ -320,25 +346,25 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { boolean noisy, boolean buzzy, boolean lights) { return getNotificationRecord(id, insistent, once, noisy, buzzy, lights, true, true, true, - null, Notification.GROUP_ALERT_ALL, true); + null, Notification.GROUP_ALERT_ALL, true, mUser); } private NotificationRecord getBeepyNotificationRecord(String groupKey, int groupAlertBehavior) { return getNotificationRecord(mId, false, false, true, false, false, true, true, true, - groupKey, groupAlertBehavior, false); + groupKey, groupAlertBehavior, false, mUser); } private NotificationRecord getLightsNotificationRecord(String groupKey, int groupAlertBehavior) { return getNotificationRecord(mId, false, false, false, false, true /*lights*/, true, - true, true, groupKey, groupAlertBehavior, false); + true, true, groupKey, groupAlertBehavior, false, mUser); } private NotificationRecord getNotificationRecord(int id, boolean insistent, boolean once, boolean noisy, boolean buzzy, boolean lights, boolean defaultVibration, boolean defaultSound, boolean defaultLights, String groupKey, int groupAlertBehavior, - boolean isLeanback) { + boolean isLeanback, UserHandle userHandle) { final Builder builder = new Builder(getContext()) .setContentTitle("foo") @@ -399,7 +425,7 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { .thenReturn(isLeanback); StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, id, mTag, mUid, - mPid, n, mUser, null, System.currentTimeMillis()); + mPid, n, userHandle, null, System.currentTimeMillis()); NotificationRecord r = new NotificationRecord(context, sbn, mChannel); mService.addNotification(r); return r; @@ -410,19 +436,26 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { // private void verifyNeverBeep() throws RemoteException { - verify(mRingtonePlayer, never()).playAsync(any(), any(), anyBoolean(), any()); + verify(mRingtonePlayer, never()).playAsync(any(), any(), anyBoolean(), any(), anyFloat()); } private void verifyBeepUnlooped() throws RemoteException { - verify(mRingtonePlayer, times(1)).playAsync(any(), any(), eq(false), any()); + verify(mRingtonePlayer, times(1)).playAsync(any(), any(), eq(false), any(), + eq(DEFAULT_VOLUME)); } private void verifyBeepLooped() throws RemoteException { - verify(mRingtonePlayer, times(1)).playAsync(any(), any(), eq(true), any()); + verify(mRingtonePlayer, times(1)).playAsync(any(), any(), eq(true), any(), + eq(DEFAULT_VOLUME)); } private void verifyBeep(int times) throws RemoteException { - verify(mRingtonePlayer, times(times)).playAsync(any(), any(), anyBoolean(), any()); + verify(mRingtonePlayer, times(times)).playAsync(any(), any(), anyBoolean(), any(), + eq(DEFAULT_VOLUME)); + } + + private void verifyBeepVolume(float volume) throws RemoteException { + verify(mRingtonePlayer, times(1)).playAsync(any(), any(), anyBoolean(), any(), eq(volume)); } private void verifyNeverStopAudio() throws RemoteException { @@ -921,7 +954,7 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { mAttentionHelper.getVibratorHelper().createFallbackVibration( /* insistent= */ false)); verify(mRingtonePlayer, never()).playAsync(anyObject(), anyObject(), anyBoolean(), - anyObject()); + anyObject(), anyFloat()); assertTrue(r.isInterruptive()); assertNotEquals(-1, r.getLastAudiblyAlertedMs()); } @@ -1948,6 +1981,259 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { assertEquals(-1, r.getLastAudiblyAlertedMs()); } + // TODO b/270456865: Only one of the two strategies will be released. + // The other one need to be removed + @Test + public void testBeepVolume_politeNotif() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); + TestableFlagResolver flagResolver = new TestableFlagResolver(); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule1"); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0); + initAttentionHelper(flagResolver); + + NotificationRecord r = getBeepyNotification(); + + // set up internal state + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + Mockito.reset(mRingtonePlayer); + + // update should beep at 50% volume + r.isUpdate = true; + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verifyBeepVolume(0.5f); + + // 2nd update should beep at 0% volume + Mockito.reset(mRingtonePlayer); + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verifyBeepVolume(0.0f); + + verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); + } + + // TODO b/270456865: Only one of the two strategies will be released. + // The other one need to be removed + @Test + public void testBeepVolume_politeNotif_Strategy2() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); + TestableFlagResolver flagResolver = new TestableFlagResolver(); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule2"); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0); + initAttentionHelper(flagResolver); + + NotificationRecord r = getBeepyNotification(); + + // set up internal state + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + Mockito.reset(mRingtonePlayer); + + // update should beep at 0% volume + r.isUpdate = true; + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verifyBeepVolume(0.0f); + + // 2nd update should beep at 50% volume + Mockito.reset(mRingtonePlayer); + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verifyBeepVolume(0.5f); + + verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test + public void testVibrationIntensity_politeNotif() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); + TestableFlagResolver flagResolver = new TestableFlagResolver(); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule1"); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0); + initAttentionHelper(flagResolver); + + NotificationRecord r = getBuzzyBeepyNotification(); + + // set up internal state + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + + VibratorHelper vibratorHelper = mAttentionHelper.getVibratorHelper(); + Mockito.reset(vibratorHelper); + + // update should buzz at 50% intensity + r.isUpdate = true; + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + + verify(vibratorHelper, times(1)).scale(any(), eq(0.5f)); + Mockito.reset(vibratorHelper); + + // 2nd update should buzz at 0% intensity + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verify(vibratorHelper, times(1)).scale(any(), eq(0.0f)); + } + + @Test + public void testVibrationIntensity_politeNotif_Strategy2() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); + TestableFlagResolver flagResolver = new TestableFlagResolver(); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule2"); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0); + initAttentionHelper(flagResolver); + + NotificationRecord r = getBuzzyBeepyNotification(); + + // set up internal state + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + + VibratorHelper vibratorHelper = mAttentionHelper.getVibratorHelper(); + Mockito.reset(vibratorHelper); + + // update should buzz at 0% intensity + r.isUpdate = true; + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + + verify(vibratorHelper, times(1)).scale(any(), eq(0.0f)); + Mockito.reset(vibratorHelper); + + // 2nd update should buzz at 50% intensity + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verify(vibratorHelper, times(1)).scale(any(), eq(0.5f)); + } + + @Test + public void testBuzzOnlyOnScreenUnlock_politeNotif() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); + TestableFlagResolver flagResolver = new TestableFlagResolver(); + + // When NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED setting is enabled + Settings.System.putInt(getContext().getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED, 1); + + initAttentionHelper(flagResolver); + // And screen is unlocked + mAttentionHelper.setUserPresent(true); + + NotificationRecord r = getBuzzyBeepyNotification(); + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + + // The notification attention should only buzz + verifyNeverBeep(); + verifyVibrate(); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test + public void testBeepVolume_politeNotif_disabled() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); + TestableFlagResolver flagResolver = new TestableFlagResolver(); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0); + + // When NOTIFICATION_COOLDOWN_ENABLED setting is disabled + Settings.System.putInt(getContext().getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_ENABLED, 0); + + initAttentionHelper(flagResolver); + + NotificationRecord r = getBeepyNotification(); + + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + Mockito.reset(mRingtonePlayer); + + // update should beep at 100% volume + r.isUpdate = true; + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verifyBeepVolume(1.0f); + + // 2nd update should beep at 100% volume + Mockito.reset(mRingtonePlayer); + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verifyBeepVolume(1.0f); + + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test + public void testBeepVolume_politeNotif_workProfile() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); + TestableFlagResolver flagResolver = new TestableFlagResolver(); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule1"); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0); + + final int workProfileUserId = mUser.getIdentifier() + 1; + + // Enable notifications cooldown for work profile + Settings.System.putIntForUser(getContext().getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_ENABLED, 1, workProfileUserId); + + when(mUserManager.getProfiles(mUser.getIdentifier())).thenReturn( + List.of(new UserInfo(workProfileUserId, "work_profile", null, + UserInfo.FLAG_PROFILE | UserInfo.FLAG_MANAGED_PROFILE, + UserInfo.getDefaultUserType(UserInfo.FLAG_MANAGED_PROFILE)))); + + initAttentionHelper(flagResolver); + + final NotificationRecord r = getBuzzyBeepyNotification(UserHandle.of(workProfileUserId)); + + // set up internal state + mAttentionHelper.buzzBeepBlinkLocked(r, WORK_PROFILE_SIGNALS); + Mockito.reset(mRingtonePlayer); + + // update should beep at 50% volume + r.isUpdate = true; + mAttentionHelper.buzzBeepBlinkLocked(r, WORK_PROFILE_SIGNALS); + verifyBeepVolume(0.5f); + + // 2nd update should beep at 0% volume + Mockito.reset(mRingtonePlayer); + mAttentionHelper.buzzBeepBlinkLocked(r, WORK_PROFILE_SIGNALS); + verifyBeepVolume(0.0f); + + verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt()); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test + public void testBeepVolume_politeNotif_workProfile_disabled() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); + TestableFlagResolver flagResolver = new TestableFlagResolver(); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule1"); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0); + + final int workProfileUserId = mUser.getIdentifier() + 1; + + // Disable notifications cooldown for work profile + Settings.System.putIntForUser(getContext().getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_ENABLED, 0, workProfileUserId); + + when(mUserManager.getProfiles(mUser.getIdentifier())).thenReturn( + List.of(new UserInfo(workProfileUserId, "work_profile", null, + UserInfo.FLAG_PROFILE | UserInfo.FLAG_MANAGED_PROFILE, + UserInfo.getDefaultUserType(UserInfo.FLAG_MANAGED_PROFILE)))); + + initAttentionHelper(flagResolver); + + final NotificationRecord r = getBuzzyBeepyNotification(UserHandle.of(workProfileUserId)); + + mAttentionHelper.buzzBeepBlinkLocked(r, WORK_PROFILE_SIGNALS); + Mockito.reset(mRingtonePlayer); + + // update should beep at 100% volume + r.isUpdate = true; + mAttentionHelper.buzzBeepBlinkLocked(r, WORK_PROFILE_SIGNALS); + verifyBeepVolume(1.0f); + + // 2nd update should beep at 100% volume + Mockito.reset(mRingtonePlayer); + mAttentionHelper.buzzBeepBlinkLocked(r, WORK_PROFILE_SIGNALS); + verifyBeepVolume(1.0f); + + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); + } + static class VibrateRepeatMatcher implements ArgumentMatcher<VibrationEffect> { private final int mRepeatIndex; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 91129a14ecab..3d4b4a62e5ac 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -687,7 +687,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mPackageIntentReceiver = broadcastReceivers.get(i); } if (filter.hasAction(Intent.ACTION_USER_SWITCHED)) { - mUserSwitchIntentReceiver = broadcastReceivers.get(i); + // There may be multiple receivers, get the NMS one + if (broadcastReceivers.get(i).toString().contains( + NotificationManagerService.class.getName())) { + mUserSwitchIntentReceiver = broadcastReceivers.get(i); + } } } assertNotNull("package intent receiver should exist", mPackageIntentReceiver); diff --git a/services/tests/vibrator/Android.bp b/services/tests/vibrator/Android.bp index ca5cfa5b60f5..95441060f0e5 100644 --- a/services/tests/vibrator/Android.bp +++ b/services/tests/vibrator/Android.bp @@ -27,6 +27,7 @@ android_test { "androidx.test.runner", "androidx.test.rules", "androidx.test.ext.junit", + "flag-junit", "frameworks-base-testutils", "frameworks-services-vibrator-testutils", "junit", diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java index bc826a3cf4a6..04158c4d4f93 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java @@ -31,6 +31,8 @@ import static org.mockito.Mockito.when; import android.content.res.Resources; import android.os.VibrationEffect; import android.os.VibratorInfo; +import android.os.vibrator.Flags; +import android.platform.test.flag.junit.SetFlagsRule; import android.util.AtomicFile; import android.util.SparseArray; @@ -49,6 +51,8 @@ import java.io.File; import java.io.FileOutputStream; public class HapticFeedbackCustomizationTest { + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Rule public MockitoRule rule = MockitoJUnit.rule(); // Pairs of valid vibration XML along with their equivalent VibrationEffect. @@ -77,6 +81,7 @@ public class HapticFeedbackCustomizationTest { @Before public void setUp() { when(mVibratorInfoMock.areVibrationFeaturesSupported(any())).thenReturn(true); + mSetFlagsRule.enableFlags(Flags.FLAG_HAPTIC_FEEDBACK_VIBRATION_OEM_CUSTOMIZATION_ENABLED); } @Test @@ -87,6 +92,21 @@ public class HapticFeedbackCustomizationTest { } @Test + public void testParseCustomizations_featureFlagDisabled_returnsNull() throws Exception { + mSetFlagsRule.disableFlags(Flags.FLAG_HAPTIC_FEEDBACK_VIBRATION_OEM_CUSTOMIZATION_ENABLED); + // Valid customization XML. + String xml = "<haptic-feedback-constants>" + + "<constant id=\"10\">" + + COMPOSITION_VIBRATION_XML + + "</constant>" + + "</haptic-feedback-constants>"; + setupCustomizationFile(xml); + + assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock)) + .isNull(); + } + + @Test public void testParseCustomizations_oneVibrationCustomization_success() throws Exception { String xml = "<haptic-feedback-constants>" + "<constant id=\"10\">" diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java index cd0389dce416..ba31944f412a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java @@ -46,10 +46,13 @@ import static com.android.server.wm.testing.Assert.assertThrows; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -583,6 +586,7 @@ public class DisplayAreaTest extends WindowTestsBase { final IDisplayAreaOrganizer mockDisplayAreaOrganizer = mock(IDisplayAreaOrganizer.class); doReturn(mock(IBinder.class)).when(mockDisplayAreaOrganizer).asBinder(); displayArea.mOrganizer = mockDisplayAreaOrganizer; + displayArea.mDisplayAreaAppearedSent = true; spyOn(mWm.mAtmService.mWindowOrganizerController.mDisplayAreaOrganizerController); mDisplayContent.addChild(displayArea, 0); @@ -687,6 +691,56 @@ public class DisplayAreaTest extends WindowTestsBase { assertEquals(parent.getChildAt(0), child); } + @Test + public void testSetOrganizer() { + final TaskDisplayArea displayArea = createTaskDisplayArea( + mDisplayContent, mWm, "NewArea", FEATURE_VENDOR_FIRST); + + assertNull(displayArea.mOrganizer); + assertFalse(displayArea.mDisplayAreaAppearedSent); + + final IDisplayAreaOrganizer organizer = mock(IDisplayAreaOrganizer.class); + final DisplayAreaOrganizerController controller = + mWm.mAtmService.mWindowOrganizerController.mDisplayAreaOrganizerController; + spyOn(controller); + doNothing().when(controller).onDisplayAreaVanished(any(), any()); + + displayArea.setOrganizer(organizer); + + assertEquals(organizer, displayArea.mOrganizer); + assertTrue(displayArea.mDisplayAreaAppearedSent); + verify(controller).onDisplayAreaAppeared(organizer, displayArea); + + // No duplicated appeared sent. + clearInvocations(controller); + displayArea.sendDisplayAreaAppeared(); + + verify(controller, never()).onDisplayAreaAppeared(any(), any()); + + // Sent info changed after appeared. + displayArea.sendDisplayAreaInfoChanged(); + + verify(controller).onDisplayAreaInfoChanged(organizer, displayArea); + + // Sent info vanished after appeared. + displayArea.setOrganizer(null); + + verify(controller).onDisplayAreaVanished(organizer, displayArea); + assertNull(displayArea.mOrganizer); + assertFalse(displayArea.mDisplayAreaAppearedSent); + + // No callback until appeared sent. + clearInvocations(controller); + + displayArea.sendDisplayAreaAppeared(); + displayArea.sendDisplayAreaInfoChanged(); + displayArea.sendDisplayAreaVanished(organizer); + + verify(controller, never()).onDisplayAreaAppeared(any(), any()); + verify(controller, never()).onDisplayAreaInfoChanged(any(), any()); + verify(controller, never()).onDisplayAreaVanished(any(), any()); + } + private static class TestDisplayArea<T extends WindowContainer> extends DisplayArea<T> { private TestDisplayArea(WindowManagerService wms, Rect bounds, String name) { super(wms, ANY, name); diff --git a/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java b/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java index 6c48a6961bc2..9f43a1785266 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/SafeActivityOptionsTest.java @@ -94,6 +94,16 @@ public class SafeActivityOptionsTest { } @Test + public void test_selectiveCloneLunchRemoteTransition() { + final RemoteTransition transition = mock(RemoteTransition.class); + final SafeActivityOptions clone = new SafeActivityOptions( + ActivityOptions.makeRemoteTransition(transition)) + .selectiveCloneLaunchOptions(); + + assertSame(clone.getOriginalOptions().getRemoteTransition(), transition); + } + + @Test public void test_getOptions() { // Mock everything necessary MockitoSession mockingSession = mockitoSession() diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index 474720f68731..c8546c613995 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -48,6 +48,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.server.wm.WindowContainer.POSITION_TOP; +import static com.android.window.flags.Flags.explicitRefreshRateHints; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -60,6 +61,7 @@ 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.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; @@ -84,6 +86,7 @@ import android.window.ITaskFragmentOrganizer; import android.window.ITaskOrganizer; import android.window.ITransitionPlayer; import android.window.RemoteTransition; +import android.window.SystemPerformanceHinter; import android.window.TaskFragmentOrganizer; import android.window.TransitionInfo; @@ -2433,6 +2436,45 @@ public class TransitionTests extends WindowTestsBase { assertTrue((player.mLastReady.getFlags() & FLAG_SYNC) == 0); } + @Test + public void testTransitionsTriggerPerformanceHints() { + assumeTrue(explicitRefreshRateHints()); + SystemPerformanceHinter systemPerformanceHinter = mock(SystemPerformanceHinter.class); + final TransitionController controller = new TestTransitionController(mAtm); + final TestTransitionPlayer player = registerTestTransitionPlayer(); + + mSyncEngine = createTestBLASTSyncEngine(); + controller.setSyncEngine(mSyncEngine); + controller.setSystemPerformanceHinter(systemPerformanceHinter); + SystemPerformanceHinter.HighPerfSession session = mock( + SystemPerformanceHinter.HighPerfSession.class); + doReturn(session).when(systemPerformanceHinter).startSession(anyInt(), anyInt(), + anyString()); + + final Transition transitA = createTestTransition(TRANSIT_OPEN, controller); + final Task task = createTask(mDisplayContent, + WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD); + final ActivityRecord act = createActivityRecord(task); + act.setVisibleRequested(true); + act.setVisible(true); + + controller.startCollectOrQueue(transitA, (deferred) -> { + }); + transitA.collect(act); + + verify(systemPerformanceHinter).startSession( + eq(SystemPerformanceHinter.HINT_SF), anyInt(), eq("Transition collected")); + + transitA.start(); + transitA.setAllReady(); + + // Aborting here doesn't abort the transition, it aborts the sync allowing the transition to + // finish successfully. + mSyncEngine.abort(transitA.getSyncId()); + controller.finishTransition(transitA); + verify(session).close(); + } + private static void makeTaskOrganized(Task... tasks) { final ITaskOrganizer organizer = mock(ITaskOrganizer.class); for (Task t : tasks) { diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java index a7675d694a24..def52a517913 100644 --- a/telecomm/java/android/telecom/Call.java +++ b/telecomm/java/android/telecom/Call.java @@ -942,6 +942,7 @@ public final class Call { * @return the Telecom identifier associated with this {@link Call} . This is not a stable * identifier and is not guaranteed to be unique across device reboots. */ + @FlaggedApi(Flags.FLAG_CALL_DETAILS_ID_CHANGES) public @NonNull String getId() { return mTelecomCallId; } /** {@hide} */ diff --git a/telecomm/java/android/telecom/StreamingCall.java b/telecomm/java/android/telecom/StreamingCall.java index 29f436d9f459..ad1b6f9e7665 100644 --- a/telecomm/java/android/telecom/StreamingCall.java +++ b/telecomm/java/android/telecom/StreamingCall.java @@ -16,6 +16,7 @@ package android.telecom; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SystemApi; @@ -25,6 +26,8 @@ import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import com.android.server.telecom.flags.Flags; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -57,6 +60,7 @@ public final class StreamingCall implements Parcelable { /** * The ID associated with this call. This is the same value as {@link CallControl#getCallId()}. */ + @FlaggedApi(Flags.FLAG_CALL_DETAILS_ID_CHANGES) public static final String EXTRA_CALL_ID = "android.telecom.extra.CALL_ID"; /** diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 63e91ad414de..7a0bf9038f7c 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -10523,6 +10523,8 @@ public class CarrierConfigManager { auto_data_switch_rat_signal_score_string_bundle.putIntArray( "LTE", new int[]{3731, 5965, 8618, 11179, 13384}); auto_data_switch_rat_signal_score_string_bundle.putIntArray( + "LTE_CA", new int[]{3831, 6065, 8718, 11379, 13484}); + auto_data_switch_rat_signal_score_string_bundle.putIntArray( "NR_SA", new int[]{5288, 6795, 6955, 7562, 9713}); auto_data_switch_rat_signal_score_string_bundle.putIntArray( "NR_NSA", new int[]{5463, 6827, 8029, 9007, 9428}); diff --git a/telephony/java/android/telephony/DisconnectCause.java b/telephony/java/android/telephony/DisconnectCause.java index f9844bcc677d..a8c077d24ed9 100644 --- a/telephony/java/android/telephony/DisconnectCause.java +++ b/telephony/java/android/telephony/DisconnectCause.java @@ -16,9 +16,12 @@ package android.telephony; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; +import com.android.internal.telephony.flags.Flags; + /** * Describes the cause of a disconnected call. Those disconnect causes can be converted into a more * generic {@link android.telecom.DisconnectCause} object. @@ -363,6 +366,7 @@ public final class DisconnectCause { /** * Indicates that the call was unable to be made because the satellite modem is enabled. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_ENABLED = 82; //********************************************************************************************* diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java index cb4a6e7479cf..7ccc27e2f94e 100644 --- a/telephony/java/android/telephony/NetworkRegistrationInfo.java +++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java @@ -16,6 +16,7 @@ package android.telephony; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -30,6 +31,8 @@ import android.telephony.AccessNetworkConstants.TransportType; import android.telephony.Annotation.NetworkType; import android.text.TextUtils; +import com.android.internal.telephony.flags.Flags; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -206,6 +209,7 @@ public final class NetworkRegistrationInfo implements Parcelable { /** * MMS service */ + @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) public static final int SERVICE_TYPE_MMS = 6; /** @hide */ @@ -702,6 +706,7 @@ public final class NetworkRegistrationInfo implements Parcelable { * * @return {@code true} if network is a non-terrestrial network else {@code false}. */ + @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) public boolean isNonTerrestrialNetwork() { return mIsNonTerrestrialNetwork; } @@ -1186,6 +1191,7 @@ public final class NetworkRegistrationInfo implements Parcelable { * else {@code false}. * @return The builder. */ + @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) public @NonNull Builder setIsNonTerrestrialNetwork(boolean isNonTerrestrialNetwork) { mIsNonTerrestrialNetwork = isNonTerrestrialNetwork; return this; diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java index 5b8848c72041..85a85c6dfadb 100644 --- a/telephony/java/android/telephony/ServiceState.java +++ b/telephony/java/android/telephony/ServiceState.java @@ -16,6 +16,7 @@ package android.telephony; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -35,6 +36,7 @@ import android.telephony.NetworkRegistrationInfo.Domain; import android.telephony.NetworkRegistrationInfo.NRState; import android.text.TextUtils; +import com.android.internal.telephony.flags.Flags; import com.android.telephony.Rlog; import java.lang.annotation.Retention; @@ -2256,6 +2258,7 @@ public class ServiceState implements Parcelable { * * @return {@code true} if device is connected to a non-terrestrial network else {@code false}. */ + @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) public boolean isUsingNonTerrestrialNetwork() { synchronized (mNetworkRegistrationInfos) { for (NetworkRegistrationInfo nri : mNetworkRegistrationInfos) { diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 548fa97db599..90fa69ff8a15 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -25,6 +25,7 @@ import static com.android.internal.util.Preconditions.checkNotNull; import android.Manifest; import android.annotation.BytesLong; import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.LongDef; import android.annotation.NonNull; @@ -128,6 +129,7 @@ import com.android.internal.telephony.IccLogicalChannelRequest; import com.android.internal.telephony.OperatorInfo; import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.RILConstants; +import com.android.internal.telephony.flags.Flags; import com.android.telephony.Rlog; import java.io.IOException; @@ -1195,6 +1197,7 @@ public class TelephonyManager { * The dialer app receives this event via * {@link Call.Callback#onConnectionEvent(Call, String, Bundle)}. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final String EVENT_DISPLAY_SOS_MESSAGE = "android.telephony.event.DISPLAY_SOS_MESSAGE"; @@ -15036,6 +15039,7 @@ public class TelephonyManager { * @hide */ @TestApi + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int HAL_SERVICE_SATELLITE = 8; /** @hide */ diff --git a/telephony/java/android/telephony/satellite/AntennaDirection.java b/telephony/java/android/telephony/satellite/AntennaDirection.java index 22412e6efadf..c690f9852bd0 100644 --- a/telephony/java/android/telephony/satellite/AntennaDirection.java +++ b/telephony/java/android/telephony/satellite/AntennaDirection.java @@ -16,11 +16,14 @@ package android.telephony.satellite; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.telephony.flags.Flags; + import java.util.Objects; /** @@ -38,6 +41,7 @@ import java.util.Objects; * @hide */ @SystemApi +@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public final class AntennaDirection implements Parcelable { /** Antenna x axis direction. */ private float mX; @@ -62,11 +66,13 @@ public final class AntennaDirection implements Parcelable { } @Override + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public int describeContents() { return 0; } @Override + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void writeToParcel(@NonNull Parcel out, int flags) { out.writeFloat(mX); out.writeFloat(mY); @@ -74,6 +80,7 @@ public final class AntennaDirection implements Parcelable { } @NonNull + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final Creator<AntennaDirection> CREATOR = new Creator<>() { @Override @@ -118,14 +125,17 @@ public final class AntennaDirection implements Parcelable { return Objects.hash(mX, mY, mZ); } + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public float getX() { return mX; } + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public float getY() { return mY; } + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public float getZ() { return mZ; } diff --git a/telephony/java/android/telephony/satellite/AntennaPosition.java b/telephony/java/android/telephony/satellite/AntennaPosition.java index 588be6cae773..8842886d3a1c 100644 --- a/telephony/java/android/telephony/satellite/AntennaPosition.java +++ b/telephony/java/android/telephony/satellite/AntennaPosition.java @@ -16,11 +16,14 @@ package android.telephony.satellite; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.telephony.flags.Flags; + import java.util.Objects; /** @@ -29,6 +32,7 @@ import java.util.Objects; * @hide */ @SystemApi +@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public final class AntennaPosition implements Parcelable { /** Antenna direction used for satellite communication. */ @NonNull AntennaDirection mAntennaDirection; @@ -49,17 +53,20 @@ public final class AntennaPosition implements Parcelable { } @Override + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public int describeContents() { return 0; } @Override + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void writeToParcel(@NonNull Parcel out, int flags) { out.writeParcelable(mAntennaDirection, flags); out.writeInt(mSuggestedHoldPosition); } @NonNull + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final Creator<AntennaPosition> CREATOR = new Creator<>() { @Override @@ -100,11 +107,13 @@ public final class AntennaPosition implements Parcelable { } @NonNull + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public AntennaDirection getAntennaDirection() { return mAntennaDirection; } @SatelliteManager.DeviceHoldPosition + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public int getSuggestedHoldPosition() { return mSuggestedHoldPosition; } diff --git a/telephony/java/android/telephony/satellite/PointingInfo.java b/telephony/java/android/telephony/satellite/PointingInfo.java index dc4d38b02f57..022a856e48dd 100644 --- a/telephony/java/android/telephony/satellite/PointingInfo.java +++ b/telephony/java/android/telephony/satellite/PointingInfo.java @@ -16,11 +16,14 @@ package android.telephony.satellite; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.telephony.flags.Flags; + import java.util.Objects; /** @@ -30,6 +33,7 @@ import java.util.Objects; * @hide */ @SystemApi +@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public final class PointingInfo implements Parcelable { /** Satellite azimuth in degrees */ private float mSatelliteAzimuthDegrees; @@ -50,16 +54,19 @@ public final class PointingInfo implements Parcelable { } @Override + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public int describeContents() { return 0; } @Override + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void writeToParcel(@NonNull Parcel out, int flags) { out.writeFloat(mSatelliteAzimuthDegrees); out.writeFloat(mSatelliteElevationDegrees); } + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final @android.annotation.NonNull Creator<PointingInfo> CREATOR = new Creator<PointingInfo>() { @Override @@ -101,10 +108,12 @@ public final class PointingInfo implements Parcelable { return sb.toString(); } + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public float getSatelliteAzimuthDegrees() { return mSatelliteAzimuthDegrees; } + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public float getSatelliteElevationDegrees() { return mSatelliteElevationDegrees; } diff --git a/telephony/java/android/telephony/satellite/SatelliteCapabilities.java b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java index bc45be110ace..0d8f10178971 100644 --- a/telephony/java/android/telephony/satellite/SatelliteCapabilities.java +++ b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java @@ -16,12 +16,15 @@ package android.telephony.satellite; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.telephony.flags.Flags; + import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -34,6 +37,7 @@ import java.util.Set; * @hide */ @SystemApi +@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public final class SatelliteCapabilities implements Parcelable { /** * List of technologies supported by the satellite modem. @@ -76,11 +80,13 @@ public final class SatelliteCapabilities implements Parcelable { } @Override + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public int describeContents() { return 0; } @Override + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void writeToParcel(@NonNull Parcel out, int flags) { if (mSupportedRadioTechnologies != null && !mSupportedRadioTechnologies.isEmpty()) { out.writeInt(mSupportedRadioTechnologies.size()); @@ -106,6 +112,7 @@ public final class SatelliteCapabilities implements Parcelable { } } + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) @NonNull public static final Creator<SatelliteCapabilities> CREATOR = new Creator<>() { @Override public SatelliteCapabilities createFromParcel(Parcel in) { @@ -165,6 +172,7 @@ public final class SatelliteCapabilities implements Parcelable { /** * @return The list of technologies supported by the satellite modem. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) @NonNull @SatelliteManager.NTRadioTechnology public Set<Integer> getSupportedRadioTechnologies() { return mSupportedRadioTechnologies; @@ -176,6 +184,7 @@ public final class SatelliteCapabilities implements Parcelable { * @return {@code true} if UE needs to point to a satellite to send and receive data and * {@code false} otherwise. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public boolean isPointingRequired() { return mIsPointingRequired; } @@ -185,6 +194,7 @@ public final class SatelliteCapabilities implements Parcelable { * * @return The maximum number of bytes per datagram that can be sent over satellite. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public int getMaxBytesPerOutgoingDatagram() { return mMaxBytesPerOutgoingDatagram; } @@ -195,6 +205,7 @@ public final class SatelliteCapabilities implements Parcelable { * @return Map key: {@link SatelliteManager.DeviceHoldPosition} value: AntennaPosition */ @NonNull + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public Map<Integer, AntennaPosition> getAntennaPositionMap() { return mAntennaPositionMap; } diff --git a/telephony/java/android/telephony/satellite/SatelliteDatagram.java b/telephony/java/android/telephony/satellite/SatelliteDatagram.java index 9037f0c4078d..4d67f80241f9 100644 --- a/telephony/java/android/telephony/satellite/SatelliteDatagram.java +++ b/telephony/java/android/telephony/satellite/SatelliteDatagram.java @@ -16,17 +16,21 @@ package android.telephony.satellite; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.telephony.flags.Flags; + /** * SatelliteDatagram is used to store data that is to be sent or received over satellite. * Data is stored in byte array format. * @hide */ @SystemApi +@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public final class SatelliteDatagram implements Parcelable { /** * Datagram to be sent or received over satellite. @@ -45,15 +49,18 @@ public final class SatelliteDatagram implements Parcelable { } @Override + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public int describeContents() { return 0; } @Override + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void writeToParcel(@NonNull Parcel out, int flags) { out.writeByteArray(mData); } + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) @NonNull public static final Creator<SatelliteDatagram> CREATOR = new Creator<>() { @Override @@ -73,6 +80,7 @@ public final class SatelliteDatagram implements Parcelable { * satellite provider. Client application should be aware of how to encode the datagram based * upon the satellite provider. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) @NonNull public byte[] getSatelliteDatagram() { return mData; } diff --git a/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java b/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java index cb2920f9e6ee..b5763c38e69c 100644 --- a/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java +++ b/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java @@ -16,9 +16,12 @@ package android.telephony.satellite; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.SystemApi; +import com.android.internal.telephony.flags.Flags; + import java.util.function.Consumer; /** @@ -27,6 +30,7 @@ import java.util.function.Consumer; * @hide */ @SystemApi +@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public interface SatelliteDatagramCallback { /** * Called when there is an incoming datagram to be received. @@ -38,6 +42,7 @@ public interface SatelliteDatagramCallback { * that they received the datagram. If the callback is not received within * five minutes, Telephony will resend the datagram. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) void onSatelliteDatagramReceived(long datagramId, @NonNull SatelliteDatagram datagram, int pendingCount, @NonNull Consumer<Void> callback); } diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java index 8dc2de8178c4..7322aeb8bfca 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -63,6 +63,7 @@ import java.util.stream.Collectors; */ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SATELLITE) @SystemApi +@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public final class SatelliteManager { private static final String TAG = "SatelliteManager"; @@ -108,6 +109,7 @@ public final class SatelliteManager { /** * Exception from the satellite service containing the {@link SatelliteResult} error code. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static class SatelliteException extends Exception { @SatelliteResult private final int mErrorCode; @@ -116,6 +118,7 @@ public final class SatelliteManager { * * @param errorCode The {@link SatelliteResult}. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public SatelliteException(@SatelliteResult int errorCode) { mErrorCode = errorCode; } @@ -125,6 +128,7 @@ public final class SatelliteManager { * * @return The {@link SatelliteResult}. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) @SatelliteResult public int getErrorCode() { return mErrorCode; } @@ -190,104 +194,127 @@ public final class SatelliteManager { /** * The request was successfully processed. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_SUCCESS = 0; /** * A generic error which should be used only when other specific errors cannot be used. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_ERROR = 1; /** * Error received from the satellite server. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_SERVER_ERROR = 2; /** * Error received from the vendor service. This generic error code should be used * only when the error cannot be mapped to other specific service error codes. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_SERVICE_ERROR = 3; /** * Error received from satellite modem. This generic error code should be used only when * the error cannot be mapped to other specific modem error codes. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_MODEM_ERROR = 4; /** * Error received from the satellite network. This generic error code should be used only when * the error cannot be mapped to other specific network error codes. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_NETWORK_ERROR = 5; /** * Telephony is not in a valid state to receive requests from clients. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_INVALID_TELEPHONY_STATE = 6; /** * Satellite modem is not in a valid state to receive requests from clients. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_INVALID_MODEM_STATE = 7; /** * Either vendor service, or modem, or Telephony framework has received a request with * invalid arguments from its clients. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_INVALID_ARGUMENTS = 8; /** * Telephony framework failed to send a request or receive a response from the vendor service * or satellite modem due to internal error. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_REQUEST_FAILED = 9; /** * Radio did not start or is resetting. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_RADIO_NOT_AVAILABLE = 10; /** * The request is not supported by either the satellite modem or the network. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_REQUEST_NOT_SUPPORTED = 11; /** * Satellite modem or network has no resources available to handle requests from clients. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_NO_RESOURCES = 12; /** * Satellite service is not provisioned yet. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_SERVICE_NOT_PROVISIONED = 13; /** * Satellite service provision is already in progress. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_SERVICE_PROVISION_IN_PROGRESS = 14; /** * The ongoing request was aborted by either the satellite modem or the network. * This error is also returned when framework decides to abort current send request as one * of the previous send request failed. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_REQUEST_ABORTED = 15; /** * The device/subscriber is barred from accessing the satellite service. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_ACCESS_BARRED = 16; /** * Satellite modem timeout to receive ACK or response from the satellite network after * sending a request to the network. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_NETWORK_TIMEOUT = 17; /** * Satellite network is not reachable from the modem. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_NOT_REACHABLE = 18; /** * The device/subscriber is not authorized to register with the satellite service provider. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_NOT_AUTHORIZED = 19; /** * The device does not support satellite. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_NOT_SUPPORTED = 20; /** * The current request is already in-progress. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_REQUEST_IN_PROGRESS = 21; /** * Satellite modem is currently busy due to which current request cannot be processed. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_MODEM_BUSY = 22; /** @hide */ @@ -323,22 +350,27 @@ public final class SatelliteManager { * Unknown Non-Terrestrial radio technology. This generic radio technology should be used * only when the radio technology cannot be mapped to other specific radio technologies. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int NT_RADIO_TECHNOLOGY_UNKNOWN = 0; /** * 3GPP NB-IoT (Narrowband Internet of Things) over Non-Terrestrial-Networks technology. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int NT_RADIO_TECHNOLOGY_NB_IOT_NTN = 1; /** * 3GPP 5G NR over Non-Terrestrial-Networks technology. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int NT_RADIO_TECHNOLOGY_NR_NTN = 2; /** * 3GPP eMTC (enhanced Machine-Type Communication) over Non-Terrestrial-Networks technology. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int NT_RADIO_TECHNOLOGY_EMTC_NTN = 3; /** * Proprietary technology. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int NT_RADIO_TECHNOLOGY_PROPRIETARY = 4; /** @hide */ @@ -353,12 +385,16 @@ public final class SatelliteManager { public @interface NTRadioTechnology {} /** Suggested device hold position is unknown. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int DEVICE_HOLD_POSITION_UNKNOWN = 0; /** User is suggested to hold the device in portrait mode. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int DEVICE_HOLD_POSITION_PORTRAIT = 1; /** User is suggested to hold the device in landscape mode with left hand. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int DEVICE_HOLD_POSITION_LANDSCAPE_LEFT = 2; /** User is suggested to hold the device in landscape mode with right hand. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int DEVICE_HOLD_POSITION_LANDSCAPE_RIGHT = 3; /** @hide */ @@ -372,14 +408,18 @@ public final class SatelliteManager { public @interface DeviceHoldPosition {} /** Display mode is unknown. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int DISPLAY_MODE_UNKNOWN = 0; /** Display mode of the device used for satellite communication for non-foldable phones. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int DISPLAY_MODE_FIXED = 1; /** Display mode of the device used for satellite communication for foldabale phones when the * device is opened. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int DISPLAY_MODE_OPENED = 2; /** Display mode of the device used for satellite communication for foldabable phones when the * device is closed. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int DISPLAY_MODE_CLOSED = 3; /** @hide */ @@ -414,7 +454,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode, @NonNull @CallbackExecutor Executor executor, @SatelliteResult @NonNull Consumer<Integer> resultListener) { @@ -457,7 +497,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void requestIsSatelliteEnabled(@NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) { Objects.requireNonNull(executor); @@ -512,7 +552,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void requestIsDemoModeEnabled(@NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) { Objects.requireNonNull(executor); @@ -565,7 +605,7 @@ public final class SatelliteManager { * * @throws IllegalStateException if the Telephony process is not currently available. */ - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void requestIsSatelliteSupported(@NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) { Objects.requireNonNull(executor); @@ -619,7 +659,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void requestSatelliteCapabilities(@NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<SatelliteCapabilities, SatelliteException> callback) { Objects.requireNonNull(executor); @@ -664,16 +704,19 @@ public final class SatelliteManager { * The default state indicating that datagram transfer is idle. * This should be sent if there are no message transfer activity happening. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE = 0; /** * A transition state indicating that a datagram is being sent. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING = 1; /** * An end state indicating that datagram sending completed successfully. * After datagram transfer completes, {@link #SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE} * will be sent if no more messages are pending. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS = 2; /** * An end state indicating that datagram sending completed with a failure. @@ -681,16 +724,19 @@ public final class SatelliteManager { * must be sent before reporting any additional datagram transfer state changes. All pending * messages will be reported as failed, to the corresponding applications. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED = 3; /** * A transition state indicating that a datagram is being received. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING = 4; /** * An end state indicating that datagram receiving completed successfully. * After datagram transfer completes, {@link #SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE} * will be sent if no more messages are pending. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS = 5; /** * An end state indicating that datagram receive operation found that there are no @@ -698,12 +744,14 @@ public final class SatelliteManager { * After datagram transfer completes, {@link #SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE} * will be sent if no more messages are pending. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_NONE = 6; /** * An end state indicating that datagram receive completed with a failure. * After datagram transfer completes, {@link #SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE} * will be sent if no more messages are pending. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED = 7; /** * A transition state indicating that Telephony is waiting for satellite modem to connect to a @@ -721,6 +769,7 @@ public final class SatelliteManager { * only when the datagram transfer state cannot be mapped to other specific datagram transfer * states. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_UNKNOWN = -1; /** @hide */ @@ -743,26 +792,32 @@ public final class SatelliteManager { /** * Satellite modem is in idle state. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_MODEM_STATE_IDLE = 0; /** * Satellite modem is listening for incoming datagrams. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_MODEM_STATE_LISTENING = 1; /** * Satellite modem is sending and/or receiving datagrams. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING = 2; /** * Satellite modem is retrying to send and/or receive datagrams. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_MODEM_STATE_DATAGRAM_RETRYING = 3; /** * Satellite modem is powered off. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_MODEM_STATE_OFF = 4; /** * Satellite modem is unavailable. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_MODEM_STATE_UNAVAILABLE = 5; /** * The satellite modem is powered on but the device is not registered to a satellite cell. @@ -778,6 +833,7 @@ public final class SatelliteManager { * Satellite modem state is unknown. This generic modem state should be used only when the * modem state cannot be mapped to other specific modem states. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_MODEM_STATE_UNKNOWN = -1; /** @hide */ @@ -799,15 +855,18 @@ public final class SatelliteManager { * Datagram type is unknown. This generic datagram type should be used only when the * datagram type cannot be mapped to other specific datagram types. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int DATAGRAM_TYPE_UNKNOWN = 0; /** * Datagram type indicating that the datagram to be sent or received is of type SOS message. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int DATAGRAM_TYPE_SOS_MESSAGE = 1; /** * Datagram type indicating that the datagram to be sent or received is of type * location sharing. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int DATAGRAM_TYPE_LOCATION_SHARING = 2; /** @hide */ @@ -857,7 +916,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void startSatelliteTransmissionUpdates(@NonNull @CallbackExecutor Executor executor, @SatelliteResult @NonNull Consumer<Integer> resultListener, @NonNull SatelliteTransmissionUpdateCallback callback) { @@ -927,7 +986,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void stopSatelliteTransmissionUpdates( @NonNull SatelliteTransmissionUpdateCallback callback, @NonNull @CallbackExecutor Executor executor, @@ -983,7 +1042,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void provisionSatelliteService(@NonNull String token, @NonNull byte[] provisionData, @Nullable CancellationSignal cancellationSignal, @NonNull @CallbackExecutor Executor executor, @@ -1036,7 +1095,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void deprovisionSatelliteService(@NonNull String token, @NonNull @CallbackExecutor Executor executor, @SatelliteResult @NonNull Consumer<Integer> resultListener) { @@ -1076,7 +1135,7 @@ public final class SatelliteManager { * @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 registerForSatelliteProvisionStateChanged( @NonNull @CallbackExecutor Executor executor, @NonNull SatelliteProvisionStateCallback callback) { @@ -1119,7 +1178,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void unregisterForSatelliteProvisionStateChanged( @NonNull SatelliteProvisionStateCallback callback) { Objects.requireNonNull(callback); @@ -1158,7 +1217,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void requestIsSatelliteProvisioned(@NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) { Objects.requireNonNull(executor); @@ -1210,7 +1269,7 @@ public final class SatelliteManager { * @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 registerForSatelliteModemStateChanged( @NonNull @CallbackExecutor Executor executor, @NonNull SatelliteStateCallback callback) { @@ -1250,7 +1309,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void unregisterForSatelliteModemStateChanged(@NonNull SatelliteStateCallback callback) { Objects.requireNonNull(callback); ISatelliteStateCallback internalCallback = sSatelliteStateCallbackMap.remove(callback); @@ -1288,7 +1347,7 @@ public final class SatelliteManager { * @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 registerForSatelliteDatagram( @NonNull @CallbackExecutor Executor executor, @NonNull SatelliteDatagramCallback callback) { @@ -1344,7 +1403,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void unregisterForSatelliteDatagram(@NonNull SatelliteDatagramCallback callback) { Objects.requireNonNull(callback); ISatelliteDatagramCallback internalCallback = @@ -1383,7 +1442,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void pollPendingSatelliteDatagrams(@NonNull @CallbackExecutor Executor executor, @SatelliteResult @NonNull Consumer<Integer> resultListener) { Objects.requireNonNull(executor); @@ -1436,7 +1495,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void sendSatelliteDatagram(@DatagramType int datagramType, @NonNull SatelliteDatagram datagram, boolean needFullScreenPointingUI, @NonNull @CallbackExecutor Executor executor, @@ -1482,7 +1541,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void requestIsSatelliteCommunicationAllowedForCurrentLocation( @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) { @@ -1540,7 +1599,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void requestTimeForNextSatelliteVisibility(@NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Duration, SatelliteException> callback) { Objects.requireNonNull(executor); @@ -1594,7 +1653,7 @@ public final class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public void setDeviceAlignedWithSatellite(boolean isAligned) { try { ITelephony telephony = getITelephony(); @@ -1628,7 +1687,7 @@ public final class SatelliteManager { * @param subId The subscription ID of the carrier. * @param enableSatellite {@code true} to enable the satellite and {@code false} to disable. * @param executor The executor on which the error code listener will be called. - * @param resultListener Listener for the {@link SatelliteError} result of the operation. + * @param resultListener Listener for 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. @@ -1662,7 +1721,7 @@ public final class SatelliteManager { * will return a {@code boolean} with value {@code true} if the satellite * is enabled and {@code false} otherwise. * If the request is not successful, {@link OutcomeReceiver#onError(Throwable)} - * will return a {@link SatelliteException} with the {@link SatelliteError}. + * 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. @@ -1688,7 +1747,7 @@ public final class SatelliteManager { * @param subId The subscription ID of the carrier. * @param reason Reason for disallowing satellite communication. * @param executor The executor on which the error code listener will be called. - * @param resultListener Listener for the {@link SatelliteError} result of the operation. + * @param resultListener Listener for 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. @@ -1731,7 +1790,7 @@ public final class SatelliteManager { * @param subId The subscription ID of the carrier. * @param reason Reason for disallowing satellite communication. * @param executor The executor on which the error code listener will be called. - * @param resultListener Listener for the {@link SatelliteError} result of the operation. + * @param resultListener Listener for 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. diff --git a/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java index 71168762cca1..a12952be7620 100644 --- a/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java +++ b/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java @@ -16,14 +16,18 @@ package android.telephony.satellite; +import android.annotation.FlaggedApi; import android.annotation.SystemApi; +import com.android.internal.telephony.flags.Flags; + /** * A callback class for monitoring satellite provision state change events. * * @hide */ @SystemApi +@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public interface SatelliteProvisionStateCallback { /** * Called when satellite provision state changes. @@ -33,5 +37,6 @@ public interface SatelliteProvisionStateCallback { * It is generally expected that the provisioning app retries if * provisioning fails. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) void onSatelliteProvisionStateChanged(boolean provisioned); } diff --git a/telephony/java/android/telephony/satellite/SatelliteStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteStateCallback.java index 812ff2decf5d..bfe6e101ffb4 100644 --- a/telephony/java/android/telephony/satellite/SatelliteStateCallback.java +++ b/telephony/java/android/telephony/satellite/SatelliteStateCallback.java @@ -16,18 +16,23 @@ package android.telephony.satellite; +import android.annotation.FlaggedApi; import android.annotation.SystemApi; +import com.android.internal.telephony.flags.Flags; + /** * A callback class for monitoring satellite modem state change events. * * @hide */ @SystemApi +@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public interface SatelliteStateCallback { /** * Called when satellite modem state changes. * @param state The new satellite modem state. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) void onSatelliteModemStateChanged(@SatelliteManager.SatelliteModemState int state); } diff --git a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java index 7ac06b04a8eb..e02097019c4c 100644 --- a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java +++ b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java @@ -16,9 +16,12 @@ 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 monitoring satellite position update and datagram transfer state change * events. @@ -26,12 +29,14 @@ import android.annotation.SystemApi; * @hide */ @SystemApi +@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public interface SatelliteTransmissionUpdateCallback { /** * Called when the satellite position changed. * * @param pointingInfo The pointing info containing the satellite location. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) void onSatellitePositionChanged(@NonNull PointingInfo pointingInfo); /** @@ -41,6 +46,7 @@ public interface SatelliteTransmissionUpdateCallback { * @param sendPendingCount The number of datagrams that are currently being sent. * @param errorCode If datagram transfer failed, the reason for failure. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) void onSendDatagramStateChanged( @SatelliteManager.SatelliteDatagramTransferState int state, int sendPendingCount, @SatelliteManager.SatelliteResult int errorCode); @@ -52,6 +58,7 @@ public interface SatelliteTransmissionUpdateCallback { * @param receivePendingCount The number of datagrams that are currently pending to be received. * @param errorCode If datagram transfer failed, the reason for failure. */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) void onReceiveDatagramStateChanged( @SatelliteManager.SatelliteDatagramTransferState int state, int receivePendingCount, @SatelliteManager.SatelliteResult int errorCode); diff --git a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl index 0fcd0d622d36..7fda550c599c 100644 --- a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl +++ b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl @@ -32,15 +32,15 @@ oneway interface ISatellite { * * @param listener The callback interface to handle satellite service indications. * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ void setSatelliteListener(in ISatelliteListener listener); @@ -53,15 +53,15 @@ oneway interface ISatellite { * disabling listening mode. * @param resultCallback The callback to receive the error code result of the operation. * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ void requestSatelliteListeningEnabled(in boolean enable, in int timeout, in IIntegerConsumer resultCallback); @@ -84,15 +84,15 @@ oneway interface ISatellite { * @param enableDemoMode True to enable demo mode and false to disable. * @param resultCallback The callback to receive the error code result of the operation. * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ void requestSatelliteEnabled(in boolean enableSatellite, in boolean enableDemoMode, in IIntegerConsumer resultCallback); @@ -101,39 +101,42 @@ oneway interface ISatellite { * Request to get whether the satellite modem is enabled. * * @param resultCallback The callback to receive the error code result of the operation. - * This must only be sent when the error is not SatelliteError#ERROR_NONE. - * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive - * whether the satellite modem is enabled. - * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * This must only be sent when the result is not + * SatelliteResult#SATELLITE_RESULT_SUCCESS. + * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to + * receive whether the satellite modem is enabled. + * + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ - void requestIsSatelliteEnabled(in IIntegerConsumer resultCallback, in IBooleanConsumer callback); + void requestIsSatelliteEnabled(in IIntegerConsumer resultCallback, + in IBooleanConsumer callback); /** * Request to get whether the satellite service is supported on the device. * * @param resultCallback The callback to receive the error code result of the operation. - * This must only be sent when the error is not SatelliteError#ERROR_NONE. - * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive - * whether the satellite service is supported on the device. - * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * This must only be sent when the result is not + * SatelliteResult#SATELLITE_RESULT_SUCCESS. + * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to + * receive whether the satellite service is supported on the device. + * + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ void requestIsSatelliteSupported(in IIntegerConsumer resultCallback, in IBooleanConsumer callback); @@ -142,19 +145,20 @@ oneway interface ISatellite { * Request to get the SatelliteCapabilities of the satellite service. * * @param resultCallback The callback to receive the error code result of the operation. - * This must only be sent when the error is not SatelliteError#ERROR_NONE. - * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive - * the SatelliteCapabilities of the satellite service. - * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * This must only be sent when the result is not + * SatelliteResult#SATELLITE_RESULT_SUCCESS. + * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to + * receive the SatelliteCapabilities of the satellite service. + * + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ void requestSatelliteCapabilities(in IIntegerConsumer resultCallback, in ISatelliteCapabilitiesConsumer callback); @@ -166,15 +170,15 @@ oneway interface ISatellite { * * @param resultCallback The callback to receive the error code result of the operation. * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ void startSendingSatellitePointingInfo(in IIntegerConsumer resultCallback); @@ -184,15 +188,15 @@ oneway interface ISatellite { * * @param resultCallback The callback to receive the error code result of the operation. * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ void stopSendingSatellitePointingInfo(in IIntegerConsumer resultCallback); @@ -206,18 +210,18 @@ oneway interface ISatellite { * @param provisionData Data from the provisioning app that can be used by provisioning server * @param resultCallback The callback to receive the error code result of the operation. * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:NETWORK_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES - * SatelliteError:REQUEST_ABORTED - * SatelliteError:NETWORK_TIMEOUT + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_NETWORK_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES + * SatelliteResult:SATELLITE_RESULT_REQUEST_ABORTED + * SatelliteResult:SATELLITE_RESULT_NETWORK_TIMEOUT */ void provisionSatelliteService(in String token, in byte[] provisionData, in IIntegerConsumer resultCallback); @@ -230,18 +234,18 @@ oneway interface ISatellite { * @param token The token of the device/subscription to be deprovisioned. * @param resultCallback The callback to receive the error code result of the operation. * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:NETWORK_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES - * SatelliteError:REQUEST_ABORTED - * SatelliteError:NETWORK_TIMEOUT + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_NETWORK_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES + * SatelliteResult:SATELLITE_RESULT_REQUEST_ABORTED + * SatelliteResult:SATELLITE_RESULT_NETWORK_TIMEOUT */ void deprovisionSatelliteService(in String token, in IIntegerConsumer resultCallback); @@ -249,19 +253,20 @@ oneway interface ISatellite { * Request to get whether this device is provisioned with a satellite provider. * * @param resultCallback The callback to receive the error code result of the operation. - * This must only be sent when the error is not SatelliteError#ERROR_NONE. - * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive - * whether this device is provisioned with a satellite provider. - * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * This must only be sent when the result is not + * SatelliteResult#SATELLITE_RESULT_SUCCESS. + * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to + * receive whether this device is provisioned with a satellite provider. + * + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ void requestIsSatelliteProvisioned(in IIntegerConsumer resultCallback, in IBooleanConsumer callback); @@ -273,20 +278,20 @@ oneway interface ISatellite { * * @param resultCallback The callback to receive the error code result of the operation. * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:NETWORK_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES - * SatelliteError:SATELLITE_ACCESS_BARRED - * SatelliteError:NETWORK_TIMEOUT - * SatelliteError:SATELLITE_NOT_REACHABLE - * SatelliteError:NOT_AUTHORIZED + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_NETWORK_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES + * SatelliteResult:SATELLITE_RESULT_ACCESS_BARRED + * SatelliteResult:SATELLITE_RESULT_NETWORK_TIMEOUT + * SatelliteResult:SATELLITE_RESULT_NOT_REACHABLE + * SatelliteResult:SATELLITE_RESULT_NOT_AUTHORIZED */ void pollPendingSatelliteDatagrams(in IIntegerConsumer resultCallback); @@ -297,21 +302,21 @@ oneway interface ISatellite { * @param isEmergency Whether this is an emergency datagram. * @param resultCallback The callback to receive the error code result of the operation. * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:NETWORK_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES - * SatelliteError:REQUEST_ABORTED - * SatelliteError:SATELLITE_ACCESS_BARRED - * SatelliteError:NETWORK_TIMEOUT - * SatelliteError:SATELLITE_NOT_REACHABLE - * SatelliteError:NOT_AUTHORIZED + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_NETWORK_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES + * SatelliteResult:SATELLITE_RESULT_REQUEST_ABORTED + * SatelliteResult:SATELLITE_RESULT_ACCESS_BARRED + * SatelliteResult:SATELLITE_RESULT_NETWORK_TIMEOUT + * SatelliteResult:SATELLITE_RESULT_NOT_REACHABLE + * SatelliteResult:SATELLITE_RESULT_NOT_AUTHORIZED */ void sendSatelliteDatagram(in SatelliteDatagram datagram, in boolean isEmergency, in IIntegerConsumer resultCallback); @@ -322,19 +327,20 @@ oneway interface ISatellite { * ISatelliteListener#onSatelliteModemStateChanged. * * @param resultCallback The callback to receive the error code result of the operation. - * This must only be sent when the error is not SatelliteError#ERROR_NONE. - * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive - * the current satellite modem state. - * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * This must only be sent when the result is not + * SatelliteResult#SATELLITE_RESULT_SUCCESS. + * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to + * receive the current satellite modem state. + * + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ void requestSatelliteModemState(in IIntegerConsumer resultCallback, in IIntegerConsumer callback); @@ -343,19 +349,20 @@ oneway interface ISatellite { * Request to get whether satellite communication is allowed for the current location. * * @param resultCallback The callback to receive the error code result of the operation. - * This must only be sent when the error is not SatelliteError#ERROR_NONE. - * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive - * whether satellite communication is allowed for the current location. - * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * This must only be sent when the result is not + * SatelliteResult#SATELLITE_RESULT_SUCCESS. + * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to + * receive whether satellite communication is allowed for the current location. + * + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ void requestIsSatelliteCommunicationAllowedForCurrentLocation( in IIntegerConsumer resultCallback, in IBooleanConsumer callback); @@ -366,19 +373,20 @@ oneway interface ISatellite { * This will return 0 if the satellite is currently visible. * * @param resultCallback The callback to receive the error code result of the operation. - * This must only be sent when the error is not SatelliteError#ERROR_NONE. - * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive - * the time after which the satellite will be visible. - * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * This must only be sent when the result is not + * SatelliteResult#SATELLITE_RESULT_SUCCESS. + * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to + * receive the time after which the satellite will be visible. + * + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ void requestTimeForNextSatelliteVisibility(in IIntegerConsumer resultCallback, in IIntegerConsumer callback); @@ -399,14 +407,14 @@ oneway interface ISatellite { * attach to them. * @param resultCallback The callback to receive the error code result of the operation. * - * Valid error codes returned: - * SatelliteError:NONE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:MODEM_ERR - * SatelliteError:NO_RESOURCES - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED + * Valid result codes returned: + * SatelliteResult:NONE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:MODEM_ERR + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED */ void setSatellitePlmn(int simSlot, in List<String> carrierPlmnList, in List<String> allSatellitePlmnList, in IIntegerConsumer resultCallback); @@ -420,12 +428,12 @@ oneway interface ISatellite { * @param serial Serial number of request. * @param enable {@code true} to enable satellite, {@code false} to disable satellite. * - * Valid errors returned: - * SatelliteError:NONE - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:MODEM_ERR - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED */ void setSatelliteEnabledForCarrier(int simSlot, boolean satelliteEnabled, in IIntegerConsumer callback); @@ -437,12 +445,12 @@ oneway interface ISatellite { * this information to determine the relevant carrier. * @param serial Serial number of request. * - * Valid errors returned: - * SatelliteError:NONE - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:MODEM_ERR - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED */ void requestIsSatelliteEnabledForCarrier(int simSlot, in IIntegerConsumer resultCallback, in IBooleanConsumer callback); diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java index a9c09c94d57c..4cee01e8bd39 100644 --- a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java +++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java @@ -72,172 +72,172 @@ public class SatelliteImplBase extends SatelliteService { @Override public void requestSatelliteListeningEnabled(boolean enable, int timeout, - IIntegerConsumer errorCallback) throws RemoteException { + IIntegerConsumer resultCallback) throws RemoteException { executeMethodAsync( () -> SatelliteImplBase.this - .requestSatelliteListeningEnabled(enable, timeout, errorCallback), + .requestSatelliteListeningEnabled(enable, timeout, resultCallback), "requestSatelliteListeningEnabled"); } @Override public void enableCellularModemWhileSatelliteModeIsOn(boolean enabled, - IIntegerConsumer errorCallback) throws RemoteException { + IIntegerConsumer resultCallback) throws RemoteException { executeMethodAsync( () -> SatelliteImplBase.this - .enableCellularModemWhileSatelliteModeIsOn(enabled, errorCallback), + .enableCellularModemWhileSatelliteModeIsOn(enabled, resultCallback), "enableCellularModemWhileSatelliteModeIsOn"); } @Override public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode, - IIntegerConsumer errorCallback) throws RemoteException { + IIntegerConsumer resultCallback) throws RemoteException { executeMethodAsync( () -> SatelliteImplBase.this .requestSatelliteEnabled( - enableSatellite, enableDemoMode, errorCallback), + enableSatellite, enableDemoMode, resultCallback), "requestSatelliteEnabled"); } @Override - public void requestIsSatelliteEnabled(IIntegerConsumer errorCallback, + public void requestIsSatelliteEnabled(IIntegerConsumer resultCallback, IBooleanConsumer callback) throws RemoteException { executeMethodAsync( () -> SatelliteImplBase.this - .requestIsSatelliteEnabled(errorCallback, callback), + .requestIsSatelliteEnabled(resultCallback, callback), "requestIsSatelliteEnabled"); } @Override - public void requestIsSatelliteSupported(IIntegerConsumer errorCallback, + public void requestIsSatelliteSupported(IIntegerConsumer resultCallback, IBooleanConsumer callback) throws RemoteException { executeMethodAsync( () -> SatelliteImplBase.this - .requestIsSatelliteSupported(errorCallback, callback), + .requestIsSatelliteSupported(resultCallback, callback), "requestIsSatelliteSupported"); } @Override - public void requestSatelliteCapabilities(IIntegerConsumer errorCallback, + public void requestSatelliteCapabilities(IIntegerConsumer resultCallback, ISatelliteCapabilitiesConsumer callback) throws RemoteException { executeMethodAsync( () -> SatelliteImplBase.this - .requestSatelliteCapabilities(errorCallback, callback), + .requestSatelliteCapabilities(resultCallback, callback), "requestSatelliteCapabilities"); } @Override - public void startSendingSatellitePointingInfo(IIntegerConsumer errorCallback) + public void startSendingSatellitePointingInfo(IIntegerConsumer resultCallback) throws RemoteException { executeMethodAsync( - () -> SatelliteImplBase.this.startSendingSatellitePointingInfo(errorCallback), + () -> SatelliteImplBase.this.startSendingSatellitePointingInfo(resultCallback), "startSendingSatellitePointingInfo"); } @Override - public void stopSendingSatellitePointingInfo(IIntegerConsumer errorCallback) + public void stopSendingSatellitePointingInfo(IIntegerConsumer resultCallback) throws RemoteException { executeMethodAsync( - () -> SatelliteImplBase.this.stopSendingSatellitePointingInfo(errorCallback), + () -> SatelliteImplBase.this.stopSendingSatellitePointingInfo(resultCallback), "stopSendingSatellitePointingInfo"); } @Override public void provisionSatelliteService(String token, byte[] provisionData, - IIntegerConsumer errorCallback) throws RemoteException { + IIntegerConsumer resultCallback) throws RemoteException { executeMethodAsync( () -> SatelliteImplBase.this - .provisionSatelliteService(token, provisionData, errorCallback), + .provisionSatelliteService(token, provisionData, resultCallback), "provisionSatelliteService"); } @Override - public void deprovisionSatelliteService(String token, IIntegerConsumer errorCallback) + public void deprovisionSatelliteService(String token, IIntegerConsumer resultCallback) throws RemoteException { executeMethodAsync( - () -> SatelliteImplBase.this.deprovisionSatelliteService(token, errorCallback), + () -> SatelliteImplBase.this.deprovisionSatelliteService(token, resultCallback), "deprovisionSatelliteService"); } @Override - public void requestIsSatelliteProvisioned(IIntegerConsumer errorCallback, + public void requestIsSatelliteProvisioned(IIntegerConsumer resultCallback, IBooleanConsumer callback) throws RemoteException { executeMethodAsync( () -> SatelliteImplBase.this - .requestIsSatelliteProvisioned(errorCallback, callback), + .requestIsSatelliteProvisioned(resultCallback, callback), "requestIsSatelliteProvisioned"); } @Override - public void pollPendingSatelliteDatagrams(IIntegerConsumer errorCallback) + public void pollPendingSatelliteDatagrams(IIntegerConsumer resultCallback) throws RemoteException { executeMethodAsync( - () -> SatelliteImplBase.this.pollPendingSatelliteDatagrams(errorCallback), + () -> SatelliteImplBase.this.pollPendingSatelliteDatagrams(resultCallback), "pollPendingSatelliteDatagrams"); } @Override public void sendSatelliteDatagram(SatelliteDatagram datagram, boolean isEmergency, - IIntegerConsumer errorCallback) throws RemoteException { + IIntegerConsumer resultCallback) throws RemoteException { executeMethodAsync( () -> SatelliteImplBase.this - .sendSatelliteDatagram(datagram, isEmergency, errorCallback), + .sendSatelliteDatagram(datagram, isEmergency, resultCallback), "sendSatelliteDatagram"); } @Override - public void requestSatelliteModemState(IIntegerConsumer errorCallback, + public void requestSatelliteModemState(IIntegerConsumer resultCallback, IIntegerConsumer callback) throws RemoteException { executeMethodAsync( () -> SatelliteImplBase.this - .requestSatelliteModemState(errorCallback, callback), + .requestSatelliteModemState(resultCallback, callback), "requestSatelliteModemState"); } @Override public void requestIsSatelliteCommunicationAllowedForCurrentLocation( - IIntegerConsumer errorCallback, IBooleanConsumer callback) + IIntegerConsumer resultCallback, IBooleanConsumer callback) throws RemoteException { executeMethodAsync( () -> SatelliteImplBase.this .requestIsSatelliteCommunicationAllowedForCurrentLocation( - errorCallback, callback), + resultCallback, callback), "requestIsSatelliteCommunicationAllowedForCurrentLocation"); } @Override - public void requestTimeForNextSatelliteVisibility(IIntegerConsumer errorCallback, + public void requestTimeForNextSatelliteVisibility(IIntegerConsumer resultCallback, IIntegerConsumer callback) throws RemoteException { executeMethodAsync( () -> SatelliteImplBase.this - .requestTimeForNextSatelliteVisibility(errorCallback, callback), + .requestTimeForNextSatelliteVisibility(resultCallback, callback), "requestTimeForNextSatelliteVisibility"); } @Override public void setSatellitePlmn(int simSlot, List<String> carrierPlmnList, - List<String> devicePlmnList, IIntegerConsumer errorCallback) + List<String> devicePlmnList, IIntegerConsumer resultCallback) throws RemoteException { executeMethodAsync( () -> SatelliteImplBase.this.setSatellitePlmn( - simSlot, carrierPlmnList, devicePlmnList, errorCallback), + simSlot, carrierPlmnList, devicePlmnList, resultCallback), "setSatellitePlmn"); } @Override public void setSatelliteEnabledForCarrier(int simSlot, boolean enableSatellite, - IIntegerConsumer errorCallback) throws RemoteException { + IIntegerConsumer resultCallback) throws RemoteException { executeMethodAsync( - () -> SatelliteImplBase.this - .setSatelliteEnabledForCarrier(simSlot, enableSatellite, errorCallback), + () -> SatelliteImplBase.this.setSatelliteEnabledForCarrier( + simSlot, enableSatellite, resultCallback), "setSatelliteEnabledForCarrier"); } @Override - public void requestIsSatelliteEnabledForCarrier(int simSlot, IIntegerConsumer errorCallback, - IBooleanConsumer callback) throws RemoteException { + public void requestIsSatelliteEnabledForCarrier(int simSlot, + IIntegerConsumer resultCallback, IBooleanConsumer callback) throws RemoteException { executeMethodAsync( () -> SatelliteImplBase.this - .requestIsSatelliteEnabledForCarrier(simSlot, errorCallback, callback), + .requestIsSatelliteEnabledForCarrier(simSlot, resultCallback, callback), "requestIsSatelliteEnabledForCarrier"); } @@ -260,15 +260,15 @@ public class SatelliteImplBase extends SatelliteService { * * @param listener The callback interface to handle satellite service indications. * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ public void setSatelliteListener(@NonNull ISatelliteListener listener) { // stub implementation @@ -281,20 +281,20 @@ public class SatelliteImplBase extends SatelliteService { * @param enable True to enable satellite listening mode and false to disable. * @param timeout How long the satellite modem should wait for the next incoming page before * disabling listening mode. - * @param errorCallback The callback to receive the error code result of the operation. - * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * @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_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ public void requestSatelliteListeningEnabled(boolean enable, int timeout, - @NonNull IIntegerConsumer errorCallback) { + @NonNull IIntegerConsumer resultCallback) { // stub implementation } @@ -302,10 +302,10 @@ public class SatelliteImplBase extends SatelliteService { * Allow cellular modem scanning while satellite mode is on. * @param enabled {@code true} to enable cellular modem while satellite mode is on * and {@code false} to disable - * @param errorCallback The callback to receive the error code result of the operation. + * @param resultCallback The callback to receive the error code result of the operation. */ public void enableCellularModemWhileSatelliteModeIsOn(boolean enabled, - @NonNull IIntegerConsumer errorCallback) { + @NonNull IIntegerConsumer resultCallback) { // stub implementation } @@ -316,42 +316,43 @@ public class SatelliteImplBase extends SatelliteService { * * @param enableSatellite True to enable the satellite modem and false to disable. * @param enableDemoMode True to enable demo mode and false to disable. - * @param errorCallback The callback to receive the error code result of the operation. - * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * @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_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode, - @NonNull IIntegerConsumer errorCallback) { + @NonNull IIntegerConsumer resultCallback) { // stub implementation } /** * Request to get whether the satellite modem is enabled. * - * @param errorCallback The callback to receive the error code result of the operation. - * This must only be sent when the result is not SatelliteError#ERROR_NONE. - * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive - * whether the satellite modem is enabled. - * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * @param resultCallback The callback to receive the error code result of the operation. + * This must only be sent when the result is not + * SatelliteResult#SATELLITE_RESULT_SUCCESS. + * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to + * receive whether the satellite modem is enabled. + * + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ - public void requestIsSatelliteEnabled(@NonNull IIntegerConsumer errorCallback, + public void requestIsSatelliteEnabled(@NonNull IIntegerConsumer resultCallback, @NonNull IBooleanConsumer callback) { // stub implementation } @@ -359,22 +360,23 @@ public class SatelliteImplBase extends SatelliteService { /** * Request to get whether the satellite service is supported on the device. * - * @param errorCallback The callback to receive the error code result of the operation. - * This must only be sent when the result is not SatelliteError#ERROR_NONE. - * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive - * whether the satellite service is supported on the device. - * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * @param resultCallback The callback to receive the error code result of the operation. + * This must only be sent when the result is not + * SatelliteResult#SATELLITE_RESULT_SUCCESS. + * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to + * receive whether the satellite service is supported on the device. + * + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ - public void requestIsSatelliteSupported(@NonNull IIntegerConsumer errorCallback, + public void requestIsSatelliteSupported(@NonNull IIntegerConsumer resultCallback, @NonNull IBooleanConsumer callback) { // stub implementation } @@ -382,22 +384,23 @@ public class SatelliteImplBase extends SatelliteService { /** * Request to get the SatelliteCapabilities of the satellite service. * - * @param errorCallback The callback to receive the error code result of the operation. - * This must only be sent when the result is not SatelliteError#ERROR_NONE. - * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive - * the SatelliteCapabilities of the satellite service. - * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * @param resultCallback The callback to receive the error code result of the operation. + * This must only be sent when the result is not + * SatelliteResult#SATELLITE_RESULT_SUCCESS. + * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to + * receive the SatelliteCapabilities of the satellite service. + * + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ - public void requestSatelliteCapabilities(@NonNull IIntegerConsumer errorCallback, + public void requestSatelliteCapabilities(@NonNull IIntegerConsumer resultCallback, @NonNull ISatelliteCapabilitiesConsumer callback) { // stub implementation } @@ -407,19 +410,19 @@ public class SatelliteImplBase extends SatelliteService { * The satellite service should report the satellite pointing info via * ISatelliteListener#onSatellitePositionChanged as the user device/satellite moves. * - * @param errorCallback The callback to receive the error code result of the operation. - * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * @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_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ - public void startSendingSatellitePointingInfo(@NonNull IIntegerConsumer errorCallback) { + public void startSendingSatellitePointingInfo(@NonNull IIntegerConsumer resultCallback) { // stub implementation } @@ -427,19 +430,19 @@ public class SatelliteImplBase extends SatelliteService { * User stopped pointing to the satellite. * The satellite service should stop reporting satellite pointing info to the framework. * - * @param errorCallback The callback to receive the error code result of the operation. - * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * @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_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ - public void stopSendingSatellitePointingInfo(@NonNull IIntegerConsumer errorCallback) { + public void stopSendingSatellitePointingInfo(@NonNull IIntegerConsumer resultCallback) { // stub implementation } @@ -452,23 +455,23 @@ public class SatelliteImplBase extends SatelliteService { * gateway. * @param provisionData Data from the provisioning app that can be used by provisioning * server - * @param errorCallback The callback to receive the error code result of the operation. - * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:NETWORK_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES - * SatelliteError:REQUEST_ABORTED - * SatelliteError:NETWORK_TIMEOUT + * @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_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_NETWORK_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES + * SatelliteResult:SATELLITE_RESULT_REQUEST_ABORTED + * SatelliteResult:SATELLITE_RESULT_NETWORK_TIMEOUT */ public void provisionSatelliteService(@NonNull String token, @NonNull byte[] provisionData, - @NonNull IIntegerConsumer errorCallback) { + @NonNull IIntegerConsumer resultCallback) { // stub implementation } @@ -478,45 +481,46 @@ public class SatelliteImplBase extends SatelliteService { * Once deprovisioned, ISatelliteListener#onSatelliteProvisionStateChanged should report false. * * @param token The token of the device/subscription to be deprovisioned. - * @param errorCallback The callback to receive the error code result of the operation. - * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:NETWORK_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES - * SatelliteError:REQUEST_ABORTED - * SatelliteError:NETWORK_TIMEOUT + * @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_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_NETWORK_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES + * SatelliteResult:SATELLITE_RESULT_REQUEST_ABORTED + * SatelliteResult:SATELLITE_RESULT_NETWORK_TIMEOUT */ public void deprovisionSatelliteService(@NonNull String token, - @NonNull IIntegerConsumer errorCallback) { + @NonNull IIntegerConsumer resultCallback) { // stub implementation } /** * Request to get whether this device is provisioned with a satellite provider. * - * @param errorCallback The callback to receive the error code result of the operation. - * This must only be sent when the result is not SatelliteError#ERROR_NONE. - * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive - * whether this device is provisioned with a satellite provider. - * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * @param resultCallback The callback to receive the error code result of the operation. + * This must only be sent when the result is not + * SatelliteResult#SATELLITE_RESULT_SUCCESS. + * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to + * receive whether this device is provisioned with a satellite provider. + * + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ - public void requestIsSatelliteProvisioned(@NonNull IIntegerConsumer errorCallback, + public void requestIsSatelliteProvisioned(@NonNull IIntegerConsumer resultCallback, @NonNull IBooleanConsumer callback) { // stub implementation } @@ -526,24 +530,24 @@ public class SatelliteImplBase extends SatelliteService { * The satellite service should check if there are any pending datagrams to be received over * satellite and report them via ISatelliteListener#onSatelliteDatagramsReceived. * - * @param errorCallback The callback to receive the error code result of the operation. - * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:NETWORK_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES - * SatelliteError:SATELLITE_ACCESS_BARRED - * SatelliteError:NETWORK_TIMEOUT - * SatelliteError:SATELLITE_NOT_REACHABLE - * SatelliteError:NOT_AUTHORIZED + * @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_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_NETWORK_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES + * SatelliteResult:SATELLITE_RESULT_ACCESS_BARRED + * SatelliteResult:SATELLITE_RESULT_NETWORK_TIMEOUT + * SatelliteResult:SATELLITE_RESULT_NOT_REACHABLE + * SatelliteResult:SATELLITE_RESULT_NOT_AUTHORIZED */ - public void pollPendingSatelliteDatagrams(@NonNull IIntegerConsumer errorCallback) { + public void pollPendingSatelliteDatagrams(@NonNull IIntegerConsumer resultCallback) { // stub implementation } @@ -552,26 +556,26 @@ public class SatelliteImplBase extends SatelliteService { * * @param datagram Datagram to send in byte format. * @param isEmergency Whether this is an emergency datagram. - * @param errorCallback The callback to receive the error code result of the operation. - * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:NETWORK_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES - * SatelliteError:REQUEST_ABORTED - * SatelliteError:SATELLITE_ACCESS_BARRED - * SatelliteError:NETWORK_TIMEOUT - * SatelliteError:SATELLITE_NOT_REACHABLE - * SatelliteError:NOT_AUTHORIZED + * @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_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_NETWORK_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES + * SatelliteResult:SATELLITE_RESULT_REQUEST_ABORTED + * SatelliteResult:SATELLITE_RESULT_ACCESS_BARRED + * SatelliteResult:SATELLITE_RESULT_NETWORK_TIMEOUT + * SatelliteResult:SATELLITE_RESULT_NOT_REACHABLE + * SatelliteResult:SATELLITE_RESULT_NOT_AUTHORIZED */ public void sendSatelliteDatagram(@NonNull SatelliteDatagram datagram, boolean isEmergency, - @NonNull IIntegerConsumer errorCallback) { + @NonNull IIntegerConsumer resultCallback) { // stub implementation } @@ -580,22 +584,23 @@ public class SatelliteImplBase extends SatelliteService { * The satellite service should report the current satellite modem state via * ISatelliteListener#onSatelliteModemStateChanged. * - * @param errorCallback The callback to receive the error code result of the operation. - * This must only be sent when the result is not SatelliteError#ERROR_NONE. - * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive - * the current satellite modem state. - * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * @param resultCallback The callback to receive the error code result of the operation. + * This must only be sent when the result is not + * SatelliteResult#SATELLITE_RESULT_SUCCESS. + * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to + * receive the current satellite modem state. + * + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ - public void requestSatelliteModemState(@NonNull IIntegerConsumer errorCallback, + public void requestSatelliteModemState(@NonNull IIntegerConsumer resultCallback, @NonNull IIntegerConsumer callback) { // stub implementation } @@ -603,23 +608,24 @@ public class SatelliteImplBase extends SatelliteService { /** * Request to get whether satellite communication is allowed for the current location. * - * @param errorCallback The callback to receive the error code result of the operation. - * This must only be sent when the result is not SatelliteError#ERROR_NONE. - * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive - * whether satellite communication is allowed for the current location. - * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * @param resultCallback The callback to receive the error code result of the operation. + * This must only be sent when the result is not + * SatelliteResult#SATELLITE_RESULT_SUCCESS. + * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to + * receive whether satellite communication is allowed for the current location. + * + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ public void requestIsSatelliteCommunicationAllowedForCurrentLocation( - @NonNull IIntegerConsumer errorCallback, @NonNull IBooleanConsumer callback) { + @NonNull IIntegerConsumer resultCallback, @NonNull IBooleanConsumer callback) { // stub implementation } @@ -628,22 +634,23 @@ public class SatelliteImplBase extends SatelliteService { * representing the duration in seconds after which the satellite will be visible. * This will return 0 if the satellite is currently visible. * - * @param errorCallback The callback to receive the error code result of the operation. - * This must only be sent when the result is not SatelliteError#ERROR_NONE. - * @param callback If the result is SatelliteError#ERROR_NONE, the callback to receive - * the time after which the satellite will be visible. - * - * Valid error codes returned: - * SatelliteError:ERROR_NONE - * SatelliteError:SERVICE_ERROR - * SatelliteError:MODEM_ERROR - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED - * SatelliteError:NO_RESOURCES + * @param resultCallback The callback to receive the error code result of the operation. + * This must only be sent when the result is not + * SatelliteResult#SATELLITE_RESULT_SUCCESS. + * @param callback If the result is SatelliteResult#SATELLITE_RESULT_SUCCESS, the callback to + * receive the time after which the satellite will be visible. + * + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ - public void requestTimeForNextSatelliteVisibility(@NonNull IIntegerConsumer errorCallback, + public void requestTimeForNextSatelliteVisibility(@NonNull IIntegerConsumer resultCallback, @NonNull IIntegerConsumer callback) { // stub implementation } @@ -658,25 +665,25 @@ public class SatelliteImplBase extends SatelliteService { * * @param simLogicalSlotIndex Indicates the SIM logical slot index to which this API will be * applied. The modem will use this information to determine the relevant carrier. - * @param errorCallback The callback to receive the error code result of the operation. + * @param resultCallback The callback to receive the error code result of the operation. * @param carrierPlmnList The list of roaming PLMN used for connecting to satellite networks * supported by user subscription. * @param allSatellitePlmnList Modem should use the allSatellitePlmnList to identify satellite * PLMNs that are not supported by the carrier and make sure not to * attach to them. * - * Valid error codes returned: - * SatelliteError:NONE - * SatelliteError:INVALID_ARGUMENTS - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:MODEM_ERR - * SatelliteError:NO_RESOURCES - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED + * Valid result codes returned: + * SatelliteResult:NONE + * SatelliteResult:SATELLITE_RESULT_INVALID_ARGUMENTS + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:MODEM_ERR + * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED */ public void setSatellitePlmn(@NonNull int simLogicalSlotIndex, @NonNull List<String> carrierPlmnList, @NonNull List<String> allSatellitePlmnList, - @NonNull IIntegerConsumer errorCallback) { + @NonNull IIntegerConsumer resultCallback) { // stub implementation } @@ -689,12 +696,12 @@ public class SatelliteImplBase extends SatelliteService { * @param satelliteEnabled {@code true} to enable satellite, {@code false} to disable satellite. * @param callback {@code true} to enable satellite, {@code false} to disable satellite. * - * Valid errors returned: - * SatelliteError:NONE - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:MODEM_ERR - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED */ public void setSatelliteEnabledForCarrier(@NonNull int simLogicalSlotIndex, @NonNull boolean satelliteEnabled, @NonNull IIntegerConsumer callback) { @@ -707,18 +714,18 @@ public class SatelliteImplBase extends SatelliteService { * * @param simLogicalSlotIndex Indicates the SIM logical slot index to which this API will be * applied. The modem will use this information to determine the relevant carrier. - * @param errorCallback The callback to receive the error code result of the operation. + * @param resultCallback The callback to receive the error code result of the operation. * @param callback {@code true} to satellite enabled, {@code false} to satellite disabled. * - * Valid errors returned: - * SatelliteError:NONE - * SatelliteError:INVALID_MODEM_STATE - * SatelliteError:MODEM_ERR - * SatelliteError:RADIO_NOT_AVAILABLE - * SatelliteError:REQUEST_NOT_SUPPORTED + * Valid result codes returned: + * SatelliteResult:SATELLITE_RESULT_SUCCESS + * SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE + * SatelliteResult:SATELLITE_RESULT_MODEM_ERROR + * SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE + * SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED */ public void requestIsSatelliteEnabledForCarrier(@NonNull int simLogicalSlotIndex, - @NonNull IIntegerConsumer errorCallback, @NonNull IBooleanConsumer callback) { + @NonNull IIntegerConsumer resultCallback, @NonNull IBooleanConsumer callback) { // stub implementation } } diff --git a/test-mock/api/current.txt b/test-mock/api/current.txt index d1a68d4e9cb2..241e69106718 100644 --- a/test-mock/api/current.txt +++ b/test-mock/api/current.txt @@ -1,4 +1,6 @@ // Signature format: 2.0 +// - add-additional-overrides=no +// - migrating=Migration in progress see b/299366704 package android.test.mock { @Deprecated public class MockAccountManager { diff --git a/test-mock/api/removed.txt b/test-mock/api/removed.txt index 1496c356da08..fa2fbd276e9a 100644 --- a/test-mock/api/removed.txt +++ b/test-mock/api/removed.txt @@ -1,4 +1,6 @@ // 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 { diff --git a/test-mock/api/system-current.txt b/test-mock/api/system-current.txt index 9e022f0cb0ef..2b5132ecd14f 100644 --- a/test-mock/api/system-current.txt +++ b/test-mock/api/system-current.txt @@ -1,4 +1,6 @@ // 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 { diff --git a/test-mock/api/system-removed.txt b/test-mock/api/system-removed.txt index d802177e249b..14191ebcb080 100644 --- a/test-mock/api/system-removed.txt +++ b/test-mock/api/system-removed.txt @@ -1 +1,3 @@ // 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 35f076fe3f00..1752edcd469e 100644 --- a/test-mock/api/test-current.txt +++ b/test-mock/api/test-current.txt @@ -1,4 +1,6 @@ // 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 { diff --git a/test-mock/api/test-removed.txt b/test-mock/api/test-removed.txt index d802177e249b..14191ebcb080 100644 --- a/test-mock/api/test-removed.txt +++ b/test-mock/api/test-removed.txt @@ -1 +1,3 @@ // 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/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt index fa86e9c4ec0a..44de6a6ecbc3 100644 --- a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt +++ b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt @@ -100,6 +100,7 @@ class KeyboardLayoutManagerTests { const val RECEIVER_NAME = "DummyReceiver" private const val ENGLISH_US_LAYOUT_NAME = "keyboard_layout_english_us" private const val ENGLISH_UK_LAYOUT_NAME = "keyboard_layout_english_uk" + private const val GERMAN_LAYOUT_NAME = "keyboard_layout_german" private const val VENDOR_SPECIFIC_LAYOUT_NAME = "keyboard_layout_vendorId:1,productId:1" const val LAYOUT_TYPE_QWERTZ = 2 const val LAYOUT_TYPE_QWERTY = 1 @@ -108,6 +109,7 @@ class KeyboardLayoutManagerTests { private val ENGLISH_US_LAYOUT_DESCRIPTOR = createLayoutDescriptor(ENGLISH_US_LAYOUT_NAME) private val ENGLISH_UK_LAYOUT_DESCRIPTOR = createLayoutDescriptor(ENGLISH_UK_LAYOUT_NAME) + private val GERMAN_LAYOUT_DESCRIPTOR = createLayoutDescriptor(GERMAN_LAYOUT_NAME) private val VENDOR_SPECIFIC_LAYOUT_DESCRIPTOR = createLayoutDescriptor(VENDOR_SPECIFIC_LAYOUT_NAME) @@ -710,7 +712,7 @@ class KeyboardLayoutManagerTests { assertCorrectLayout( keyboardDevice, createImeSubtypeForLanguageTag("de"), - createLayoutDescriptor("keyboard_layout_german") + GERMAN_LAYOUT_DESCRIPTOR ) assertCorrectLayout( keyboardDevice, @@ -775,13 +777,13 @@ class KeyboardLayoutManagerTests { assertCorrectLayout( keyboardDevice, createImeSubtypeForLanguageTagAndLayoutType("de", "qwertz"), - createLayoutDescriptor("keyboard_layout_german") + GERMAN_LAYOUT_DESCRIPTOR ) // Wrong layout type should match with language if provided layout type not available assertCorrectLayout( keyboardDevice, createImeSubtypeForLanguageTagAndLayoutType("de", "qwerty"), - createLayoutDescriptor("keyboard_layout_german") + GERMAN_LAYOUT_DESCRIPTOR ) assertCorrectLayout( keyboardDevice, @@ -856,7 +858,7 @@ class KeyboardLayoutManagerTests { ArgumentMatchers.eq(createByteArray( KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG, LAYOUT_TYPE_DEFAULT, - "German", + GERMAN_LAYOUT_NAME, KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD, "de-Latn", LAYOUT_TYPE_QWERTZ)) @@ -882,7 +884,7 @@ class KeyboardLayoutManagerTests { ArgumentMatchers.eq(createByteArray( "en", LAYOUT_TYPE_QWERTY, - "English (US)", + ENGLISH_US_LAYOUT_NAME, KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE, "de-Latn", LAYOUT_TYPE_QWERTZ)) diff --git a/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt b/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt index b39c93244ad6..33ff09b55534 100644 --- a/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt +++ b/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt @@ -113,7 +113,7 @@ class KeyboardMetricsCollectorTests { ) val event = builder.addLayoutSelection( createImeSubtype(1, ULocale.forLanguageTag("en-US"), "qwerty"), - KeyboardLayout(null, "English(US)(Qwerty)", null, 0, null, 0, 0, 0), + "English(US)(Qwerty)", KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD ).addLayoutSelection( createImeSubtype(2, ULocale.forLanguageTag("en-US"), "azerty"), @@ -121,7 +121,7 @@ class KeyboardMetricsCollectorTests { KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_USER ).addLayoutSelection( createImeSubtype(3, ULocale.forLanguageTag("en-US"), "qwerty"), - KeyboardLayout(null, "German", null, 0, null, 0, 0, 0), + "German", KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE ).setIsFirstTimeConfiguration(true).build() @@ -184,7 +184,7 @@ class KeyboardMetricsCollectorTests { ) val event = builder.addLayoutSelection( createImeSubtype(4, null, "qwerty"), // Default language tag - KeyboardLayout(null, "German", null, 0, null, 0, 0, 0), + "German", KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE ).build() diff --git a/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt b/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt index 56ab755af47b..7e43566d56f8 100644 --- a/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt +++ b/tests/SilkFX/src/com/android/test/silkfx/common/ColorModeControls.kt @@ -37,6 +37,8 @@ class ColorModeControls : LinearLayout, WindowObserver { private var window: Window? = null private var currentModeDisplay: TextView? = null + private var desiredRatio = 0.0f + override fun onFinishInflate() { super.onFinishInflate() val window = window ?: throw IllegalStateException("Failed to attach window") @@ -67,6 +69,7 @@ class ColorModeControls : LinearLayout, WindowObserver { override fun onAttachedToWindow() { super.onAttachedToWindow() + desiredRatio = window?.desiredHdrHeadroom ?: 0.0f val hdrVis = if (display.isHdrSdrRatioAvailable) { display.registerHdrSdrRatioChangedListener({ it.run() }, hdrSdrListener) View.VISIBLE @@ -83,6 +86,11 @@ class ColorModeControls : LinearLayout, WindowObserver { } private fun setColorMode(newMode: Int) { + if (newMode == ActivityInfo.COLOR_MODE_HDR && + window!!.colorMode == ActivityInfo.COLOR_MODE_HDR) { + desiredRatio = (desiredRatio + 1) % 5.0f + window!!.desiredHdrHeadroom = desiredRatio + } window!!.colorMode = newMode updateModeInfoDisplay() } diff --git a/tests/testables/src/com/android/internal/config/sysui/TestableFlagResolver.java b/tests/testables/src/com/android/internal/config/sysui/TestableFlagResolver.java index 6a6ab00e33ff..a43e1b091d41 100644 --- a/tests/testables/src/com/android/internal/config/sysui/TestableFlagResolver.java +++ b/tests/testables/src/com/android/internal/config/sysui/TestableFlagResolver.java @@ -21,15 +21,39 @@ import java.util.Map; public class TestableFlagResolver implements SystemUiSystemPropertiesFlags.FlagResolver { private Map<String, Boolean> mOverrides = new HashMap<>(); + private Map<String, Integer> mOverridesInt = new HashMap<>(); + private Map<String, String> mOverridesString = new HashMap<>(); @Override public boolean isEnabled(SystemUiSystemPropertiesFlags.Flag flag) { return mOverrides.getOrDefault(flag.mSysPropKey, flag.mDefaultValue); } + @Override + public int getIntValue(SystemUiSystemPropertiesFlags.Flag flag) { + return mOverridesInt.getOrDefault(flag.mSysPropKey, flag.mDefaultIntValue); + } + + @Override + public String getStringValue(SystemUiSystemPropertiesFlags.Flag flag) { + return mOverridesString.getOrDefault(flag.mSysPropKey, flag.mDefaultStringValue); + } + public TestableFlagResolver setFlagOverride(SystemUiSystemPropertiesFlags.Flag flag, boolean isEnabled) { mOverrides.put(flag.mSysPropKey, isEnabled); return this; } + + public TestableFlagResolver setFlagOverride(SystemUiSystemPropertiesFlags.Flag flag, + int value) { + mOverridesInt.put(flag.mSysPropKey, value); + return this; + } + + public TestableFlagResolver setFlagOverride(SystemUiSystemPropertiesFlags.Flag flag, + String value) { + mOverridesString.put(flag.mSysPropKey, value); + return this; + } } diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ConstantFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ConstantFilter.kt index 33010baaf894..678e6eae0be6 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ConstantFilter.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ConstantFilter.kt @@ -46,8 +46,11 @@ class ConstantFilter( } methodPolicy = policy - // TODO: Need to think about the realistic default behavior. - classPolicy = if (policy != FilterPolicy.Throw) policy else FilterPolicy.Remove + // If the default policy is "throw", we convert it to "keep" for classes and fields. + classPolicy = when (policy) { + FilterPolicy.Throw -> FilterPolicy.Keep + else -> policy + } fieldPolicy = classPolicy } diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt index 9c372ff68e37..c6334c40e8f4 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt @@ -17,6 +17,8 @@ package com.android.hoststubgen.filters import com.android.hoststubgen.HostStubGenErrors import com.android.hoststubgen.HostStubGenInternalException +import com.android.hoststubgen.asm.CLASS_INITIALIZER_DESC +import com.android.hoststubgen.asm.CLASS_INITIALIZER_NAME import com.android.hoststubgen.asm.isAnonymousInnerClass import com.android.hoststubgen.log import com.android.hoststubgen.asm.ClassNodes @@ -81,6 +83,16 @@ class ImplicitOutputFilter( } } + // If we throw from the static initializer, the class would be useless, so we convert it + // "keep" instead. + if (methodName == CLASS_INITIALIZER_NAME && descriptor == CLASS_INITIALIZER_DESC && + fallback.policy == FilterPolicy.Throw) { + // TODO Maybe show a warning?? But that'd be too noisy with --default-throw. + return FilterPolicy.Keep.withReason( + "'throw' on static initializer is handled as 'keep'" + + " [original throw reason: ${fallback.reason}]") + } + return fallback } }
\ No newline at end of file diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt index 57b668954f98..ce72a8e79882 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt @@ -79,7 +79,7 @@ class ImplGeneratingAdapter( // StaticInitMerger will merge it with the existing one, if any. visitMethod( Opcodes.ACC_PRIVATE or Opcodes.ACC_STATIC, - "<clinit>", + CLASS_INITIALIZER_NAME, "()V", null, null diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt index 43ceec42660d..0761edc34916 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt @@ -372,7 +372,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithIn flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer super_class: #x // java/lang/Object - interfaces: 0, fields: 1, methods: 2, attributes: 3 + interfaces: 0, fields: 1, methods: 1, attributes: 3 public static boolean sInitialized; descriptor: Z flags: (0x0009) ACC_PUBLIC, ACC_STATIC @@ -387,17 +387,6 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithIn x: ldc #x // String Stub! x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V x: athrow - - static {}; - descriptor: ()V - flags: (0x0008) ACC_STATIC - Code: - stack=3, locals=0, args_size=0 - x: new #x // class java/lang/RuntimeException - x: dup - x: ldc #x // String Stub! - x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - x: athrow } SourceFile: "TinyFrameworkClassWithInitializer.java" RuntimeVisibleAnnotations: diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt index 43ceec42660d..0761edc34916 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt @@ -372,7 +372,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithIn flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer super_class: #x // java/lang/Object - interfaces: 0, fields: 1, methods: 2, attributes: 3 + interfaces: 0, fields: 1, methods: 1, attributes: 3 public static boolean sInitialized; descriptor: Z flags: (0x0009) ACC_PUBLIC, ACC_STATIC @@ -387,17 +387,6 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithIn x: ldc #x // String Stub! x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V x: athrow - - static {}; - descriptor: ()V - flags: (0x0008) ACC_STATIC - Code: - stack=3, locals=0, args_size=0 - x: new #x // class java/lang/RuntimeException - x: dup - x: ldc #x // String Stub! - x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V - x: athrow } SourceFile: "TinyFrameworkClassWithInitializer.java" RuntimeVisibleAnnotations: diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt index 079d2a84e498..8fcd2fbe7c84 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt @@ -15,3 +15,8 @@ class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy stub # Class load hook class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy ~com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded + + +class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithInitializer stubclass + # Testing 'throw' on a static initializer. This should be handled as 'keep'. + method <clinit> ()V throw 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 fd486468a1a7..722905f15b41 100755 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh @@ -16,6 +16,10 @@ 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 @@ -57,7 +61,7 @@ framework_compile_classpaths=( test_compile_classpaths=( $SOONG_INT/external/junit/junit/android_common/combined/junit.jar - $SOONG_INT/prebuilts/tools/common/m2/truth-prebuilt/android_common/combined/truth-prebuilt.jar + $ANDROID_BUILD_TOP/out/target/common/obj/JAVA_LIBRARIES/truth-prebuilt_intermediates/classes.jar ) test_runtime_classpaths=( diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer.java index 53cfdf6cd412..01a690b73ba0 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer.java +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer.java @@ -18,10 +18,14 @@ package com.android.hoststubgen.test.tinyframework; import android.hosttest.annotation.HostSideTestClassLoadHook; import android.hosttest.annotation.HostSideTestWholeClassStub; + +// Note, policy-override-tiny-framework.txt hss an override on this class. @HostSideTestClassLoadHook( "com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded") @HostSideTestWholeClassStub public class TinyFrameworkClassWithInitializer { + // Note, this method has a 'throw' in the policy file, which is handled as a 'keep' (because + // it's a static initializer), so this won't show up in the stub jar. static { sInitialized = true; } diff --git a/tools/hoststubgen/scripts/run-all-tests.sh b/tools/hoststubgen/scripts/run-all-tests.sh index 7600942c99e6..2e9cf428354a 100755 --- a/tools/hoststubgen/scripts/run-all-tests.sh +++ b/tools/hoststubgen/scripts/run-all-tests.sh @@ -33,7 +33,9 @@ run m run-ravenwood-test ${READY_TEST_MODULES[*]} ${NOT_READY_TEST_MODULES[*]} run ./hoststubgen/test-tiny-framework/diff-and-update-golden.sh run ./hoststubgen/test-framework/run-test-without-atest.sh -run ./hoststubgen/test-tiny-framework/run-test-manually.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 atest tiny-framework-dump-test run ./scripts/build-framework-hostside-jars-and-extract.sh |