diff options
13 files changed, 370 insertions, 41 deletions
diff --git a/core/java/android/security/advancedprotection/OWNERS b/core/java/android/security/advancedprotection/OWNERS new file mode 100644 index 000000000000..ddac8edb6f4a --- /dev/null +++ b/core/java/android/security/advancedprotection/OWNERS @@ -0,0 +1,12 @@ +# Bug component: 315013 + +achim@google.com +azharaa@google.com +cpinelli@google.com +eranm@google.com +hanikazmi@google.com +haok@google.com +lus@google.com +mattgilbride@google.com +mpgroover@google.com +wnan@google.com diff --git a/libs/WindowManager/Shell/res/drawable/decor_back_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_back_button_dark.xml index 5ecba380fb60..a36b21f0408c 100644 --- a/libs/WindowManager/Shell/res/drawable/decor_back_button_dark.xml +++ b/libs/WindowManager/Shell/res/drawable/decor_back_button_dark.xml @@ -15,6 +15,7 @@ --> <vector xmlns:android="http://schemas.android.com/apk/res/android" + android:autoMirrored="true" android:width="32.0dp" android:height="32.0dp" android:viewportWidth="32.0" diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java index e604cb7e918b..82e6ed3d9597 100644 --- a/media/java/android/media/tv/TvView.java +++ b/media/java/android/media/tv/TvView.java @@ -91,7 +91,7 @@ public class TvView extends ViewGroup { private static final Object sMainTvViewLock = new Object(); private static WeakReference<TvView> sMainTvView = NULL_TV_VIEW; - private final Handler mHandler = new Handler(); + private Handler mHandler = new Handler(); private Session mSession; private SurfaceView mSurfaceView; private Surface mSurface; @@ -207,6 +207,17 @@ public class TvView extends ViewGroup { mCallback = callback; } + /** + * Sets the handler to be invoked when an event is dispatched to this TvView. + * If handler is not set by this function, TvView will use its default handler. + * + * @param handler The handler to handle events. + * @hide + */ + public void setHandler(@NonNull Handler handler) { + mHandler = handler; + } + /** @hide */ public Session getInputSession() { return mSession; diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java index 59e64cd06667..87bd80754269 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClient.java @@ -121,6 +121,11 @@ public class FingerprintUpdateActiveUserClient extends StartUserClient<ISession, final int targetId = getTargetUserId(); Slog.d(TAG, "Setting active user: " + targetId); HidlToAidlSessionAdapter sessionAdapter = (HidlToAidlSessionAdapter) getFreshDaemon(); + if (sessionAdapter.getIBiometricsFingerprint() == null) { + Slog.e(TAG, "Failed to setActiveGroup: HIDL daemon is null."); + mCallback.onClientFinished(this, false /* success */); + return; + } sessionAdapter.setActiveGroup(targetId, mDirectory.getAbsolutePath()); mAuthenticatorIds.put(targetId, mHasEnrolledBiometrics ? sessionAdapter.getAuthenticatorIdForUpdateClient() : 0L); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapter.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapter.java index b469752d49cf..671bd875d194 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapter.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSessionAdapter.java @@ -209,6 +209,10 @@ public class HidlToAidlSessionAdapter implements ISession { return null; } + protected IBiometricsFingerprint getIBiometricsFingerprint() { + return mSession.get(); + } + public long getAuthenticatorIdForUpdateClient() throws RemoteException { return mSession.get().getAuthenticatorId(); } diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index 0ffd002197c4..35003af5c56b 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -65,6 +65,7 @@ import static com.android.server.stats.pull.IonMemoryUtil.readSystemIonHeapSizeF import static com.android.server.stats.pull.ProcfsMemoryUtil.getProcessCmdlines; import static com.android.server.stats.pull.ProcfsMemoryUtil.readCmdlineFromProcfs; import static com.android.server.stats.pull.ProcfsMemoryUtil.readMemorySnapshotFromProcfs; +import static com.android.server.stats.pull.netstats.NetworkStatsUtils.fromPublicNetworkStats; import static libcore.io.IoUtils.closeQuietly; @@ -203,7 +204,6 @@ import com.android.internal.os.SelectedProcessCpuThreadReader; import com.android.internal.os.StoragedUidIoStatsReader; import com.android.internal.util.CollectionUtils; import com.android.internal.util.FrameworkStatsLog; -import com.android.net.module.util.NetworkStatsUtils; import com.android.role.RoleManagerLocal; import com.android.server.BinderCallsStatsService; import com.android.server.LocalManagerRegistry; @@ -1522,7 +1522,7 @@ public class StatsPullAtomService extends SystemService { currentTimeInMillis); final NetworkStats nonTaggedStats = - NetworkStatsUtils.fromPublicNetworkStats(queryNonTaggedStats); + fromPublicNetworkStats(queryNonTaggedStats); queryNonTaggedStats.close(); if (!includeTags) return nonTaggedStats; @@ -1531,7 +1531,7 @@ public class StatsPullAtomService extends SystemService { currentTimeInMillis - elapsedMillisSinceBoot - bucketDuration, currentTimeInMillis); final NetworkStats taggedStats = - NetworkStatsUtils.fromPublicNetworkStats(queryTaggedStats); + fromPublicNetworkStats(queryTaggedStats); queryTaggedStats.close(); return nonTaggedStats.add(taggedStats); } diff --git a/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsUtils.java b/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsUtils.java new file mode 100644 index 000000000000..de5885201ea4 --- /dev/null +++ b/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsUtils.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.stats.pull.netstats; + +import static android.net.NetworkStats.DEFAULT_NETWORK_ALL; +import static android.net.NetworkStats.METERED_ALL; +import static android.net.NetworkStats.ROAMING_ALL; +import static android.net.NetworkStats.SET_ALL; + +import android.app.usage.NetworkStats; + +import com.android.internal.annotations.VisibleForTesting; + +/** + * Utility methods for accessing {@link android.net.NetworkStats}. + */ +public class NetworkStatsUtils { + + /** + * Convert structure from android.app.usage.NetworkStats to android.net.NetworkStats. + */ + public static android.net.NetworkStats fromPublicNetworkStats( + NetworkStats publiceNetworkStats) { + android.net.NetworkStats stats = new android.net.NetworkStats(0L, 0); + while (publiceNetworkStats.hasNextBucket()) { + NetworkStats.Bucket bucket = new NetworkStats.Bucket(); + publiceNetworkStats.getNextBucket(bucket); + final android.net.NetworkStats.Entry entry = fromBucket(bucket); + stats = stats.addEntry(entry); + } + return stats; + } + + /** + * Convert structure from android.app.usage.NetworkStats.Bucket + * to android.net.NetworkStats.Entry. + */ + @VisibleForTesting + public static android.net.NetworkStats.Entry fromBucket(NetworkStats.Bucket bucket) { + return new android.net.NetworkStats.Entry( + null /* IFACE_ALL */, bucket.getUid(), convertBucketState(bucket.getState()), + convertBucketTag(bucket.getTag()), convertBucketMetered(bucket.getMetered()), + convertBucketRoaming(bucket.getRoaming()), + convertBucketDefaultNetworkStatus(bucket.getDefaultNetworkStatus()), + bucket.getRxBytes(), bucket.getRxPackets(), + bucket.getTxBytes(), bucket.getTxPackets(), 0 /* operations */); + } + + private static int convertBucketState(int networkStatsSet) { + switch (networkStatsSet) { + case NetworkStats.Bucket.STATE_ALL: return SET_ALL; + case NetworkStats.Bucket.STATE_DEFAULT: return android.net.NetworkStats.SET_DEFAULT; + case NetworkStats.Bucket.STATE_FOREGROUND: + return android.net.NetworkStats.SET_FOREGROUND; + } + return 0; + } + + private static int convertBucketTag(int tag) { + switch (tag) { + case NetworkStats.Bucket.TAG_NONE: return android.net.NetworkStats.TAG_NONE; + } + return tag; + } + + private static int convertBucketMetered(int metered) { + switch (metered) { + case NetworkStats.Bucket.METERED_ALL: return METERED_ALL; + case NetworkStats.Bucket.METERED_NO: return android.net.NetworkStats.METERED_NO; + case NetworkStats.Bucket.METERED_YES: return android.net.NetworkStats.METERED_YES; + } + return 0; + } + + private static int convertBucketRoaming(int roaming) { + switch (roaming) { + case NetworkStats.Bucket.ROAMING_ALL: return ROAMING_ALL; + case NetworkStats.Bucket.ROAMING_NO: return android.net.NetworkStats.ROAMING_NO; + case NetworkStats.Bucket.ROAMING_YES: return android.net.NetworkStats.ROAMING_YES; + } + return 0; + } + + private static int convertBucketDefaultNetworkStatus(int defaultNetworkStatus) { + switch (defaultNetworkStatus) { + case NetworkStats.Bucket.DEFAULT_NETWORK_ALL: + return DEFAULT_NETWORK_ALL; + case NetworkStats.Bucket.DEFAULT_NETWORK_NO: + return android.net.NetworkStats.DEFAULT_NETWORK_NO; + case NetworkStats.Bucket.DEFAULT_NETWORK_YES: + return android.net.NetworkStats.DEFAULT_NETWORK_YES; + } + return 0; + } +} diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index 41ea5737dbc8..0f10b8cbea51 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -83,6 +83,7 @@ android_test { "securebox", "flag-junit", "ravenwood-junit", + "net-tests-utils", "net_flags_lib", "CtsVirtualDeviceCommonLib", "com_android_server_accessibility_flags_lib", diff --git a/services/tests/servicestests/src/com/android/server/stats/pull/netstats/NetworkStatsUtilsTest.kt b/services/tests/servicestests/src/com/android/server/stats/pull/netstats/NetworkStatsUtilsTest.kt new file mode 100644 index 000000000000..c560c04e72cd --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/stats/pull/netstats/NetworkStatsUtilsTest.kt @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.stats.pull.netstats + +import android.net.NetworkStats +import android.net.NetworkStats.DEFAULT_NETWORK_NO +import android.net.NetworkStats.DEFAULT_NETWORK_YES +import android.net.NetworkStats.Entry +import android.net.NetworkStats.METERED_NO +import android.net.NetworkStats.ROAMING_NO +import android.net.NetworkStats.ROAMING_YES +import android.net.NetworkStats.SET_DEFAULT +import android.net.NetworkStats.TAG_NONE +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.testutils.assertEntryEquals +import com.android.testutils.assertNetworkStatsEquals +import com.android.testutils.makePublicStatsFromAndroidNetStats +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.doReturn +import org.mockito.Mockito.mock +import org.mockito.Mockito.`when` + +/** + * Build/Install/Run: + * atest FrameworksServicesTests:NetworkStatsUtilsTest + */ +@RunWith(AndroidJUnit4::class) +class NetworkStatsUtilsTest { + + @Test + fun testBucketToEntry() { + val bucket = makeMockBucket(android.app.usage.NetworkStats.Bucket.UID_ALL, + android.app.usage.NetworkStats.Bucket.TAG_NONE, + android.app.usage.NetworkStats.Bucket.STATE_DEFAULT, + android.app.usage.NetworkStats.Bucket.METERED_YES, + android.app.usage.NetworkStats.Bucket.ROAMING_NO, + android.app.usage.NetworkStats.Bucket.DEFAULT_NETWORK_ALL, 1024, 8, 2048, 12) + val entry = NetworkStatsUtils.fromBucket(bucket) + val expectedEntry = NetworkStats.Entry(null /* IFACE_ALL */, NetworkStats.UID_ALL, + NetworkStats.SET_DEFAULT, NetworkStats.TAG_NONE, NetworkStats.METERED_YES, + NetworkStats.ROAMING_NO, NetworkStats.DEFAULT_NETWORK_ALL, 1024, 8, 2048, 12, + 0 /* operations */) + + assertEntryEquals(expectedEntry, entry) + } + + @Test + fun testPublicStatsToAndroidNetStats() { + val uid1 = 10001 + val uid2 = 10002 + val testIface = "wlan0" + val testAndroidNetStats = NetworkStats(0L, 3) + .addEntry(Entry(testIface, uid1, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 20, 3, 57, 40, 3)) + .addEntry(Entry( + testIface, uid2, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_YES, DEFAULT_NETWORK_NO, 2, 7, 2, 5, 7)) + .addEntry(Entry(testIface, uid2, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_YES, DEFAULT_NETWORK_NO, 4, 5, 3, 1, 8)) + val publicStats: android.app.usage.NetworkStats = + makePublicStatsFromAndroidNetStats(testAndroidNetStats) + val androidNetStats: NetworkStats = + NetworkStatsUtils.fromPublicNetworkStats(publicStats) + + // 1. The public `NetworkStats` class does not include interface information. + // Interface details must be removed and items with duplicated + // keys need to be merged before making any comparisons. + // 2. The public `NetworkStats` class lacks an operations field. + // Thus, the information will not be preserved during the conversion. + val expectedStats = NetworkStats(0L, 2) + .addEntry(Entry(null, uid1, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 20, 3, 57, 40, 0)) + .addEntry(Entry(null, uid2, SET_DEFAULT, TAG_NONE, + METERED_NO, ROAMING_YES, DEFAULT_NETWORK_NO, 6, 12, 5, 6, 0)) + assertNetworkStatsEquals(expectedStats, androidNetStats) + } + + private fun makeMockBucket( + uid: Int, + tag: Int, + state: Int, + metered: Int, + roaming: Int, + defaultNetwork: Int, + rxBytes: Long, + rxPackets: Long, + txBytes: Long, + txPackets: Long + ): android.app.usage.NetworkStats.Bucket { + val ret: android.app.usage.NetworkStats.Bucket = + mock(android.app.usage.NetworkStats.Bucket::class.java) + doReturn(uid).`when`(ret).getUid() + doReturn(tag).`when`(ret).getTag() + doReturn(state).`when`(ret).getState() + doReturn(metered).`when`(ret).getMetered() + doReturn(roaming).`when`(ret).getRoaming() + doReturn(defaultNetwork).`when`(ret).getDefaultNetworkStatus() + doReturn(rxBytes).`when`(ret).getRxBytes() + doReturn(rxPackets).`when`(ret).getRxPackets() + doReturn(txBytes).`when`(ret).getTxBytes() + doReturn(txPackets).`when`(ret).getTxPackets() + return ret + } +}
\ No newline at end of file diff --git a/tools/systemfeatures/Android.bp b/tools/systemfeatures/Android.bp index 2cebfe9790d0..aca25eb8f603 100644 --- a/tools/systemfeatures/Android.bp +++ b/tools/systemfeatures/Android.bp @@ -30,9 +30,9 @@ java_binary_host { genrule { name: "systemfeatures-gen-tests-srcs", cmd: "$(location systemfeatures-gen-tool) com.android.systemfeatures.RwNoFeatures --readonly=false > $(location RwNoFeatures.java) && " + - "$(location systemfeatures-gen-tool) com.android.systemfeatures.RoNoFeatures --readonly=true > $(location RoNoFeatures.java) && " + + "$(location systemfeatures-gen-tool) com.android.systemfeatures.RoNoFeatures --readonly=true --feature-apis=WATCH > $(location RoNoFeatures.java) && " + "$(location systemfeatures-gen-tool) com.android.systemfeatures.RwFeatures --readonly=false --feature=WATCH:1 --feature=WIFI:0 --feature=VULKAN:-1 --feature=AUTO: > $(location RwFeatures.java) && " + - "$(location systemfeatures-gen-tool) com.android.systemfeatures.RoFeatures --readonly=true --feature=WATCH:1 --feature=WIFI:0 --feature=VULKAN:-1 --feature=AUTO: > $(location RoFeatures.java)", + "$(location systemfeatures-gen-tool) com.android.systemfeatures.RoFeatures --readonly=true --feature=WATCH:1 --feature=WIFI:0 --feature=VULKAN:-1 --feature=AUTO: --feature-apis=WATCH,PC > $(location RoFeatures.java)", out: [ "RwNoFeatures.java", "RoNoFeatures.java", diff --git a/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt b/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt index 9bfda451067f..e537ffcb56bd 100644 --- a/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt +++ b/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt @@ -32,6 +32,7 @@ import javax.lang.model.element.Modifier * <pre> * <cmd> com.foo.RoSystemFeatures --readonly=true \ * --feature=WATCH:0 --feature=AUTOMOTIVE: --feature=VULKAN:9348 + * --feature-apis=WATCH,PC,LEANBACK * </pre> * * This generates a class that has the following signature: @@ -45,12 +46,15 @@ import javax.lang.model.element.Modifier * public static boolean hasFeatureAutomotive(Context context); * @AssumeTrueForR8 * public static boolean hasFeatureVulkan(Context context); + * public static boolean hasFeaturePc(Context context); + * public static boolean hasFeatureLeanback(Context context); * public static Boolean maybeHasFeature(String feature, int version); * } * </pre> */ object SystemFeaturesGenerator { private const val FEATURE_ARG = "--feature=" + private const val FEATURE_APIS_ARG = "--feature-apis=" private const val READONLY_ARG = "--readonly=" private val PACKAGEMANAGER_CLASS = ClassName.get("android.content.pm", "PackageManager") private val CONTEXT_CLASS = ClassName.get("android.content", "Context") @@ -64,6 +68,15 @@ object SystemFeaturesGenerator { println(" Options:") println(" --readonly=true|false Whether to encode features as build-time constants") println(" --feature=\$NAME:\$VER A feature+version pair (blank version == disabled)") + println(" This will always generate associated query APIs,") + println(" adding to or replacing those from `--feature-apis=`.") + println(" --feature-apis=\$NAME_1,\$NAME_2") + println(" A comma-separated set of features for which to always") + println(" generate named query APIs. If a feature in this set is") + println(" not explicitly defined via `--feature=`, then a simple") + println(" runtime passthrough API will be generated, regardless") + println(" of the `--readonly` flag. This allows decoupling the") + println(" API surface from variations in device feature sets.") } /** Main entrypoint for build-time system feature codegen. */ @@ -76,18 +89,42 @@ object SystemFeaturesGenerator { var readonly = false var outputClassName: ClassName? = null - val features = mutableListOf<FeatureInfo>() + val featureArgs = mutableListOf<FeatureArg>() + // We could just as easily hardcode this list, as the static API surface should change + // somewhat infrequently, but this decouples the codegen from the framework completely. + val featureApiArgs = mutableSetOf<String>() for (arg in args) { when { arg.startsWith(READONLY_ARG) -> readonly = arg.substring(READONLY_ARG.length).toBoolean() arg.startsWith(FEATURE_ARG) -> { - features.add(parseFeatureArg(arg)) + featureArgs.add(parseFeatureArg(arg)) + } + arg.startsWith(FEATURE_APIS_ARG) -> { + featureApiArgs.addAll( + arg.substring(FEATURE_APIS_ARG.length).split(",").map { + parseFeatureName(it) + } + ) } else -> outputClassName = ClassName.bestGuess(arg) } } + // First load in all of the feature APIs we want to generate. Explicit feature definitions + // will then override this set with the appropriate readonly and version value. + val features = mutableMapOf<String, FeatureInfo>() + featureApiArgs.associateByTo( + features, + { it }, + { FeatureInfo(it, version = null, readonly = false) }, + ) + featureArgs.associateByTo( + features, + { it.name }, + { FeatureInfo(it.name, it.version, readonly) }, + ) + outputClassName ?: run { println("Output class name must be provided.") @@ -100,8 +137,8 @@ object SystemFeaturesGenerator { .addModifiers(Modifier.PUBLIC, Modifier.FINAL) .addJavadoc("@hide") - addFeatureMethodsToClass(classBuilder, readonly, features) - addMaybeFeatureMethodToClass(classBuilder, readonly, features) + addFeatureMethodsToClass(classBuilder, features.values) + addMaybeFeatureMethodToClass(classBuilder, features.values) // TODO(b/203143243): Add validation of build vs runtime values to ensure consistency. JavaFile.builder(outputClassName.packageName(), classBuilder.build()) @@ -115,13 +152,16 @@ object SystemFeaturesGenerator { * * "--feature=WATCH:7" -> Feature enabled w/ version 7 * * "--feature=WATCH:" -> Feature disabled */ - private fun parseFeatureArg(arg: String): FeatureInfo { + private fun parseFeatureArg(arg: String): FeatureArg { val featureArgs = arg.substring(FEATURE_ARG.length).split(":") - val name = featureArgs[0].let { if (!it.startsWith("FEATURE_")) "FEATURE_$it" else it } + val name = parseFeatureName(featureArgs[0]) val version = featureArgs.getOrNull(1)?.toIntOrNull() - return FeatureInfo(name, version) + return FeatureArg(name, version) } + private fun parseFeatureName(name: String): String = + if (name.startsWith("FEATURE_")) name else "FEATURE_$name" + /* * Adds per-feature query methods to the class with the form: * {@code public static boolean hasFeatureX(Context context)}, @@ -129,8 +169,7 @@ object SystemFeaturesGenerator { */ private fun addFeatureMethodsToClass( builder: TypeSpec.Builder, - readonly: Boolean, - features: List<FeatureInfo> + features: Collection<FeatureInfo>, ) { for (feature in features) { // Turn "FEATURE_FOO" into "hasFeatureFoo". @@ -142,7 +181,7 @@ object SystemFeaturesGenerator { .returns(Boolean::class.java) .addParameter(CONTEXT_CLASS, "context") - if (readonly) { + if (feature.readonly) { val featureEnabled = compareValues(feature.version, 0) >= 0 methodBuilder.addAnnotation( if (featureEnabled) ASSUME_TRUE_CLASS else ASSUME_FALSE_CLASS @@ -158,19 +197,17 @@ object SystemFeaturesGenerator { builder.addMethod(methodBuilder.build()) } - if (!readonly) { - builder.addMethod( - MethodSpec.methodBuilder("hasFeatureFallback") - .addModifiers(Modifier.PRIVATE, Modifier.STATIC) - .returns(Boolean::class.java) - .addParameter(CONTEXT_CLASS, "context") - .addParameter(String::class.java, "featureName") - .addStatement( - "return context.getPackageManager().hasSystemFeature(featureName, 0)" - ) - .build() - ) - } + // This is a trivial method, even if unused based on readonly-codegen, it does little harm + // to always include it. + builder.addMethod( + MethodSpec.methodBuilder("hasFeatureFallback") + .addModifiers(Modifier.PRIVATE, Modifier.STATIC) + .returns(Boolean::class.java) + .addParameter(CONTEXT_CLASS, "context") + .addParameter(String::class.java, "featureName") + .addStatement("return context.getPackageManager().hasSystemFeature(featureName, 0)") + .build() + ) } /* @@ -185,8 +222,7 @@ object SystemFeaturesGenerator { */ private fun addMaybeFeatureMethodToClass( builder: TypeSpec.Builder, - readonly: Boolean, - features: List<FeatureInfo> + features: Collection<FeatureInfo>, ) { val methodBuilder = MethodSpec.methodBuilder("maybeHasFeature") @@ -196,16 +232,27 @@ object SystemFeaturesGenerator { .addParameter(String::class.java, "featureName") .addParameter(Int::class.java, "version") - if (readonly) { - methodBuilder.beginControlFlow("switch (featureName)") - for (feature in features) { - methodBuilder.addCode("case \$T.\$N: ", PACKAGEMANAGER_CLASS, feature.name) - if (feature.version != null) { - methodBuilder.addStatement("return \$L >= version", feature.version) - } else { - methodBuilder.addStatement("return false") - } + var hasSwitchBlock = false + for (feature in features) { + // We only return non-null results for queries against readonly-defined features. + if (!feature.readonly) { + continue + } + if (!hasSwitchBlock) { + // As an optimization, only create the switch block if needed. Even an empty + // switch-on-string block can induce a hash, which we can avoid if readonly + // support is completely disabled. + hasSwitchBlock = true + methodBuilder.beginControlFlow("switch (featureName)") } + methodBuilder.addCode("case \$T.\$N: ", PACKAGEMANAGER_CLASS, feature.name) + if (feature.version != null) { + methodBuilder.addStatement("return \$L >= version", feature.version) + } else { + methodBuilder.addStatement("return false") + } + } + if (hasSwitchBlock) { methodBuilder.addCode("default: ") methodBuilder.addStatement("break") methodBuilder.endControlFlow() @@ -214,5 +261,7 @@ object SystemFeaturesGenerator { builder.addMethod(methodBuilder.build()) } - private data class FeatureInfo(val name: String, val version: Int?) + private data class FeatureArg(val name: String, val version: Int?) + + private data class FeatureInfo(val name: String, val version: Int?, val readonly: Boolean) } diff --git a/tools/systemfeatures/tests/PackageManager.java b/tools/systemfeatures/tests/PackageManager.java index 645d500bc762..db670482065a 100644 --- a/tools/systemfeatures/tests/PackageManager.java +++ b/tools/systemfeatures/tests/PackageManager.java @@ -19,6 +19,7 @@ package android.content.pm; /** Stub for testing */ public class PackageManager { public static final String FEATURE_AUTO = "automotive"; + public static final String FEATURE_PC = "pc"; public static final String FEATURE_VULKAN = "vulkan"; public static final String FEATURE_WATCH = "watch"; public static final String FEATURE_WIFI = "wifi"; diff --git a/tools/systemfeatures/tests/SystemFeaturesGeneratorTest.java b/tools/systemfeatures/tests/SystemFeaturesGeneratorTest.java index 547d2cbd26f9..6dfd244a807b 100644 --- a/tools/systemfeatures/tests/SystemFeaturesGeneratorTest.java +++ b/tools/systemfeatures/tests/SystemFeaturesGeneratorTest.java @@ -68,6 +68,13 @@ public class SystemFeaturesGeneratorTest { assertThat(RoNoFeatures.maybeHasFeature(PackageManager.FEATURE_VULKAN, 0)).isNull(); assertThat(RoNoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, 0)).isNull(); assertThat(RoNoFeatures.maybeHasFeature("com.arbitrary.feature", 0)).isNull(); + + // Also ensure we fall back to the PackageManager for feature APIs without an accompanying + // versioned feature definition. + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH, 0)).thenReturn(true); + assertThat(RwFeatures.hasFeatureWatch(mContext)).isTrue(); + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH, 0)).thenReturn(false); + assertThat(RwFeatures.hasFeatureWatch(mContext)).isFalse(); } @Test @@ -127,6 +134,16 @@ public class SystemFeaturesGeneratorTest { assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, 0)).isFalse(); assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, 100)).isFalse(); + // For feature APIs without an associated feature definition, conditional queries should + // report null, and explicit queries should report runtime-defined versions. + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_PC, 0)).thenReturn(true); + assertThat(RoFeatures.hasFeaturePc(mContext)).isTrue(); + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_PC, 0)).thenReturn(false); + assertThat(RoFeatures.hasFeaturePc(mContext)).isFalse(); + assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_PC, -1)).isNull(); + assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_PC, 0)).isNull(); + assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_PC, 100)).isNull(); + // For undefined types, conditional queries should report null (unknown). assertThat(RoFeatures.maybeHasFeature("com.arbitrary.feature", -1)).isNull(); assertThat(RoFeatures.maybeHasFeature("com.arbitrary.feature", 0)).isNull(); |