diff options
54 files changed, 984 insertions, 503 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 0265431822e7..45f65709533f 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -199,6 +199,24 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +// Media +aconfig_declarations { + name: "android.media.playback.flags-aconfig", + package: "com.android.media.playback.flags", + srcs: ["media/jni/playback_flags.aconfig"], +} + +cc_aconfig_library { + name: "android.media.playback.flags-aconfig-cc", + aconfig_declarations: "android.media.playback.flags-aconfig", +} + +java_aconfig_library { + name: "android.media.playback.flags-aconfig-java", + aconfig_declarations: "android.media.playback.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + // VCN aconfig_declarations { name: "android.net.vcn.flags-aconfig", diff --git a/Android.bp b/Android.bp index 1d11014a6c54..1fb50b6c24a6 100644 --- a/Android.bp +++ b/Android.bp @@ -603,19 +603,6 @@ java_library { ], } -packages_to_document = [ - "android", - "dalvik", - "java", - "javax", - "junit", - "org.apache.http", - "org.json", - "org.w3c.dom", - "org.xml.sax", - "org.xmlpull", -] - filegroup { name: "android-non-updatable-stub-sources", srcs: [ @@ -630,198 +617,6 @@ filegroup { visibility: ["//frameworks/base/api"], } -// Defaults for all stubs that include the non-updatable framework. These defaults do not include -// module symbols, so will not compile correctly on their own. Users must add module APIs to the -// classpath (or sources) somehow. -stubs_defaults { - name: "android-non-updatable-stubs-defaults", - srcs: [":android-non-updatable-stub-sources"], - sdk_version: "none", - system_modules: "none", - java_version: "1.8", - arg_files: [":frameworks-base-core-AndroidManifest.xml"], - aidl: { - include_dirs: [ - "frameworks/av/aidl", - "frameworks/base/media/aidl", - "frameworks/base/telephony/java", - "frameworks/native/libs/permission/aidl", - "packages/modules/Bluetooth/framework/aidl-export", - "packages/modules/Connectivity/framework/aidl-export", - "packages/modules/Media/apex/aidl/stable", - "hardware/interfaces/biometrics/common/aidl", - "hardware/interfaces/biometrics/fingerprint/aidl", - "hardware/interfaces/graphics/common/aidl", - "hardware/interfaces/keymaster/aidl", - "system/hardware/interfaces/media/aidl", - ], - }, - // These are libs from framework-internal-utils that are required (i.e. being referenced) - // from framework-non-updatable-sources. Add more here when there's a need. - // DO NOT add the entire framework-internal-utils. It might cause unnecessary circular - // dependencies gets bigger. - libs: [ - "android.hardware.cas-V1.2-java", - "android.hardware.health-V1.0-java-constants", - "android.hardware.radio-V1.5-java", - "android.hardware.radio-V1.6-java", - "android.hardware.thermal-V1.0-java-constants", - "android.hardware.thermal-V2.0-java", - "android.hardware.tv.input-V1.0-java-constants", - "android.hardware.usb-V1.0-java-constants", - "android.hardware.usb-V1.1-java-constants", - "android.hardware.usb.gadget-V1.0-java", - "android.hardware.vibrator-V1.3-java", - "framework-protos", - ], - flags: [ - "--api-lint-ignore-prefix android.icu.", - "--api-lint-ignore-prefix java.", - "--api-lint-ignore-prefix junit.", - "--api-lint-ignore-prefix org.", - "--error NoSettingsProvider", - "--error UnhiddenSystemApi", - "--error UnflaggedApi", - "--force-convert-to-warning-nullability-annotations +*:-android.*:+android.icu.*:-dalvik.*", - "--hide BroadcastBehavior", - "--hide CallbackInterface", - "--hide DeprecationMismatch", - "--hide HiddenSuperclass", - "--hide MissingPermission", - "--hide RequiresPermission", - "--hide SdkConstant", - "--hide Todo", - "--hide-package android.audio.policy.configuration.V7_0", - "--hide-package com.android.server", - "--manifest $(location :frameworks-base-core-AndroidManifest.xml)", - ], - filter_packages: packages_to_document, - high_mem: true, // Lots of sources => high memory use, see b/170701554 - installable: false, - annotations_enabled: true, - previous_api: ":android.api.public.latest", - merge_annotations_dirs: ["metalava-manual"], - defaults_visibility: ["//frameworks/base/api"], - visibility: ["//frameworks/base/api"], -} - -// Defaults with module APIs in the classpath (mostly from prebuilts). -// Suitable for compiling android-non-updatable. -stubs_defaults { - name: "module-classpath-stubs-defaults", - aidl: { - include_dirs: [ - "packages/modules/Bluetooth/framework/aidl-export", - "packages/modules/Connectivity/framework/aidl-export", - "packages/modules/Media/apex/aidl/stable", - ], - }, - libs: [ - "art.module.public.api", - "sdk_module-lib_current_framework-tethering", - "sdk_module-lib_current_framework-connectivity-t", - "sdk_public_current_framework-bluetooth", - // There are a few classes from modules used by the core that - // need to be resolved by metalava. We use a prebuilt stub of the - // full sdk to ensure we can resolve them. If a new class gets added, - // the prebuilts/sdk/current needs to be updated. - "sdk_system_current_android", - // NOTE: The below can be removed once the prebuilt stub contains IKE. - "sdk_system_current_android.net.ipsec.ike", - ], -} - -// Defaults for the java_sdk_libraries of unbundled jars from framework. -// java_sdk_libraries using these defaults should also add themselves to the -// non_updatable_modules list in frameworks/base/api/api.go -java_defaults { - name: "framework-non-updatable-unbundled-defaults", - defaults: ["framework-non-updatable-lint-defaults"], - - sdk_version: "core_platform", - - // Api scope settings - public: { - enabled: true, - sdk_version: "module_current", - libs: ["android_module_lib_stubs_current"], - }, - system: { - enabled: true, - sdk_version: "module_current", - libs: ["android_module_lib_stubs_current"], - }, - module_lib: { - enabled: true, - sdk_version: "module_current", - libs: ["android_module_lib_stubs_current"], - }, - test: { - enabled: true, - sdk_version: "test_frameworks_core_current", - libs: ["android_test_frameworks_core_stubs_current"], - }, - - stub_only_libs: [ - "framework-protos", - ], - impl_only_libs: [ - "framework-minus-apex-headers", // full access to framework-minus-apex including hidden API - "framework-annotations-lib", - ], - visibility: ["//visibility:public"], - stubs_library_visibility: ["//visibility:public"], - stubs_source_visibility: ["//visibility:private"], - impl_library_visibility: [ - ":__pkg__", - "//frameworks/base", - "//frameworks/base/api", // For framework-all - ], - defaults_visibility: [ - "//frameworks/base/location", - ], - plugins: [ - "error_prone_android_framework", - ], - errorprone: { - javacflags: [ - "-Xep:AndroidFrameworkCompatChange:ERROR", - "-Xep:AndroidFrameworkUid:ERROR", - ], - }, - - // Include manual annotations in API txt files - merge_annotations_dirs: ["metalava-manual"], - - // Use the source of annotations that affect metalava doc generation, since - // the relevant generation instructions are themselves in javadoc, which is - // not present in class files. - api_srcs: [":framework-metalava-annotations"], - - // Framework modules are not generally shared libraries, i.e. they are not - // intended, and must not be allowed, to be used in a <uses-library> manifest - // entry. - shared_library: false, - - // Prevent dependencies that do not specify an sdk_version from accessing the - // implementation library by default and force them to use stubs instead. - default_to_stubs: true, - - // Subdirectory for the artifacts that are copied to the dist directory - dist_group: "android", - - droiddoc_options: [ - "--error UnhiddenSystemApi " + - "--hide CallbackInterface " + - "--hide HiddenTypedefConstant " + - "--hide RequiresPermission " + - "--enhance-documentation " + - "--hide-package com.android.server ", - ], - - annotations_enabled: true, -} - build = [ "AconfigFlags.bp", "ProtoLibraries.bp", diff --git a/BAL_OWNERS b/BAL_OWNERS new file mode 100644 index 000000000000..d56a1d4634df --- /dev/null +++ b/BAL_OWNERS @@ -0,0 +1,5 @@ +brufino@google.com +achim@google.com +topjohnwu@google.com +lus@google.com + diff --git a/api/Android.bp b/api/Android.bp index 6986ac09f89e..d2e0849ed946 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -262,6 +262,157 @@ java_genrule { cmd: "cat $(in) | md5sum | cut -d' ' -f1 > $(out)", } +packages_to_document = [ + "android", + "dalvik", + "java", + "javax", + "junit", + "org.apache.http", + "org.json", + "org.w3c.dom", + "org.xml.sax", + "org.xmlpull", +] + +// Defaults for all stubs that include the non-updatable framework. These defaults do not include +// module symbols, so will not compile correctly on their own. Users must add module APIs to the +// classpath (or sources) somehow. +stubs_defaults { + name: "android-non-updatable-stubs-defaults", + srcs: [":android-non-updatable-stub-sources"], + sdk_version: "none", + system_modules: "none", + java_version: "1.8", + arg_files: [":frameworks-base-core-AndroidManifest.xml"], + aidl: { + include_dirs: [ + "frameworks/av/aidl", + "frameworks/base/media/aidl", + "frameworks/base/telephony/java", + "frameworks/native/libs/permission/aidl", + "packages/modules/Bluetooth/framework/aidl-export", + "packages/modules/Connectivity/framework/aidl-export", + "packages/modules/Media/apex/aidl/stable", + "hardware/interfaces/biometrics/common/aidl", + "hardware/interfaces/biometrics/fingerprint/aidl", + "hardware/interfaces/graphics/common/aidl", + "hardware/interfaces/keymaster/aidl", + "system/hardware/interfaces/media/aidl", + ], + }, + // These are libs from framework-internal-utils that are required (i.e. being referenced) + // from framework-non-updatable-sources. Add more here when there's a need. + // DO NOT add the entire framework-internal-utils. It might cause unnecessary circular + // dependencies gets bigger. + libs: [ + "android.hardware.cas-V1.2-java", + "android.hardware.health-V1.0-java-constants", + "android.hardware.radio-V1.5-java", + "android.hardware.radio-V1.6-java", + "android.hardware.thermal-V1.0-java-constants", + "android.hardware.thermal-V2.0-java", + "android.hardware.tv.input-V1.0-java-constants", + "android.hardware.usb-V1.0-java-constants", + "android.hardware.usb-V1.1-java-constants", + "android.hardware.usb.gadget-V1.0-java", + "android.hardware.vibrator-V1.3-java", + "framework-protos", + ], + flags: [ + "--api-lint-ignore-prefix android.icu.", + "--api-lint-ignore-prefix java.", + "--api-lint-ignore-prefix junit.", + "--api-lint-ignore-prefix org.", + "--error NoSettingsProvider", + "--error UnhiddenSystemApi", + "--error UnflaggedApi", + "--force-convert-to-warning-nullability-annotations +*:-android.*:+android.icu.*:-dalvik.*", + "--hide BroadcastBehavior", + "--hide CallbackInterface", + "--hide DeprecationMismatch", + "--hide HiddenSuperclass", + "--hide MissingPermission", + "--hide RequiresPermission", + "--hide SdkConstant", + "--hide Todo", + "--hide-package android.audio.policy.configuration.V7_0", + "--hide-package com.android.server", + "--manifest $(location :frameworks-base-core-AndroidManifest.xml)", + ], + filter_packages: packages_to_document, + high_mem: true, // Lots of sources => high memory use, see b/170701554 + installable: false, + annotations_enabled: true, + previous_api: ":android.api.public.latest", + merge_annotations_dirs: ["metalava-manual"], + defaults_visibility: ["//frameworks/base/api"], + visibility: ["//frameworks/base/api"], +} + +// Defaults with module APIs in the classpath (mostly from prebuilts). +// Suitable for compiling android-non-updatable. +stubs_defaults { + name: "module-classpath-stubs-defaults", + aidl: { + include_dirs: [ + "packages/modules/Bluetooth/framework/aidl-export", + "packages/modules/Connectivity/framework/aidl-export", + "packages/modules/Media/apex/aidl/stable", + ], + }, + libs: [ + "art.module.public.api", + "sdk_module-lib_current_framework-tethering", + "sdk_module-lib_current_framework-connectivity-t", + "sdk_public_current_framework-bluetooth", + // There are a few classes from modules used by the core that + // need to be resolved by metalava. We use a prebuilt stub of the + // full sdk to ensure we can resolve them. If a new class gets added, + // the prebuilts/sdk/current needs to be updated. + "sdk_system_current_android", + // NOTE: The below can be removed once the prebuilt stub contains IKE. + "sdk_system_current_android.net.ipsec.ike", + ], +} + +// Defaults for the java_sdk_libraries of unbundled jars from framework. +// java_sdk_libraries using these defaults should also add themselves to the +// non_updatable_modules list in frameworks/base/api/api.go +java_defaults { + name: "framework-non-updatable-unbundled-defaults", + defaults: [ + "framework-non-updatable-lint-defaults", + "non-updatable-framework-module-defaults", + ], + public: { + libs: ["android_module_lib_stubs_current"], + }, + system: { + libs: ["android_module_lib_stubs_current"], + }, + module_lib: { + libs: ["android_module_lib_stubs_current"], + }, + test: { + libs: ["android_test_frameworks_core_stubs_current"], + }, + sdk_version: "core_platform", + stub_only_libs: ["framework-protos"], + impl_only_libs: ["framework-minus-apex-headers"], // the framework, including hidden API + impl_library_visibility: ["//frameworks/base"], + defaults_visibility: ["//frameworks/base/location"], + plugins: ["error_prone_android_framework"], + errorprone: { + javacflags: [ + "-Xep:AndroidFrameworkCompatChange:ERROR", + "-Xep:AndroidFrameworkUid:ERROR", + ], + }, + // Include manual annotations in API txt files + merge_annotations_dirs: ["metalava-manual"], +} + build = [ "ApiDocs.bp", "StubLibraries.bp", diff --git a/core/api/current.txt b/core/api/current.txt index 7cf7e19f865c..af58174ff9a2 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -18291,11 +18291,13 @@ package android.hardware.biometrics { public class BiometricManager { method @Deprecated @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public int canAuthenticate(); method @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public int canAuthenticate(int); + method @FlaggedApi("android.hardware.biometrics.last_authentication_time") @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public long getLastAuthenticationTime(int); method @NonNull @RequiresPermission(android.Manifest.permission.USE_BIOMETRIC) public android.hardware.biometrics.BiometricManager.Strings getStrings(int); field public static final int BIOMETRIC_ERROR_HW_UNAVAILABLE = 1; // 0x1 field public static final int BIOMETRIC_ERROR_NONE_ENROLLED = 11; // 0xb field public static final int BIOMETRIC_ERROR_NO_HARDWARE = 12; // 0xc field public static final int BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED = 15; // 0xf + field @FlaggedApi("android.hardware.biometrics.last_authentication_time") public static final long BIOMETRIC_NO_AUTHENTICATION = -1L; // 0xffffffffffffffffL field public static final int BIOMETRIC_SUCCESS = 0; // 0x0 } @@ -18341,6 +18343,7 @@ package android.hardware.biometrics { field public static final int BIOMETRIC_ERROR_UNABLE_TO_PROCESS = 2; // 0x2 field public static final int BIOMETRIC_ERROR_USER_CANCELED = 10; // 0xa field public static final int BIOMETRIC_ERROR_VENDOR = 8; // 0x8 + field @FlaggedApi("android.hardware.biometrics.last_authentication_time") public static final long BIOMETRIC_NO_AUTHENTICATION = -1L; // 0xffffffffffffffffL } public abstract static class BiometricPrompt.AuthenticationCallback { @@ -28960,12 +28963,12 @@ package android.net.vcn { method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder addExposedCapability(int); method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder addGatewayOption(int); method @NonNull public android.net.vcn.VcnGatewayConnectionConfig build(); - method @FlaggedApi("android.net.vcn.safe_mode_config") @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder enableSafeMode(boolean); method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder removeExposedCapability(int); method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder removeGatewayOption(int); method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setMaxMtu(@IntRange(from=0x500) int); method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setMinUdpPort4500NatTimeoutSeconds(@IntRange(from=0x78) int); method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setRetryIntervalsMillis(@NonNull long[]); + method @FlaggedApi("android.net.vcn.safe_mode_config") @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setSafeModeEnabled(boolean); method @NonNull public android.net.vcn.VcnGatewayConnectionConfig.Builder setVcnUnderlyingNetworkPriorities(@NonNull java.util.List<android.net.vcn.VcnUnderlyingNetworkTemplate>); } diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS index e1c45d98e678..164bdbe9f516 100644 --- a/core/java/android/app/OWNERS +++ b/core/java/android/app/OWNERS @@ -36,6 +36,7 @@ per-file GameMode* = file:/GAME_MANAGER_OWNERS per-file GameState* = file:/GAME_MANAGER_OWNERS per-file IGameManager* = file:/GAME_MANAGER_OWNERS per-file IGameMode* = file:/GAME_MANAGER_OWNERS +per-file BackgroundStartPrivileges.java = file:/BAL_OWNERS # ActivityThread per-file ActivityThread.java = file:/services/core/java/com/android/server/am/OWNERS diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index c3fd744469e9..0fd0e1531771 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -1161,6 +1161,11 @@ public class Intent implements Parcelable, Cloneable { * numbers. Applications can <strong>dial</strong> emergency numbers using * {@link #ACTION_DIAL}, however. * + * <p>Note: This Intent can only be used to dial call forwarding MMI codes if the application + * using this intent is set as the default or system dialer. The system will treat any other + * application using this Intent for the purpose of dialing call forwarding MMI codes as if the + * {@link #ACTION_DIAL} Intent was used instead. + * * <p>Note: An app filling the {@link android.app.role.RoleManager#ROLE_DIALER} role should use * {@link android.telecom.TelecomManager#placeCall(Uri, Bundle)} to place calls rather than * relying on this intent. diff --git a/core/java/android/hardware/biometrics/BiometricConstants.java b/core/java/android/hardware/biometrics/BiometricConstants.java index 943eee461809..61d87026b6e9 100644 --- a/core/java/android/hardware/biometrics/BiometricConstants.java +++ b/core/java/android/hardware/biometrics/BiometricConstants.java @@ -18,6 +18,7 @@ package android.hardware.biometrics; import static android.hardware.biometrics.BiometricManager.Authenticators; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; @@ -296,4 +297,15 @@ public interface BiometricConstants { @Retention(RetentionPolicy.SOURCE) @IntDef({BIOMETRIC_LOCKOUT_NONE, BIOMETRIC_LOCKOUT_TIMED, BIOMETRIC_LOCKOUT_PERMANENT}) @interface LockoutMode {} + + // + // Other miscellaneous constants + // + + /** + * Returned from {@link BiometricManager#getLastAuthenticationTime(int)} when there has + * been no successful authentication for the given authenticator since boot. + */ + @FlaggedApi(Flags.FLAG_LAST_AUTHENTICATION_TIME) + long BIOMETRIC_NO_AUTHENTICATION = -1; } diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java index 82694ee3463b..90bbca8336e1 100644 --- a/core/java/android/hardware/biometrics/BiometricManager.java +++ b/core/java/android/hardware/biometrics/BiometricManager.java @@ -23,6 +23,8 @@ import static android.Manifest.permission.WRITE_DEVICE_CONFIG; import static com.android.internal.util.FrameworkStatsLog.AUTH_DEPRECATED_APIUSED__DEPRECATED_API__API_BIOMETRIC_MANAGER_CAN_AUTHENTICATE; +import android.annotation.ElapsedRealtimeLong; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -30,6 +32,7 @@ import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; +import android.app.KeyguardManager; import android.content.Context; import android.os.IBinder; import android.os.RemoteException; @@ -86,6 +89,17 @@ public class BiometricManager { BiometricConstants.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED; /** + * Returned from {@link BiometricManager#getLastAuthenticationTime(int)} when no matching + * successful authentication has been performed since boot. + */ + @FlaggedApi(Flags.FLAG_LAST_AUTHENTICATION_TIME) + public static final long BIOMETRIC_NO_AUTHENTICATION = + BiometricConstants.BIOMETRIC_NO_AUTHENTICATION; + + private static final int GET_LAST_AUTH_TIME_ALLOWED_AUTHENTICATORS = + Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_STRONG; + + /** * @hide */ @IntDef({BIOMETRIC_SUCCESS, @@ -637,5 +651,58 @@ public class BiometricManager { } } + + /** + * Gets the last time the user successfully authenticated using one of the given authenticators. + * The returned value is time in + * {@link android.os.SystemClock#elapsedRealtime SystemClock.elapsedRealtime()} (time since + * boot, including sleep). + * <p> + * {@link BiometricManager#BIOMETRIC_NO_AUTHENTICATION} is returned in the case where there + * has been no successful authentication using any of the given authenticators since boot. + * <p> + * Currently, only {@link Authenticators#DEVICE_CREDENTIAL} and + * {@link Authenticators#BIOMETRIC_STRONG} are accepted. {@link IllegalArgumentException} will + * be thrown if {@code authenticators} contains other authenticator types. + * <p> + * Note that this may return successful authentication times even if the device is currently + * locked. You may use {@link KeyguardManager#isDeviceLocked()} to determine if the device + * is unlocked or not. Additionally, this method may return valid times for an authentication + * method that is no longer available. For instance, if the user unlocked the device with a + * {@link Authenticators#BIOMETRIC_STRONG} authenticator but then deleted that authenticator + * (e.g., fingerprint data), this method will still return the time of that unlock for + * {@link Authenticators#BIOMETRIC_STRONG} if it is the most recent successful event. The caveat + * is that {@link BiometricManager#BIOMETRIC_NO_AUTHENTICATION} will be returned if the device + * no longer has a secure lock screen at all, even if there were successful authentications + * performed before the lock screen was made insecure. + * + * @param authenticators bit field consisting of constants defined in {@link Authenticators}. + * @return the time of last authentication or + * {@link BiometricManager#BIOMETRIC_NO_AUTHENTICATION} + * @throws IllegalArgumentException if {@code authenticators} contains values other than + * {@link Authenticators#DEVICE_CREDENTIAL} and {@link Authenticators#BIOMETRIC_STRONG} or is + * 0. + */ + @RequiresPermission(USE_BIOMETRIC) + @ElapsedRealtimeLong + @FlaggedApi(Flags.FLAG_LAST_AUTHENTICATION_TIME) + public long getLastAuthenticationTime( + @BiometricManager.Authenticators.Types int authenticators) { + if (authenticators == 0 + || (GET_LAST_AUTH_TIME_ALLOWED_AUTHENTICATORS & authenticators) != authenticators) { + throw new IllegalArgumentException( + "Only BIOMETRIC_STRONG and DEVICE_CREDENTIAL authenticators may be used."); + } + + if (mService != null) { + try { + return mService.getLastAuthenticationTime(UserHandle.myUserId(), authenticators); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } else { + return BIOMETRIC_NO_AUTHENTICATION; + } + } } diff --git a/core/java/android/hardware/biometrics/IAuthService.aidl b/core/java/android/hardware/biometrics/IAuthService.aidl index c2e5c0b6d519..5bdbe2b573b2 100644 --- a/core/java/android/hardware/biometrics/IAuthService.aidl +++ b/core/java/android/hardware/biometrics/IAuthService.aidl @@ -57,6 +57,9 @@ interface IAuthService { // Checks if biometrics can be used. int canAuthenticate(String opPackageName, int userId, int authenticators); + // Gets the time of last authentication for the given user and authenticators. + long getLastAuthenticationTime(int userId, int authenticators); + // Checks if any biometrics are enrolled. boolean hasEnrolledBiometrics(int userId, String opPackageName); diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl index 18c8d1bd3a1e..058f302af62b 100644 --- a/core/java/android/hardware/biometrics/IBiometricService.aidl +++ b/core/java/android/hardware/biometrics/IBiometricService.aidl @@ -53,6 +53,10 @@ interface IBiometricService { @EnforcePermission("USE_BIOMETRIC_INTERNAL") int canAuthenticate(String opPackageName, int userId, int callingUserId, int authenticators); + // Gets the time of last authentication for the given user and authenticators. + @EnforcePermission("USE_BIOMETRIC_INTERNAL") + long getLastAuthenticationTime(int userId, int authenticators); + // Checks if any biometrics are enrolled. @EnforcePermission("USE_BIOMETRIC_INTERNAL") boolean hasEnrolledBiometrics(int userId, String opPackageName); diff --git a/core/java/android/hardware/biometrics/flags.aconfig b/core/java/android/hardware/biometrics/flags.aconfig index 0924e0d12bd3..c370ad658c3b 100644 --- a/core/java/android/hardware/biometrics/flags.aconfig +++ b/core/java/android/hardware/biometrics/flags.aconfig @@ -1,6 +1,13 @@ package: "android.hardware.biometrics" flag { + name: "last_authentication_time" + namespace: "wallet_integration" + description: "Feature flag for adding getLastAuthenticationTime API to BiometricManager" + bug: "301979982" +} + +flag { name: "add_key_agreement_crypto_object" namespace: "biometrics" description: "Feature flag for adding KeyAgreement api to CryptoObject." diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java index 779a8db433a8..6f11d3ae661c 100644 --- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java +++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java @@ -493,7 +493,7 @@ public final class VcnGatewayConnectionConfig { /** * Check whether safe mode is enabled * - * @see Builder#enableSafeMode(boolean) + * @see Builder#setSafeModeEnabled(boolean) */ @FlaggedApi(FLAG_SAFE_MODE_CONFIG) public boolean isSafeModeEnabled() { @@ -815,8 +815,8 @@ public final class VcnGatewayConnectionConfig { * * <p>If a VCN fails to provide connectivity within a system-provided timeout, it will enter * safe mode. In safe mode, the VCN Network will be torn down and the system will restore - * connectivity by allowing underlying cellular networks to be used as default. At the same - * time, VCN will continue to retry until it succeeds. + * connectivity by allowing underlying cellular or WiFi networks to be used as default. At + * the same time, VCN will continue to retry until it succeeds. * * <p>When safe mode is disabled and VCN connection fails to provide connectivity, end users * might not have connectivity, and may not have access to carrier-owned underlying @@ -826,7 +826,7 @@ public final class VcnGatewayConnectionConfig { */ @FlaggedApi(FLAG_SAFE_MODE_CONFIG) @NonNull - public Builder enableSafeMode(boolean enabled) { + public Builder setSafeModeEnabled(boolean enabled) { mIsSafeModeDisabled = !enabled; return this; } diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java index 6a4ec9b7605a..25fba60b9bb5 100644 --- a/core/java/android/os/BatteryManager.java +++ b/core/java/android/os/BatteryManager.java @@ -21,6 +21,7 @@ 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.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; @@ -236,6 +237,7 @@ public class BatteryManager { OsProtoEnums.CHARGING_POLICY_ADAPTIVE_LONGLIFE; // = 4 /** @hide */ + @SuppressLint("UnflaggedApi") // TestApi without associated feature. @TestApi public static final int BATTERY_PLUGGED_ANY = BATTERY_PLUGGED_AC | BATTERY_PLUGGED_USB | BATTERY_PLUGGED_WIRELESS diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java index a07735e7540e..8fcff78fb025 100644 --- a/core/java/android/os/GraphicsEnvironment.java +++ b/core/java/android/os/GraphicsEnvironment.java @@ -117,8 +117,6 @@ public class GraphicsEnvironment { private static final String ANGLE_GL_DRIVER_CHOICE_NATIVE = "native"; private static final String SYSTEM_ANGLE_STRING = "system"; - private static final String PROPERTY_RO_ANGLE_SUPPORTED = "ro.gfx.angle.supported"; - private ClassLoader mClassLoader; private String mLibrarySearchPaths; private String mLibraryPermittedPaths; @@ -620,8 +618,7 @@ public class GraphicsEnvironment { } /** - * Attempt to set up ANGLE from system, if the apk can be found, pass ANGLE details to - * the C++ GraphicsEnv class. + * Set up ANGLE from system. * * @param context - Context of the application. * @param bundle - Bundle of the application. @@ -630,14 +627,8 @@ public class GraphicsEnvironment { * false: can not set up to use system ANGLE because it doesn't exist. */ private boolean setupAngleFromSystem(Context context, Bundle bundle, String packageName) { - final boolean systemAngleSupported = SystemProperties - .getBoolean(PROPERTY_RO_ANGLE_SUPPORTED, false); - if (!systemAngleSupported) { - return false; - } - - // If we make it to here, system ANGLE will be used. Call nativeSetAngleInfo() with - // the application package name and ANGLE features to use. + // System ANGLE always exists, call nativeSetAngleInfo() with the application package + // name and ANGLE features to use. final String[] features = getAngleEglFeatures(context, bundle); nativeSetAngleInfo(SYSTEM_ANGLE_STRING, false, packageName, features); return true; diff --git a/core/java/android/security/OWNERS b/core/java/android/security/OWNERS index 96c0be7f803e..33a67aed6023 100644 --- a/core/java/android/security/OWNERS +++ b/core/java/android/security/OWNERS @@ -8,3 +8,4 @@ per-file *NetworkSecurityPolicy.java = file:net/OWNERS per-file Confirmation*.java = file:/keystore/OWNERS per-file FileIntegrityManager.java = file:platform/system/security:/fsverity/OWNERS per-file IFileIntegrityService.aidl = file:platform/system/security:/fsverity/OWNERS +per-file *.aconfig = victorhsieh@google.com diff --git a/core/java/android/window/flags/OWNERS b/core/java/android/window/flags/OWNERS new file mode 100644 index 000000000000..fa81ee3905c3 --- /dev/null +++ b/core/java/android/window/flags/OWNERS @@ -0,0 +1 @@ +per-file responsible_apis.aconfig = file:/BAL_OWNERS
\ No newline at end of file diff --git a/core/tests/coretests/src/android/app/OWNERS b/core/tests/coretests/src/android/app/OWNERS index 64f5e6c68a95..5636f9bc436c 100644 --- a/core/tests/coretests/src/android/app/OWNERS +++ b/core/tests/coretests/src/android/app/OWNERS @@ -10,3 +10,7 @@ per-file *UiAutomation* = file:/services/accessibility/OWNERS # KeyguardManagerTest per-file KeyguardManagerTest.java = file:/services/core/java/com/android/server/locksettings/OWNERS + +# Files related to background activity launches +per-file Background*Start* = file:/BAL_OWNERS + diff --git a/keystore/java/android/security/Authorization.java b/keystore/java/android/security/Authorization.java index 2d2dd24763c4..b4b3e9275035 100644 --- a/keystore/java/android/security/Authorization.java +++ b/keystore/java/android/security/Authorization.java @@ -18,7 +18,9 @@ package android.security; import android.annotation.NonNull; import android.annotation.Nullable; +import android.hardware.biometrics.BiometricConstants; import android.hardware.security.keymint.HardwareAuthToken; +import android.hardware.security.keymint.HardwareAuthenticatorType; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceSpecificException; @@ -37,7 +39,10 @@ public class Authorization { public static final int SYSTEM_ERROR = ResponseCode.SYSTEM_ERROR; - private static IKeystoreAuthorization getService() { + /** + * @return an instance of IKeystoreAuthorization + */ + public static IKeystoreAuthorization getService() { return IKeystoreAuthorization.Stub.asInterface( ServiceManager.checkService("android.security.authorization")); } @@ -100,4 +105,24 @@ public class Authorization { } } + /** + * Gets the last authentication time of the given user and authenticators. + * + * @param userId user id + * @param authenticatorTypes an array of {@link HardwareAuthenticatorType}. + * @return the last authentication time or + * {@link BiometricConstants#BIOMETRIC_NO_AUTHENTICATION}. + */ + public static long getLastAuthenticationTime( + long userId, @HardwareAuthenticatorType int[] authenticatorTypes) { + try { + return getService().getLastAuthTime(userId, authenticatorTypes); + } catch (RemoteException | NullPointerException e) { + Log.w(TAG, "Can not connect to keystore", e); + return BiometricConstants.BIOMETRIC_NO_AUTHENTICATION; + } catch (ServiceSpecificException e) { + return BiometricConstants.BIOMETRIC_NO_AUTHENTICATION; + } + } + } diff --git a/keystore/java/android/security/GateKeeper.java b/keystore/java/android/security/GateKeeper.java index af188a95c929..464714fe2895 100644 --- a/keystore/java/android/security/GateKeeper.java +++ b/keystore/java/android/security/GateKeeper.java @@ -45,8 +45,19 @@ public abstract class GateKeeper { @UnsupportedAppUsage public static long getSecureUserId() throws IllegalStateException { + return getSecureUserId(UserHandle.myUserId()); + } + + /** + * Return the secure user id for a given user id + * @param userId the user id, e.g. 0 + * @return the secure user id or {@link GateKeeper#INVALID_SECURE_USER_ID} if no such mapping + * for the given user id is found. + * @throws IllegalStateException if there is an error retrieving the secure user id + */ + public static long getSecureUserId(int userId) throws IllegalStateException { try { - return getService().getSecureUserId(UserHandle.myUserId()); + return getService().getSecureUserId(userId); } catch (RemoteException e) { throw new IllegalStateException( "Failed to obtain secure user ID from gatekeeper", e); diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java index 1ba41b106f56..b7140355e489 100644 --- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java @@ -1596,6 +1596,8 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu * {@link #getAttestationChallenge()} returns non-null and the spec is used to generate a * symmetric (AES or HMAC) key, {@link javax.crypto.KeyGenerator#generateKey()} will throw * {@link java.security.InvalidAlgorithmParameterException}. + * + * <p>The challenge may be up to 128 bytes. */ @NonNull public Builder setAttestationChallenge(byte[] attestationChallenge) { diff --git a/media/jni/Android.bp b/media/jni/Android.bp index ed1072cf409f..6031ef70535d 100644 --- a/media/jni/Android.bp +++ b/media/jni/Android.bp @@ -81,6 +81,7 @@ cc_library_shared { "libhidlallocatorutils", "libhidlbase", "libsonivox", + "server_configurable_flags", "android.hardware.cas@1.0", "android.hardware.cas.native@1.0", "android.hardware.drm@1.3", @@ -99,6 +100,7 @@ cc_library_shared { static_libs: [ "libgrallocusage", "libmedia_midiiowrapper", + "android.media.playback.flags-aconfig-cc", ], include_dirs: [ diff --git a/media/jni/android_media_MediaMetadataRetriever.cpp b/media/jni/android_media_MediaMetadataRetriever.cpp index 14587589372b..2a10fa7957bf 100644 --- a/media/jni/android_media_MediaMetadataRetriever.cpp +++ b/media/jni/android_media_MediaMetadataRetriever.cpp @@ -35,7 +35,9 @@ #include "android_media_MediaDataSource.h" #include "android_media_Streams.h" #include "android_util_Binder.h" +#include <com_android_media_playback_flags.h> +namespace playback_flags = com::android::media::playback::flags; using namespace android; struct fields_t { @@ -374,9 +376,12 @@ static jobject android_media_MediaMetadataRetriever_getFrameAtTime( jniThrowException(env, "java/lang/IllegalStateException", "No retriever available"); return NULL; } - // For getFrameAtTime family of calls, default to ANDROID_BITMAP_FORMAT_RGB_565 - // to keep the behavior consistent with older releases - AndroidBitmapFormat colorFormat = getColorFormat(env, params, ANDROID_BITMAP_FORMAT_RGB_565); + + AndroidBitmapFormat defaultColorFormat = + playback_flags::mediametadataretriever_default_rgba8888() + ? ANDROID_BITMAP_FORMAT_RGBA_8888 + : ANDROID_BITMAP_FORMAT_RGB_565; + AndroidBitmapFormat colorFormat = getColorFormat(env, params, defaultColorFormat); // Call native method to retrieve a video frame VideoFrame *videoFrame = NULL; diff --git a/media/jni/playback_flags.aconfig b/media/jni/playback_flags.aconfig new file mode 100644 index 000000000000..2bb0ec5375fd --- /dev/null +++ b/media/jni/playback_flags.aconfig @@ -0,0 +1,8 @@ +package: "com.android.media.playback.flags" + +flag { + name: "mediametadataretriever_default_rgba8888" + namespace: "media_solutions" + description: "Change MediaMetadataRetriever to use RGBA8888 for bitmap handling by default." + bug: "298965955" +} diff --git a/media/tests/MediaFrameworkTest/Android.bp b/media/tests/MediaFrameworkTest/Android.bp index bdd7afeb2f65..7a329bccf940 100644 --- a/media/tests/MediaFrameworkTest/Android.bp +++ b/media/tests/MediaFrameworkTest/Android.bp @@ -20,6 +20,8 @@ android_test { "androidx.test.ext.junit", "androidx.test.rules", "android-ex-camera2", + "android.media.playback.flags-aconfig-java", + "flag-junit", "testables", "testng", "truth", diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java index f70d2d1f8ae7..e3d389737bb0 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java @@ -16,19 +16,27 @@ package com.android.mediaframeworktest.unit; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import android.graphics.Bitmap; import android.media.MediaMetadataRetriever; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.test.suitebuilder.annotation.LargeTest; import android.test.suitebuilder.annotation.MediumTest; import android.util.Log; import androidx.test.runner.AndroidJUnit4; +import com.android.media.playback.flags.Flags; import com.android.mediaframeworktest.MediaNames; import com.android.mediaframeworktest.MediaProfileReader; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -40,6 +48,9 @@ public class MediaMetadataRetrieverTest { private static final String TAG = "MediaMetadataRetrieverTest"; + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + // Test album art extraction. @MediumTest @Test @@ -284,6 +295,34 @@ public class MediaMetadataRetrieverTest { assertTrue(!hasFailed); } + /** Test the thumbnail is generated when the default is set to RGBA8888 */ + @MediumTest + // TODO(b/305160754) Remove the following annotation and use SetFlagsRule.enableFlags + @RequiresFlagsEnabled(Flags.FLAG_MEDIAMETADATARETRIEVER_DEFAULT_RGBA8888) + @Test + public void testGetFrameAtTimeWithRGBA8888Flag_Set() throws IOException { + try (MediaMetadataRetriever retriever = new MediaMetadataRetriever()) { + retriever.setDataSource(MediaNames.TEST_PATH_1); + Bitmap bitmap = retriever.getFrameAtTime(-1); + assertNotNull(bitmap); + assertEquals(Bitmap.Config.ARGB_8888, bitmap.getConfig()); + } + } + + /** Test the thumbnail is generated when the default is not set to RGBA8888 */ + @MediumTest + // TODO(b/305160754) Remove the following annotation and use SetFlagsRule.disableFlags + @RequiresFlagsDisabled(Flags.FLAG_MEDIAMETADATARETRIEVER_DEFAULT_RGBA8888) + @Test + public void testGetFrameAtTimeWithRGBA8888Flag_Unset() throws IOException { + try (MediaMetadataRetriever retriever = new MediaMetadataRetriever()) { + retriever.setDataSource(MediaNames.TEST_PATH_1); + Bitmap bitmap = retriever.getFrameAtTime(-1); + assertNotNull(bitmap); + assertEquals(Bitmap.Config.RGB_565, bitmap.getConfig()); + } + } + // TODO: // Encode and test for the correct mix of metadata elements on a per-file basis? // We should be able to compare the actual returned metadata with the expected metadata diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index 2e45da3c8d42..d56448d43e43 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -1174,7 +1174,7 @@ final class ActivityManagerShellCommand extends ShellCommand { synchronized (mInternal.mOomAdjuster.mCachedAppOptimizer.mFreezerLock) { app.mOptRecord.setFreezeSticky(isSticky); mInternal.mOomAdjuster.mCachedAppOptimizer.unfreezeAppInternalLSP(app, 0, - false); + true); } } } diff --git a/services/core/java/com/android/server/am/LmkdStatsReporter.java b/services/core/java/com/android/server/am/LmkdStatsReporter.java index 4380b42ee54c..1e4dd648cab5 100644 --- a/services/core/java/com/android/server/am/LmkdStatsReporter.java +++ b/services/core/java/com/android/server/am/LmkdStatsReporter.java @@ -44,6 +44,7 @@ public final class LmkdStatsReporter { private static final int DIRECT_RECL_AND_THRASHING = 5; private static final int LOW_MEM_AND_SWAP_UTIL = 6; private static final int LOW_FILECACHE_AFTER_THRASHING = 7; + private static final int LOW_MEM = 8; /** * Processes the LMK_KILL_OCCURRED packet data diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java index 4538cad513d6..0629e6373b6e 100644 --- a/services/core/java/com/android/server/biometrics/AuthService.java +++ b/services/core/java/com/android/server/biometrics/AuthService.java @@ -39,6 +39,7 @@ import android.content.pm.PackageManager; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.ComponentInfoInternal; +import android.hardware.biometrics.Flags; import android.hardware.biometrics.IAuthService; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; import android.hardware.biometrics.IBiometricService; @@ -333,6 +334,33 @@ public class AuthService extends SystemService { } @Override + public long getLastAuthenticationTime(int userId, + @Authenticators.Types int authenticators) throws RemoteException { + // Only allow internal clients to call getLastAuthenticationTime with a different + // userId. + final int callingUserId = UserHandle.getCallingUserId(); + + if (userId != callingUserId) { + checkInternalPermission(); + } else { + checkPermission(); + } + + final long identity = Binder.clearCallingIdentity(); + try { + // We can't do this above because we need the READ_DEVICE_CONFIG permission, which + // the calling user may not possess. + if (!Flags.lastAuthenticationTime()) { + throw new UnsupportedOperationException(); + } + + return mBiometricService.getLastAuthenticationTime(userId, authenticators); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override public boolean hasEnrolledBiometrics(int userId, String opPackageName) 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 e8ffe4feb458..9c454ae3b867 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -18,6 +18,7 @@ package com.android.server.biometrics; import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL; import static android.hardware.biometrics.BiometricManager.Authenticators; +import static android.hardware.biometrics.BiometricManager.BIOMETRIC_NO_AUTHENTICATION; import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_IDLE; @@ -37,6 +38,7 @@ import android.hardware.SensorPrivacyManager; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricPrompt; +import android.hardware.biometrics.Flags; import android.hardware.biometrics.IBiometricAuthenticator; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; import android.hardware.biometrics.IBiometricSensorReceiver; @@ -51,6 +53,7 @@ import android.hardware.biometrics.SensorPropertiesInternal; import android.hardware.camera2.CameraManager; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; +import android.hardware.security.keymint.HardwareAuthenticatorType; import android.net.Uri; import android.os.Binder; import android.os.Build; @@ -60,10 +63,16 @@ import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.ServiceSpecificException; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; +import android.security.Authorization; +import android.security.GateKeeper; import android.security.KeyStore; +import android.security.authorization.IKeystoreAuthorization; +import android.security.authorization.ResponseCode; +import android.service.gatekeeper.IGateKeeperService; import android.text.TextUtils; import android.util.ArraySet; import android.util.Pair; @@ -77,6 +86,7 @@ import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.DumpUtils; import com.android.server.SystemService; import com.android.server.biometrics.log.BiometricContext; +import com.android.server.utils.Slogf; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -114,6 +124,10 @@ public class BiometricService extends SystemService { KeyStore mKeyStore; @VisibleForTesting ITrustManager mTrustManager; + @VisibleForTesting + IKeystoreAuthorization mKeystoreAuthorization; + @VisibleForTesting + IGateKeeperService mGateKeeper; // Get and cache the available biometric authenticators and their associated info. final ArrayList<BiometricSensor> mSensors = new ArrayList<>(); @@ -630,6 +644,64 @@ public class BiometricService extends SystemService { } @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) + @Override // Binder call + public long getLastAuthenticationTime( + int userId, @Authenticators.Types int authenticators) { + super.getLastAuthenticationTime_enforcePermission(); + + if (!Flags.lastAuthenticationTime()) { + throw new UnsupportedOperationException(); + } + + Slogf.d(TAG, "getLastAuthenticationTime(userId=%d, authenticators=0x%x)", + userId, authenticators); + + final long secureUserId; + try { + secureUserId = mGateKeeper.getSecureUserId(userId); + } catch (RemoteException e) { + Slogf.w(TAG, "Failed to get secure user id for " + userId, e); + return BIOMETRIC_NO_AUTHENTICATION; + } + + if (secureUserId == GateKeeper.INVALID_SECURE_USER_ID) { + Slogf.w(TAG, "No secure user id for " + userId); + return BIOMETRIC_NO_AUTHENTICATION; + } + + ArrayList<Integer> hardwareAuthenticators = new ArrayList<>(2); + + if ((authenticators & Authenticators.DEVICE_CREDENTIAL) != 0) { + hardwareAuthenticators.add(HardwareAuthenticatorType.PASSWORD); + } + + if ((authenticators & Authenticators.BIOMETRIC_STRONG) != 0) { + hardwareAuthenticators.add(HardwareAuthenticatorType.FINGERPRINT); + } + + if (hardwareAuthenticators.isEmpty()) { + throw new IllegalArgumentException("authenticators must not be empty"); + } + + int[] authTypesArray = hardwareAuthenticators.stream() + .mapToInt(Integer::intValue) + .toArray(); + try { + return mKeystoreAuthorization.getLastAuthTime(secureUserId, authTypesArray); + } catch (RemoteException e) { + Slog.w(TAG, "Error getting last auth time: " + e); + return BiometricConstants.BIOMETRIC_NO_AUTHENTICATION; + } catch (ServiceSpecificException e) { + // This is returned when the feature flag test fails in keystore2 + if (e.errorCode == ResponseCode.PERMISSION_DENIED) { + throw new UnsupportedOperationException(); + } + + return BiometricConstants.BIOMETRIC_NO_AUTHENTICATION; + } + } + + @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override public boolean hasEnrolledBiometrics(int userId, String opPackageName) { @@ -951,6 +1023,14 @@ public class BiometricService extends SystemService { return ActivityManager.getService(); } + public IKeystoreAuthorization getKeystoreAuthorizationService() { + return Authorization.getService(); + } + + public IGateKeeperService getGateKeeperService() { + return GateKeeper.getService(); + } + public ITrustManager getTrustManager() { return ITrustManager.Stub.asInterface(ServiceManager.getService(Context.TRUST_SERVICE)); } @@ -1064,6 +1144,8 @@ public class BiometricService extends SystemService { mBiometricContext = injector.getBiometricContext(context); mUserManager = injector.getUserManager(context); mBiometricCameraManager = injector.getBiometricCameraManager(context); + mKeystoreAuthorization = injector.getKeystoreAuthorizationService(); + mGateKeeper = injector.getGateKeeperService(); try { injector.getActivityManagerService().registerUserSwitchObserver( diff --git a/services/core/java/com/android/server/wm/OWNERS b/services/core/java/com/android/server/wm/OWNERS index f8c39d0906a0..cd704478aa83 100644 --- a/services/core/java/com/android/server/wm/OWNERS +++ b/services/core/java/com/android/server/wm/OWNERS @@ -18,5 +18,8 @@ rgl@google.com yunfanc@google.com wilsonshih@google.com -per-file BackgroundActivityStartController.java = set noparent -per-file BackgroundActivityStartController.java = brufino@google.com, topjohnwu@google.com, achim@google.com, ogunwale@google.com, louischang@google.com, lus@google.com +# Files related to background activity launches +per-file Background*Start* = set noparent +per-file Background*Start* = file:/BAL_OWNERS +per-file Background*Start* = ogunwale@google.com, louischang@google.com + diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 9a0e47de7873..eba9bf669d47 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -1637,8 +1637,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent> return false; } - if (!StorageManager.isUserKeyUnlocked(mCurrentUser)) { - // Can't launch home on secondary display areas if device is still locked. + if (!StorageManager.isCeStorageUnlocked(mCurrentUser)) { + // Can't launch home on secondary display areas if CE storage is still locked. return false; } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 3063d46208c2..46ca4455f93c 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -6510,11 +6510,11 @@ class Task extends TaskFragment { mActivityType = ACTIVITY_TYPE_STANDARD; } - if (mActivityType != ACTIVITY_TYPE_STANDARD + if (!DisplayContent.alwaysCreateRootTask(tda.getWindowingMode(), mActivityType) && mActivityType != ACTIVITY_TYPE_UNDEFINED) { - // For now there can be only one root task of a particular non-standard activity - // type on a display. So, get that ignoring whatever windowing mode it is - // currently in. + // Only Recents or Standard activity types are allowed to have more than one + // root task on a display, this is independent of whatever windowing mode it + // is currently in. Task rootTask = tda.getRootTask(WINDOWING_MODE_UNDEFINED, mActivityType); if (rootTask != null) { throw new IllegalArgumentException("Root task=" + rootTask + " of activityType=" diff --git a/services/midi/OWNERS b/services/midi/OWNERS index f4d51f91b51b..683cae1b0f3a 100644 --- a/services/midi/OWNERS +++ b/services/midi/OWNERS @@ -1 +1,3 @@ philburk@google.com +robertwu@google.com +elaurent@google.com #{LAST_RESORT_SUGGESTION} diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index f770f8c4571e..e656cf30e8e8 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -65,6 +65,7 @@ android_test { "ActivityContext", "coretests-aidl", "securebox", + "flag-junit", ], libs: [ diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java index f88afe7839c3..a78f2dcf2ab2 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java @@ -41,6 +41,8 @@ import android.app.AppOpsManager; import android.content.Context; import android.content.pm.PackageManager; import android.content.res.Resources; +import android.hardware.biometrics.BiometricManager; +import android.hardware.biometrics.Flags; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; import android.hardware.biometrics.IBiometricService; import android.hardware.biometrics.IBiometricServiceReceiver; @@ -53,6 +55,7 @@ import android.hardware.iris.IIrisService; import android.os.Binder; import android.os.UserHandle; import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.SetFlagsRule; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; @@ -84,6 +87,8 @@ public class AuthServiceTest { @Rule public MockitoRule mockitorule = MockitoJUnit.rule(); + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Mock private Context mContext; @Mock @@ -418,6 +423,37 @@ public class AuthServiceTest { eq(callback)); } + @Test(expected = UnsupportedOperationException.class) + public void testGetLastAuthenticationTime_flaggedOff_throwsUnsupportedOperationException() + throws Exception { + mSetFlagsRule.disableFlags(Flags.FLAG_LAST_AUTHENTICATION_TIME); + setInternalAndTestBiometricPermissions(mContext, true /* hasPermission */); + + mAuthService = new AuthService(mContext, mInjector); + mAuthService.onStart(); + + mAuthService.mImpl.getLastAuthenticationTime(0, + BiometricManager.Authenticators.BIOMETRIC_STRONG); + } + + @Test + public void testGetLastAuthenticationTime_flaggedOn_callsBiometricService() + throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_LAST_AUTHENTICATION_TIME); + setInternalAndTestBiometricPermissions(mContext, true /* hasPermission */); + + mAuthService = new AuthService(mContext, mInjector); + mAuthService.onStart(); + + final int userId = 0; + final int authenticators = BiometricManager.Authenticators.BIOMETRIC_STRONG; + + mAuthService.mImpl.getLastAuthenticationTime(userId, authenticators); + + waitForIdle(); + verify(mBiometricService).getLastAuthenticationTime(eq(userId), eq(authenticators)); + } + private static void setInternalAndTestBiometricPermissions( Context context, boolean hasPermission) { for (String p : List.of(TEST_BIOMETRIC, MANAGE_BIOMETRIC, USE_BIOMETRIC_INTERNAL)) { 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 6f4791af43f0..14a567a08165 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java @@ -18,7 +18,6 @@ package com.android.server.biometrics; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; import static android.hardware.biometrics.BiometricManager.Authenticators; -import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG; import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS; import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTHENTICATED_PENDING_SYSUI; @@ -61,6 +60,7 @@ import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricPrompt; +import android.hardware.biometrics.Flags; import android.hardware.biometrics.IBiometricAuthenticator; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; import android.hardware.biometrics.IBiometricSensorReceiver; @@ -70,12 +70,17 @@ import android.hardware.biometrics.IBiometricSysuiReceiver; import android.hardware.biometrics.PromptInfo; import android.hardware.display.DisplayManagerGlobal; import android.hardware.fingerprint.FingerprintManager; +import android.hardware.keymaster.HardwareAuthenticatorType; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; import android.os.UserManager; import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.SetFlagsRule; +import android.security.GateKeeper; import android.security.KeyStore; +import android.security.authorization.IKeystoreAuthorization; +import android.service.gatekeeper.IGateKeeperService; import android.view.Display; import android.view.DisplayInfo; import android.view.WindowManager; @@ -91,6 +96,7 @@ import com.android.server.biometrics.sensors.AuthSessionCoordinator; import com.android.server.biometrics.sensors.LockoutTracker; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.mockito.AdditionalMatchers; import org.mockito.ArgumentCaptor; @@ -104,6 +110,9 @@ import java.util.Random; @SmallTest public class BiometricServiceTest { + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private static final String TEST_PACKAGE_NAME = "test_package"; private static final long TEST_REQUEST_ID = 44; @@ -155,10 +164,16 @@ public class BiometricServiceTest { @Mock private BiometricCameraManager mBiometricCameraManager; + @Mock + private IKeystoreAuthorization mKeystoreAuthService; + + @Mock + private IGateKeeperService mGateKeeperService; + BiometricContextProvider mBiometricContextProvider; @Before - public void setUp() { + public void setUp() throws RemoteException { MockitoAnnotations.initMocks(this); resetReceivers(); @@ -196,6 +211,9 @@ public class BiometricServiceTest { mStatusBarService, null /* handler */, mAuthSessionCoordinator); when(mInjector.getBiometricContext(any())).thenReturn(mBiometricContextProvider); + when(mInjector.getKeystoreAuthorizationService()).thenReturn(mKeystoreAuthService); + when(mInjector.getGateKeeperService()).thenReturn(mGateKeeperService); + when(mGateKeeperService.getSecureUserId(anyInt())).thenReturn(42L); final String[] config = { "0:2:15", // ID0:Fingerprint:Strong @@ -1612,6 +1630,44 @@ public class BiometricServiceTest { verifyNoMoreInteractions(callback); } + @Test(expected = UnsupportedOperationException.class) + public void testGetLastAuthenticationTime_flagOff_throwsUnsupportedOperationException() + throws RemoteException { + mSetFlagsRule.disableFlags(Flags.FLAG_LAST_AUTHENTICATION_TIME); + + mBiometricService = new BiometricService(mContext, mInjector); + mBiometricService.mImpl.getLastAuthenticationTime(0, Authenticators.BIOMETRIC_STRONG); + } + + @Test + public void testGetLastAuthenticationTime_flagOn_callsKeystoreAuthorization() + throws RemoteException { + mSetFlagsRule.enableFlags(Flags.FLAG_LAST_AUTHENTICATION_TIME); + + final int[] hardwareAuthenticators = new int[] { + HardwareAuthenticatorType.PASSWORD, + HardwareAuthenticatorType.FINGERPRINT + }; + + final int userId = 0; + final long secureUserId = mGateKeeperService.getSecureUserId(userId); + + assertNotEquals(GateKeeper.INVALID_SECURE_USER_ID, secureUserId); + + final long expectedResult = 31337L; + + when(mKeystoreAuthService.getLastAuthTime(eq(secureUserId), eq(hardwareAuthenticators))) + .thenReturn(expectedResult); + + mBiometricService = new BiometricService(mContext, mInjector); + + final long result = mBiometricService.mImpl.getLastAuthenticationTime(userId, + Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL); + + assertEquals(expectedResult, result); + verify(mKeystoreAuthService).getLastAuthTime(eq(secureUserId), eq(hardwareAuthenticators)); + } + // Helper methods private int invokeCanAuthenticate(BiometricService service, int authenticators) diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java index 3bc6450ae591..b92cc64194e6 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java @@ -870,7 +870,7 @@ public class RootWindowContainerTests extends WindowTestsBase { new TestDisplayContent.Builder(mAtm, 1000, 1500) .setSystemDecorations(true).build(); - // Use invalid user id to let StorageManager.isUserKeyUnlocked() return false. + // Use invalid user id to let StorageManager.isCeStorageUnlocked() return false. final int currentUser = mRootWindowContainer.mCurrentUser; mRootWindowContainer.mCurrentUser = -1; diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index a72f7806d3ea..89ef523c2c71 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -2352,6 +2352,11 @@ public class TelecomManager { * <p> * <b>Note</b>: {@link android.app.Notification.CallStyle} notifications should be posted after * the call is placed in order for the notification to be non-dismissible. + * <p><b>Note</b>: Call Forwarding MMI codes can only be dialed by applications that are + * configured as the user defined default dialer or system dialer role. If a call containing a + * call forwarding MMI code is placed by an application that is not in one of these roles, the + * dialer will be launched with a UI showing the MMI code already populated so that the user can + * confirm the action before the call is placed. * @param address The address to make the call to. * @param extras Bundle of extras to use with the call. */ diff --git a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java index cb3782173dc8..59dc68900100 100644 --- a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java +++ b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java @@ -313,7 +313,8 @@ public class VcnGatewayConnectionConfigTest { @Test public void testBuilderAndGettersSafeModeDisabled() { - final VcnGatewayConnectionConfig config = newBuilderMinimal().enableSafeMode(false).build(); + final VcnGatewayConnectionConfig config = + newBuilderMinimal().setSafeModeEnabled(false).build(); assertFalse(config.isSafeModeEnabled()); } @@ -335,7 +336,8 @@ public class VcnGatewayConnectionConfigTest { @Test public void testPersistableBundleSafeModeDisabled() { - final VcnGatewayConnectionConfig config = newBuilderMinimal().enableSafeMode(false).build(); + final VcnGatewayConnectionConfig config = + newBuilderMinimal().setSafeModeEnabled(false).build(); assertEquals(config, new VcnGatewayConnectionConfig(config.toPersistableBundle())); } @@ -456,7 +458,7 @@ public class VcnGatewayConnectionConfigTest { assertEquals(config.isSafeModeEnabled(), configEqual.isSafeModeEnabled()); final VcnGatewayConnectionConfig configNotEqual = - newBuilderMinimal().enableSafeMode(false).build(); + newBuilderMinimal().setSafeModeEnabled(false).build(); assertEquals(config, configEqual); assertNotEquals(config, configNotEqual); diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java index bf73198d1006..f84616426389 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java @@ -661,7 +661,7 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection throws Exception { final VcnGatewayConnectionConfig config = VcnGatewayConnectionConfigTest.newTestBuilderMinimal() - .enableSafeMode(safeModeEnabledByCaller) + .setSafeModeEnabled(safeModeEnabledByCaller) .build(); final VcnGatewayConnection.Dependencies deps = mock(VcnGatewayConnection.Dependencies.class); diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp index 0d6dc3522d24..ed3e1ac5c39f 100644 --- a/tools/aapt2/Android.bp +++ b/tools/aapt2/Android.bp @@ -121,7 +121,6 @@ cc_library_host_static { "link/AutoVersioner.cpp", "link/ManifestFixer.cpp", "link/NoDefaultResourceRemover.cpp", - "link/ProductFilter.cpp", "link/PrivateAttributeMover.cpp", "link/ReferenceLinker.cpp", "link/ResourceExcluder.cpp", @@ -134,6 +133,7 @@ cc_library_host_static { "optimize/ResourceFilter.cpp", "optimize/Obfuscator.cpp", "optimize/VersionCollapser.cpp", + "process/ProductFilter.cpp", "process/SymbolTable.cpp", "split/TableSplitter.cpp", "text/Printer.cpp", diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp index 03f9715fb265..bb7b13a71412 100644 --- a/tools/aapt2/cmd/Compile.cpp +++ b/tools/aapt2/cmd/Compile.cpp @@ -45,6 +45,7 @@ #include "io/StringStream.h" #include "io/Util.h" #include "io/ZipArchive.h" +#include "process/ProductFilter.h" #include "trace/TraceBuffer.h" #include "util/Files.h" #include "util/Util.h" @@ -179,6 +180,15 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options, if (!res_parser.Parse(&xml_parser)) { return false; } + + if (options.product_.has_value()) { + if (!ProductFilter({*options.product_}, /* remove_default_config_values = */ true) + .Consume(context, &table)) { + context->GetDiagnostics()->Error(android::DiagMessage(path_data.source) + << "failed to filter product"); + return false; + } + } } if (options.pseudolocalize && translatable_file) { diff --git a/tools/aapt2/cmd/Compile.h b/tools/aapt2/cmd/Compile.h index 14a730a1b1a0..984d8901ac56 100644 --- a/tools/aapt2/cmd/Compile.h +++ b/tools/aapt2/cmd/Compile.h @@ -42,6 +42,7 @@ struct CompileOptions { // See comments on aapt::ResourceParserOptions. bool preserve_visibility_of_styleables = false; bool verbose = false; + std::optional<std::string> product_; }; /** Parses flags and compiles resources to be used in linking. */ @@ -76,6 +77,10 @@ class CompileCommand : public Command { AddOptionalFlag("--source-path", "Sets the compiled resource file source file path to the given string.", &options_.source_path); + AddOptionalFlag("--filter-product", + "Leave only resources specific to the given product. All " + "other resources (including defaults) are removed.", + &options_.product_); } int Action(const std::vector<std::string>& args) override; diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp index 97404fc69af2..f00be3688597 100644 --- a/tools/aapt2/cmd/Link.cpp +++ b/tools/aapt2/cmd/Link.cpp @@ -66,6 +66,7 @@ #include "optimize/ResourceDeduper.h" #include "optimize/VersionCollapser.h" #include "process/IResourceTableConsumer.h" +#include "process/ProductFilter.h" #include "process/SymbolTable.h" #include "split/TableSplitter.h" #include "trace/TraceBuffer.h" @@ -2127,7 +2128,7 @@ class Linker { << "can't select products when building static library"); } } else { - ProductFilter product_filter(options_.products); + ProductFilter product_filter(options_.products, /* remove_default_config_values = */ false); if (!product_filter.Consume(context_, &final_table_)) { context_->GetDiagnostics()->Error(android::DiagMessage() << "failed stripping products"); return 1; diff --git a/tools/aapt2/link/Linkers.h b/tools/aapt2/link/Linkers.h index 44cd276f77a2..18165f7d489f 100644 --- a/tools/aapt2/link/Linkers.h +++ b/tools/aapt2/link/Linkers.h @@ -20,12 +20,12 @@ #include <set> #include <unordered_set> +#include "Resource.h" +#include "SdkConstants.h" #include "android-base/macros.h" +#include "android-base/result.h" #include "androidfw/ConfigDescription.h" #include "androidfw/StringPiece.h" - -#include "Resource.h" -#include "SdkConstants.h" #include "process/IResourceTableConsumer.h" #include "xml/XmlDom.h" @@ -92,28 +92,6 @@ class PrivateAttributeMover : public IResourceTableConsumer { DISALLOW_COPY_AND_ASSIGN(PrivateAttributeMover); }; -class ResourceConfigValue; - -class ProductFilter : public IResourceTableConsumer { - public: - using ResourceConfigValueIter = std::vector<std::unique_ptr<ResourceConfigValue>>::iterator; - - explicit ProductFilter(std::unordered_set<std::string> products) : products_(products) { - } - - ResourceConfigValueIter SelectProductToKeep(const ResourceNameRef& name, - const ResourceConfigValueIter begin, - const ResourceConfigValueIter end, - android::IDiagnostics* diag); - - bool Consume(IAaptContext* context, ResourceTable* table) override; - - private: - DISALLOW_COPY_AND_ASSIGN(ProductFilter); - - std::unordered_set<std::string> products_; -}; - // Removes namespace nodes and URI information from the XmlResource. // // Once an XmlResource is processed by this consumer, it is no longer able to have its attributes diff --git a/tools/aapt2/link/ProductFilter.cpp b/tools/aapt2/process/ProductFilter.cpp index 9544986fda76..0b1c0a6adb51 100644 --- a/tools/aapt2/link/ProductFilter.cpp +++ b/tools/aapt2/process/ProductFilter.cpp @@ -14,16 +14,18 @@ * limitations under the License. */ -#include "link/Linkers.h" +#include "process/ProductFilter.h" + +#include <algorithm> #include "ResourceTable.h" #include "trace/TraceBuffer.h" namespace aapt { -ProductFilter::ResourceConfigValueIter ProductFilter::SelectProductToKeep( - const ResourceNameRef& name, const ResourceConfigValueIter begin, - const ResourceConfigValueIter end, android::IDiagnostics* diag) { +std::optional<ProductFilter::ResourceConfigValueIter> ProductFilter::SelectProductToKeep( + const ResourceNameRef& name, ResourceConfigValueIter begin, ResourceConfigValueIter end, + android::IDiagnostics* diag) { ResourceConfigValueIter default_product_iter = end; ResourceConfigValueIter selected_product_iter = end; @@ -36,12 +38,11 @@ ProductFilter::ResourceConfigValueIter ProductFilter::SelectProductToKeep( << "selection of product '" << config_value->product << "' for resource " << name << " is ambiguous"); - ResourceConfigValue* previously_selected_config_value = - selected_product_iter->get(); + ResourceConfigValue* previously_selected_config_value = selected_product_iter->get(); diag->Note(android::DiagMessage(previously_selected_config_value->value->GetSource()) << "product '" << previously_selected_config_value->product << "' is also a candidate"); - return end; + return std::nullopt; } // Select this product. @@ -54,11 +55,10 @@ ProductFilter::ResourceConfigValueIter ProductFilter::SelectProductToKeep( diag->Error(android::DiagMessage(config_value->value->GetSource()) << "multiple default products defined for resource " << name); - ResourceConfigValue* previously_default_config_value = - default_product_iter->get(); + ResourceConfigValue* previously_default_config_value = default_product_iter->get(); diag->Note(android::DiagMessage(previously_default_config_value->value->GetSource()) << "default product also defined here"); - return end; + return std::nullopt; } // Mark the default. @@ -66,9 +66,16 @@ ProductFilter::ResourceConfigValueIter ProductFilter::SelectProductToKeep( } } + if (remove_default_config_values_) { + // If we are leaving only a specific product, return early here instead of selecting the default + // value. Returning end here will cause this value set to be skipped, and will be removed with + // ClearEmptyValues method. + return selected_product_iter; + } + if (default_product_iter == end) { diag->Error(android::DiagMessage() << "no default product defined for resource " << name); - return end; + return std::nullopt; } if (selected_product_iter == end) { @@ -89,20 +96,27 @@ bool ProductFilter::Consume(IAaptContext* context, ResourceTable* table) { ResourceConfigValueIter start_range_iter = iter; while (iter != entry->values.end()) { ++iter; - if (iter == entry->values.end() || - (*iter)->config != (*start_range_iter)->config) { + if (iter == entry->values.end() || (*iter)->config != (*start_range_iter)->config) { // End of the array, or we saw a different config, // so this must be the end of a range of products. // Select the product to keep from the set of products defined. ResourceNameRef name(pkg->name, type->named_type, entry->name); - auto value_to_keep = SelectProductToKeep( - name, start_range_iter, iter, context->GetDiagnostics()); - if (value_to_keep == iter) { + auto value_to_keep = + SelectProductToKeep(name, start_range_iter, iter, context->GetDiagnostics()); + if (!value_to_keep.has_value()) { // An error occurred, we could not pick a product. error = true; - } else { + } else if (auto val = value_to_keep.value(); val != iter) { // We selected a product to keep. Move it to the new array. - new_values.push_back(std::move(*value_to_keep)); + if (remove_default_config_values_) { + // We are filtering values with the given product. The selected value here will be + // a new default value, and all other values will be removed. + new_values.push_back( + std::make_unique<ResourceConfigValue>((*val)->config, android::StringPiece{})); + new_values.back()->value = std::move((*val)->value); + } else { + new_values.push_back(std::move(*val)); + } } // Start the next range of products. @@ -115,7 +129,27 @@ bool ProductFilter::Consume(IAaptContext* context, ResourceTable* table) { } } } + + if (remove_default_config_values_) { + ClearEmptyValues(table); + } + return !error; } +void ProductFilter::ClearEmptyValues(ResourceTable* table) { + // Clear any empty packages/types/entries, as remove_default_config_values_ may remove an entire + // value set. + CHECK(remove_default_config_values_) + << __func__ << " should only be called when remove_default_config_values_ is set"; + + for (auto& pkg : table->packages) { + for (auto& type : pkg->types) { + std::erase_if(type->entries, [](auto& entry) { return entry->values.empty(); }); + } + std::erase_if(pkg->types, [](auto& type) { return type->entries.empty(); }); + } + std::erase_if(table->packages, [](auto& package) { return package->types.empty(); }); +} + } // namespace aapt diff --git a/tools/aapt2/process/ProductFilter.h b/tools/aapt2/process/ProductFilter.h new file mode 100644 index 000000000000..0ec2f00863fc --- /dev/null +++ b/tools/aapt2/process/ProductFilter.h @@ -0,0 +1,65 @@ +#pragma once + +#include <memory> +#include <optional> +#include <string> +#include <unordered_set> +#include <utility> +#include <vector> + +#include "Resource.h" +#include "android-base/macros.h" +#include "androidfw/ConfigDescription.h" +#include "androidfw/IDiagnostics.h" +#include "process/IResourceTableConsumer.h" + +namespace aapt { + +class ResourceConfigValue; + +class ProductFilter : public IResourceTableConsumer { + public: + using ResourceConfigValueIter = std::vector<std::unique_ptr<ResourceConfigValue>>::iterator; + + // Setting remove_default_config_values will remove all values other than + // specified product, including default. For example, if the following table + // + // <string name="foo" product="default">foo_default</string> + // <string name="foo" product="tablet">foo_tablet</string> + // <string name="bar">bar</string> + // + // is consumed with tablet, it will result in + // + // <string name="foo">foo_tablet</string> + // + // removing foo_default and bar. This option is to generate an RRO package + // with given product. + explicit ProductFilter(std::unordered_set<std::string> products, + bool remove_default_config_values) + : products_(std::move(products)), + remove_default_config_values_(remove_default_config_values) { + } + + bool Consume(IAaptContext* context, ResourceTable* table) override; + + private: + DISALLOW_COPY_AND_ASSIGN(ProductFilter); + + // SelectProductToKeep returns an iterator for the selected value. + // + // Returns std::nullopt in case of failure (e.g. ambiguous values, missing or duplicated default + // values). + // Returns `end` if keep_as_default_product is set and no value for the specified product was + // found. + std::optional<ResourceConfigValueIter> SelectProductToKeep(const ResourceNameRef& name, + ResourceConfigValueIter begin, + ResourceConfigValueIter end, + android::IDiagnostics* diag); + + void ClearEmptyValues(ResourceTable* table); + + std::unordered_set<std::string> products_; + bool remove_default_config_values_; +}; + +} // namespace aapt diff --git a/tools/aapt2/link/ProductFilter_test.cpp b/tools/aapt2/process/ProductFilter_test.cpp index 2cb9afa05cad..27a82dcc3453 100644 --- a/tools/aapt2/link/ProductFilter_test.cpp +++ b/tools/aapt2/process/ProductFilter_test.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "link/Linkers.h" +#include "process/ProductFilter.h" #include "test/Test.h" @@ -57,17 +57,15 @@ TEST(ProductFilterTest, SelectTwoProducts) { .Build(), context->GetDiagnostics())); - ProductFilter filter({"tablet"}); + ProductFilter filter({"tablet"}, /* remove_default_config_values = */ false); ASSERT_TRUE(filter.Consume(context.get(), &table)); - EXPECT_EQ(nullptr, test::GetValueForConfigAndProduct<Id>( - &table, "android:string/one", land, "")); - EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<Id>( - &table, "android:string/one", land, "tablet")); - EXPECT_EQ(nullptr, test::GetValueForConfigAndProduct<Id>( - &table, "android:string/one", port, "")); - EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<Id>( - &table, "android:string/one", port, "tablet")); + EXPECT_EQ(nullptr, test::GetValueForConfigAndProduct<Id>(&table, "android:string/one", land, "")); + EXPECT_NE(nullptr, + test::GetValueForConfigAndProduct<Id>(&table, "android:string/one", land, "tablet")); + EXPECT_EQ(nullptr, test::GetValueForConfigAndProduct<Id>(&table, "android:string/one", port, "")); + EXPECT_NE(nullptr, + test::GetValueForConfigAndProduct<Id>(&table, "android:string/one", port, "tablet")); } TEST(ProductFilterTest, SelectDefaultProduct) { @@ -88,15 +86,15 @@ TEST(ProductFilterTest, SelectDefaultProduct) { context->GetDiagnostics())); ; - ProductFilter filter(std::unordered_set<std::string>{}); + ProductFilter filter(std::unordered_set<std::string>{}, + /* remove_default_config_values = */ false); ASSERT_TRUE(filter.Consume(context.get(), &table)); - EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<Id>( - &table, "android:string/one", - ConfigDescription::DefaultConfig(), "")); - EXPECT_EQ(nullptr, test::GetValueForConfigAndProduct<Id>( - &table, "android:string/one", - ConfigDescription::DefaultConfig(), "tablet")); + EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<Id>(&table, "android:string/one", + ConfigDescription::DefaultConfig(), "")); + EXPECT_EQ(nullptr, + test::GetValueForConfigAndProduct<Id>(&table, "android:string/one", + ConfigDescription::DefaultConfig(), "tablet")); } TEST(ProductFilterTest, FailOnAmbiguousProduct) { @@ -123,7 +121,7 @@ TEST(ProductFilterTest, FailOnAmbiguousProduct) { .Build(), context->GetDiagnostics())); - ProductFilter filter({"tablet", "no-sdcard"}); + ProductFilter filter({"tablet", "no-sdcard"}, /* remove_default_config_values = */ false); ASSERT_FALSE(filter.Consume(context.get(), &table)); } @@ -144,8 +142,67 @@ TEST(ProductFilterTest, FailOnMultipleDefaults) { .Build(), context->GetDiagnostics())); - ProductFilter filter(std::unordered_set<std::string>{}); + ProductFilter filter(std::unordered_set<std::string>{}, + /* remove_default_config_values = */ false); ASSERT_FALSE(filter.Consume(context.get(), &table)); } +TEST(ProductFilterTest, RemoveDefaultConfigValues) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + + const ConfigDescription land = test::ParseConfigOrDie("land"); + const ConfigDescription port = test::ParseConfigOrDie("port"); + + ResourceTable table; + ASSERT_TRUE(table.AddResource( + NewResourceBuilder(test::ParseNameOrDie("android:string/one")) + .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("land/default.xml")).Build(), + land) + .Build(), + context->GetDiagnostics())); + + ASSERT_TRUE(table.AddResource( + NewResourceBuilder(test::ParseNameOrDie("android:string/one")) + .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("land/tablet.xml")).Build(), + land, "tablet") + .Build(), + context->GetDiagnostics())); + + ASSERT_TRUE(table.AddResource( + NewResourceBuilder(test::ParseNameOrDie("android:string/two")) + .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("land/default.xml")).Build(), + land) + .Build(), + context->GetDiagnostics())); + + ASSERT_TRUE(table.AddResource( + NewResourceBuilder(test::ParseNameOrDie("android:string/one")) + .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("port/default.xml")).Build(), + port) + .Build(), + context->GetDiagnostics())); + + ASSERT_TRUE(table.AddResource( + NewResourceBuilder(test::ParseNameOrDie("android:string/one")) + .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("port/tablet.xml")).Build(), + port, "tablet") + .Build(), + context->GetDiagnostics())); + + ASSERT_TRUE(table.AddResource( + NewResourceBuilder(test::ParseNameOrDie("android:string/two")) + .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("port/default.xml")).Build(), + port) + .Build(), + context->GetDiagnostics())); + + ProductFilter filter({"tablet"}, /* remove_default_config_values = */ true); + ASSERT_TRUE(filter.Consume(context.get(), &table)); + + EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<Id>(&table, "android:string/one", land, "")); + EXPECT_EQ(nullptr, test::GetValueForConfigAndProduct<Id>(&table, "android:string/two", land, "")); + EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<Id>(&table, "android:string/one", port, "")); + EXPECT_EQ(nullptr, test::GetValueForConfigAndProduct<Id>(&table, "android:string/two", port, "")); +} + } // namespace aapt diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt index a20266a9b140..28eab8f62e74 100644 --- a/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt +++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt @@ -20,7 +20,6 @@ import com.android.tools.lint.client.api.IssueRegistry import com.android.tools.lint.client.api.Vendor import com.android.tools.lint.detector.api.CURRENT_API import com.google.android.lint.aidl.EnforcePermissionDetector -import com.google.android.lint.aidl.EnforcePermissionHelperDetector import com.google.android.lint.aidl.SimpleManualPermissionEnforcementDetector import com.google.auto.service.AutoService @@ -30,7 +29,8 @@ class AndroidGlobalIssueRegistry : IssueRegistry() { override val issues = listOf( EnforcePermissionDetector.ISSUE_MISSING_ENFORCE_PERMISSION, EnforcePermissionDetector.ISSUE_MISMATCHING_ENFORCE_PERMISSION, - EnforcePermissionHelperDetector.ISSUE_ENFORCE_PERMISSION_HELPER, + EnforcePermissionDetector.ISSUE_ENFORCE_PERMISSION_HELPER, + EnforcePermissionDetector.ISSUE_MISUSING_ENFORCE_PERMISSION, SimpleManualPermissionEnforcementDetector.ISSUE_SIMPLE_MANUAL_PERMISSION_ENFORCEMENT, ) @@ -45,4 +45,4 @@ class AndroidGlobalIssueRegistry : IssueRegistry() { feedbackUrl = "http://b/issues/new?component=315013", contact = "repsonsible-apis@google.com" ) -}
\ No newline at end of file +} diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt index 0baac2c7aacf..a74400d3c0b3 100644 --- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt +++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt @@ -30,29 +30,34 @@ import com.android.tools.lint.detector.api.JavaContext import com.android.tools.lint.detector.api.Scope import com.android.tools.lint.detector.api.Severity import com.android.tools.lint.detector.api.SourceCodeScanner +import com.google.android.lint.findCallExpression import com.intellij.psi.PsiAnnotation import com.intellij.psi.PsiArrayInitializerMemberValue import com.intellij.psi.PsiClass import com.intellij.psi.PsiElement import com.intellij.psi.PsiMethod -import org.jetbrains.uast.UAnnotation +import org.jetbrains.uast.UBlockExpression +import org.jetbrains.uast.UDeclarationsExpression import org.jetbrains.uast.UElement +import org.jetbrains.uast.UExpression import org.jetbrains.uast.UMethod -import org.jetbrains.uast.toUElement +import org.jetbrains.uast.skipParenthesizedExprDown + +import java.util.EnumSet /** - * Lint Detector that ensures that any method overriding a method annotated - * with @EnforcePermission is also annotated with the exact same annotation. - * The intent is to surface the effective permission checks to the service - * implementations. + * Lint Detector that ensures consistency when using the @EnforcePermission + * annotation. Multiple verifications are implemented: * - * This is done with 2 mechanisms: * 1. Visit any annotation usage, to ensure that any derived class will have - * the correct annotation on each methods. This is for the top to bottom - * propagation. - * 2. Visit any annotation, to ensure that if a method is annotated, it has + * the correct annotation on each methods. Even if the subclass does not + * have the annotation, visitAnnotationUsage will be called which allows us + * to capture the issue. + * 2. Visit any method, to ensure that if a method is annotated, it has * its ancestor also annotated. This is to avoid having an annotation on a * Java method without the corresponding annotation on the AIDL interface. + * 3. When annotated, ensures that the first instruction is to call the helper + * method (or the parent helper). */ class EnforcePermissionDetector : Detector(), SourceCodeScanner { @@ -60,9 +65,8 @@ class EnforcePermissionDetector : Detector(), SourceCodeScanner { return listOf(ANNOTATION_ENFORCE_PERMISSION) } - override fun getApplicableUastTypes(): List<Class<out UElement>> { - return listOf(UAnnotation::class.java) - } + override fun getApplicableUastTypes(): List<Class<out UElement?>> = + listOf(UMethod::class.java) private fun annotationValueGetChildren(elem: PsiElement): Array<PsiElement> { if (elem is PsiArrayInitializerMemberValue) @@ -121,11 +125,6 @@ class EnforcePermissionDetector : Detector(), SourceCodeScanner { overriddenMethod: PsiMethod, checkEquivalence: Boolean = true ) { - // If method is not from a Stub subclass, this method shouldn't use @EP at all. - // This is handled by EnforcePermissionHelperDetector. - if (!isContainedInSubclassOfStub(context, overridingMethod.toUElement() as? UMethod)) { - return - } val overridingAnnotation = overridingMethod.getAnnotation(ANNOTATION_ENFORCE_PERMISSION) val overriddenAnnotation = overriddenMethod.getAnnotation(ANNOTATION_ENFORCE_PERMISSION) val location = context.getLocation(element) @@ -161,40 +160,102 @@ class EnforcePermissionDetector : Detector(), SourceCodeScanner { ) { if (usageInfo.type == AnnotationUsageType.METHOD_OVERRIDE && annotationInfo.origin == AnnotationOrigin.METHOD) { + /* Ignore implementations that are not a sub-class of Stub (i.e., Proxy). */ + val uMethod = element as? UMethod ?: return + if (!isContainedInSubclassOfStub(context, uMethod)) { + return + } val overridingMethod = element.sourcePsi as PsiMethod val overriddenMethod = usageInfo.referenced as PsiMethod compareMethods(context, element, overridingMethod, overriddenMethod) } } - override fun createUastHandler(context: JavaContext): UElementHandler { - return object : UElementHandler() { - override fun visitAnnotation(node: UAnnotation) { - if (node.qualifiedName != ANNOTATION_ENFORCE_PERMISSION) { - return - } - val method = node.uastParent as? UMethod ?: return - val overridingMethod = method as PsiMethod - val parents = overridingMethod.findSuperMethods() - for (overriddenMethod in parents) { - // The equivalence check can be skipped, if both methods are - // annotated, it will be verified by visitAnnotationUsage. - compareMethods(context, method, overridingMethod, - overriddenMethod, checkEquivalence = false) - } + override fun createUastHandler(context: JavaContext): UElementHandler = AidlStubHandler(context) + + private inner class AidlStubHandler(val context: JavaContext) : UElementHandler() { + override fun visitMethod(node: UMethod) { + if (context.evaluator.isAbstract(node)) return + if (!node.hasAnnotation(ANNOTATION_ENFORCE_PERMISSION)) return + + if (!isContainedInSubclassOfStub(context, node)) { + context.report( + ISSUE_MISUSING_ENFORCE_PERMISSION, + node, + context.getLocation(node), + "The class of ${node.name} does not inherit from an AIDL generated Stub class" + ) + return + } + + /* Check that we are connected to the super class */ + val overridingMethod = node as PsiMethod + val parents = overridingMethod.findSuperMethods() + for (overriddenMethod in parents) { + // The equivalence check can be skipped, if both methods are + // annotated, it will be verified by visitAnnotationUsage. + compareMethods(context, node, overridingMethod, + overriddenMethod, checkEquivalence = false) + } + + /* Check that the helper is called as a first instruction */ + val targetExpression = getHelperMethodCallSourceString(node) + val message = + "Method must start with $targetExpression or super.${node.name}(), if applicable" + + val firstExpression = (node.uastBody as? UBlockExpression) + ?.expressions?.firstOrNull() + + if (firstExpression == null) { + context.report( + ISSUE_ENFORCE_PERMISSION_HELPER, + context.getLocation(node), + message, + ) + return + } + + val firstExpressionSource = firstExpression.skipParenthesizedExprDown() + .asSourceString() + .filterNot(Char::isWhitespace) + + if (firstExpressionSource != targetExpression && + firstExpressionSource != "super.$targetExpression") { + // calling super.<methodName>() is also legal + val directSuper = context.evaluator.getSuperMethod(node) + val firstCall = findCallExpression(firstExpression)?.resolve() + if (directSuper != null && firstCall == directSuper) return + + val locationTarget = getLocationTarget(firstExpression) + val expressionLocation = context.getLocation(locationTarget) + + context.report( + ISSUE_ENFORCE_PERMISSION_HELPER, + context.getLocation(node), + message, + getHelperMethodFix(node, expressionLocation), + ) } } } companion object { + + private const val HELPER_SUFFIX = "_enforcePermission" + val EXPLANATION = """ - The @EnforcePermission annotation is used to indicate that the underlying binder code - has already verified the caller's permissions before calling the appropriate method. The - verification code is usually generated by the AIDL compiler, which also takes care of - annotating the generated Java code. + The @EnforcePermission annotation is used to delegate the verification of the caller's + permissions to a generated AIDL method. In order to surface that information to platform developers, the same annotation must be used on the implementation class or methods. + + The @EnforcePermission annotation can only be used on methods whose class extends from + the Stub class generated by the AIDL compiler. When @EnforcePermission is applied, the + AIDL compiler generates a Stub method to do the permission check called yourMethodName$HELPER_SUFFIX. + + yourMethodName$HELPER_SUFFIX must be executed before any other operation. To do that, you can + either call it directly, or call it indirectly via super.yourMethodName(). """ val ISSUE_MISSING_ENFORCE_PERMISSION: Issue = Issue.create( @@ -206,7 +267,7 @@ class EnforcePermissionDetector : Detector(), SourceCodeScanner { severity = Severity.ERROR, implementation = Implementation( EnforcePermissionDetector::class.java, - Scope.JAVA_FILE_SCOPE + EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES) ) ) @@ -219,8 +280,47 @@ class EnforcePermissionDetector : Detector(), SourceCodeScanner { severity = Severity.ERROR, implementation = Implementation( EnforcePermissionDetector::class.java, - Scope.JAVA_FILE_SCOPE + EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES) + ) + ) + + val ISSUE_ENFORCE_PERMISSION_HELPER: Issue = Issue.create( + id = "MissingEnforcePermissionHelper", + briefDescription = """Missing permission-enforcing method call in AIDL method + |annotated with @EnforcePermission""".trimMargin(), + explanation = EXPLANATION, + category = Category.SECURITY, + priority = 6, + severity = Severity.ERROR, + implementation = Implementation( + EnforcePermissionDetector::class.java, + EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES) + ) + ) + + val ISSUE_MISUSING_ENFORCE_PERMISSION: Issue = Issue.create( + id = "MisusingEnforcePermissionAnnotation", + briefDescription = "@EnforcePermission cannot be used here", + explanation = EXPLANATION, + category = Category.SECURITY, + priority = 6, + severity = Severity.ERROR, + implementation = Implementation( + EnforcePermissionDetector::class.java, + EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES) ) ) + + /** + * handles an edge case with UDeclarationsExpression, where sourcePsi is null, + * resulting in an incorrect Location if used directly + */ + private fun getLocationTarget(firstExpression: UExpression): PsiElement? { + if (firstExpression.sourcePsi != null) return firstExpression.sourcePsi + if (firstExpression is UDeclarationsExpression) { + return firstExpression.declarations.firstOrNull()?.sourcePsi + } + return null + } } } diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt deleted file mode 100644 index df13af516514..000000000000 --- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.android.lint.aidl - -import com.android.tools.lint.client.api.UElementHandler -import com.android.tools.lint.detector.api.Category -import com.android.tools.lint.detector.api.Detector -import com.android.tools.lint.detector.api.Implementation -import com.android.tools.lint.detector.api.Issue -import com.android.tools.lint.detector.api.JavaContext -import com.android.tools.lint.detector.api.Scope -import com.android.tools.lint.detector.api.Severity -import com.android.tools.lint.detector.api.SourceCodeScanner -import com.google.android.lint.findCallExpression -import com.intellij.psi.PsiElement -import org.jetbrains.uast.UBlockExpression -import org.jetbrains.uast.UDeclarationsExpression -import org.jetbrains.uast.UElement -import org.jetbrains.uast.UExpression -import org.jetbrains.uast.UMethod -import org.jetbrains.uast.skipParenthesizedExprDown - -class EnforcePermissionHelperDetector : Detector(), SourceCodeScanner { - override fun getApplicableUastTypes(): List<Class<out UElement?>> = - listOf(UMethod::class.java) - - override fun createUastHandler(context: JavaContext): UElementHandler = AidlStubHandler(context) - - private inner class AidlStubHandler(val context: JavaContext) : UElementHandler() { - override fun visitMethod(node: UMethod) { - if (context.evaluator.isAbstract(node)) return - if (!node.hasAnnotation(ANNOTATION_ENFORCE_PERMISSION)) return - - if (!isContainedInSubclassOfStub(context, node)) { - context.report( - ISSUE_MISUSING_ENFORCE_PERMISSION, - node, - context.getLocation(node), - "The class of ${node.name} does not inherit from an AIDL generated Stub class" - ) - return - } - - val targetExpression = getHelperMethodCallSourceString(node) - val message = - "Method must start with $targetExpression or super.${node.name}(), if applicable" - - val firstExpression = (node.uastBody as? UBlockExpression) - ?.expressions?.firstOrNull() - - if (firstExpression == null) { - context.report( - ISSUE_ENFORCE_PERMISSION_HELPER, - context.getLocation(node), - message, - ) - return - } - - val firstExpressionSource = firstExpression.skipParenthesizedExprDown() - .asSourceString() - .filterNot(Char::isWhitespace) - - if (firstExpressionSource != targetExpression && - firstExpressionSource != "super.$targetExpression") { - // calling super.<methodName>() is also legal - val directSuper = context.evaluator.getSuperMethod(node) - val firstCall = findCallExpression(firstExpression)?.resolve() - if (directSuper != null && firstCall == directSuper) return - - val locationTarget = getLocationTarget(firstExpression) - val expressionLocation = context.getLocation(locationTarget) - - context.report( - ISSUE_ENFORCE_PERMISSION_HELPER, - context.getLocation(node), - message, - getHelperMethodFix(node, expressionLocation), - ) - } - } - } - - companion object { - private const val HELPER_SUFFIX = "_enforcePermission" - - private const val EXPLANATION = """ - The @EnforcePermission annotation can only be used on methods whose class extends from - the Stub class generated by the AIDL compiler. When @EnforcePermission is applied, the - AIDL compiler generates a Stub method to do the permission check called yourMethodName$HELPER_SUFFIX. - - yourMethodName$HELPER_SUFFIX must be executed before any other operation. To do that, you can - either call it directly, or call it indirectly via super.yourMethodName(). - """ - - val ISSUE_ENFORCE_PERMISSION_HELPER: Issue = Issue.create( - id = "MissingEnforcePermissionHelper", - briefDescription = """Missing permission-enforcing method call in AIDL method - |annotated with @EnforcePermission""".trimMargin(), - explanation = EXPLANATION, - category = Category.SECURITY, - priority = 6, - severity = Severity.ERROR, - implementation = Implementation( - EnforcePermissionHelperDetector::class.java, - Scope.JAVA_FILE_SCOPE - ) - ) - - val ISSUE_MISUSING_ENFORCE_PERMISSION: Issue = Issue.create( - id = "MisusingEnforcePermissionAnnotation", - briefDescription = "@EnforcePermission cannot be used here", - explanation = EXPLANATION, - category = Category.SECURITY, - priority = 6, - severity = Severity.ERROR, - implementation = Implementation( - EnforcePermissionDetector::class.java, - Scope.JAVA_FILE_SCOPE - ) - ) - - /** - * handles an edge case with UDeclarationsExpression, where sourcePsi is null, - * resulting in an incorrect Location if used directly - */ - private fun getLocationTarget(firstExpression: UExpression): PsiElement? { - if (firstExpression.sourcePsi != null) return firstExpression.sourcePsi - if (firstExpression is UDeclarationsExpression) { - return firstExpression.declarations.firstOrNull()?.sourcePsi - } - return null - } - } -} diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorCodegenTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorCodegenTest.kt index 5a63bb4084d2..3ef02f865355 100644 --- a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorCodegenTest.kt +++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorCodegenTest.kt @@ -25,10 +25,10 @@ import com.android.tools.lint.detector.api.Issue @Suppress("UnstableApiUsage") class EnforcePermissionHelperDetectorCodegenTest : LintDetectorTest() { - override fun getDetector(): Detector = EnforcePermissionHelperDetector() + override fun getDetector(): Detector = EnforcePermissionDetector() override fun getIssues(): List<Issue> = listOf( - EnforcePermissionHelperDetector.ISSUE_ENFORCE_PERMISSION_HELPER + EnforcePermissionDetector.ISSUE_ENFORCE_PERMISSION_HELPER ) override fun lint(): TestLintTask = super.lint().allowMissingSdk(true) diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt index 10a6e1da91dc..64e2bfbad7bb 100644 --- a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt +++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt @@ -20,10 +20,10 @@ import com.android.tools.lint.checks.infrastructure.LintDetectorTest import com.android.tools.lint.checks.infrastructure.TestLintTask class EnforcePermissionHelperDetectorTest : LintDetectorTest() { - override fun getDetector() = EnforcePermissionHelperDetector() + override fun getDetector() = EnforcePermissionDetector() override fun getIssues() = listOf( - EnforcePermissionHelperDetector.ISSUE_ENFORCE_PERMISSION_HELPER, - EnforcePermissionHelperDetector.ISSUE_MISUSING_ENFORCE_PERMISSION + EnforcePermissionDetector.ISSUE_ENFORCE_PERMISSION_HELPER, + EnforcePermissionDetector.ISSUE_MISUSING_ENFORCE_PERMISSION ) override fun lint(): TestLintTask = super.lint().allowMissingSdk() |