Merge "BinderProxyTest: more time to get proxy" into main
diff --git a/apct-tests/perftests/aconfig/Android.bp b/apct-tests/perftests/aconfig/Android.bp
new file mode 100644
index 0000000..715923d
--- /dev/null
+++ b/apct-tests/perftests/aconfig/Android.bp
@@ -0,0 +1,39 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_team: "trendy_team_android_core_experiments",
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "AconfigPerfTests",
+ srcs: ["src/**/*.java"],
+ static_libs: [
+ "aconfig_device_paths_java_util",
+ "androidx.test.rules",
+ "apct-perftests-utils",
+ "collector-device-lib",
+ "truth",
+ ],
+ platform_apis: true,
+ certificate: "platform",
+ test_suites: ["device-tests"],
+ data: [":perfetto_artifacts"],
+}
diff --git a/apct-tests/perftests/aconfig/AndroidManifest.xml b/apct-tests/perftests/aconfig/AndroidManifest.xml
new file mode 100644
index 0000000..e9d7c17
--- /dev/null
+++ b/apct-tests/perftests/aconfig/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.perftests.aconfig">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.perftests.aconfig"/>
+
+</manifest>
\ No newline at end of file
diff --git a/apct-tests/perftests/aconfig/AndroidTest.xml b/apct-tests/perftests/aconfig/AndroidTest.xml
new file mode 100644
index 0000000..036e031
--- /dev/null
+++ b/apct-tests/perftests/aconfig/AndroidTest.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs AconfigPerfTests metric instrumentation.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-metric-instrumentation" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="AconfigPerfTests.apk" />
+ </target_preparer>
+
+ <!-- Needed for pushing the trace config file -->
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="push-file" key="trace_config_detailed.textproto" value="/data/misc/perfetto-traces/trace_config.textproto" />
+ </target_preparer>
+
+
+ <!-- Needed for pulling the collected trace config on to the host -->
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="pull-pattern-keys" value="perfetto_file_path" />
+ </metrics_collector>
+
+ <!-- Needed for storing the perfetto trace files in the sdcard/test_results-->
+ <option name="isolated-storage" value="false" />
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.perftests.aconfig" />
+ <option name="hidden-api-checks" value="false"/>
+
+ <!-- Listener related args for collecting the traces and waiting for the device to stabilize. -->
+ <option name="device-listeners" value="android.device.collectors.ProcLoadListener,android.device.collectors.PerfettoListener" />
+ <!-- Guarantee that user defined RunListeners will be running before any of the default listeners defined in this runner. -->
+ <option name="instrumentation-arg" key="newRunListenerMode" value="true" />
+
+ <!-- ProcLoadListener related arguments -->
+ <!-- Wait for device last minute threshold to reach 3 with 2 minute timeout before starting the test run -->
+ <option name="instrumentation-arg" key="procload-collector:per_run" value="true" />
+ <option name="instrumentation-arg" key="proc-loadavg-threshold" value="3" />
+ <option name="instrumentation-arg" key="proc-loadavg-timeout" value="120000" />
+ <option name="instrumentation-arg" key="proc-loadavg-interval" value="10000" />
+
+ <!-- PerfettoListener related arguments -->
+ <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true" />
+ <option name="instrumentation-arg" key="perfetto_config_file" value="trace_config.textproto" />
+
+ <option name="instrumentation-arg" key="newRunListenerMode" value="true" />
+
+ </test>
+</configuration>
\ No newline at end of file
diff --git a/apct-tests/perftests/aconfig/OWNERS b/apct-tests/perftests/aconfig/OWNERS
new file mode 100644
index 0000000..2202076
--- /dev/null
+++ b/apct-tests/perftests/aconfig/OWNERS
@@ -0,0 +1 @@
+file:platform/packages/modules/ConfigInfrastructure:/OWNERS
\ No newline at end of file
diff --git a/apct-tests/perftests/aconfig/src/android/os/flagging/AconfigPackagePerfTest.java b/apct-tests/perftests/aconfig/src/android/os/flagging/AconfigPackagePerfTest.java
new file mode 100644
index 0000000..df6e3c8
--- /dev/null
+++ b/apct-tests/perftests/aconfig/src/android/os/flagging/AconfigPackagePerfTest.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 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 android.os.flagging;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.aconfig.DeviceProtosTestUtil;
+import android.aconfig.nano.Aconfig.parsed_flag;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+@RunWith(Parameterized.class)
+public class AconfigPackagePerfTest {
+
+ @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ @Parameterized.Parameters(name = "isPlatform={0}")
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][] {{false}, {true}});
+ }
+
+ private static final Set<String> PLATFORM_CONTAINERS = Set.of("system", "vendor", "product");
+ private static List<parsed_flag> sFlags;
+
+ @BeforeClass
+ public static void init() {
+ try {
+ sFlags = DeviceProtosTestUtil.loadAndParseFlagProtos();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Parameterized.Parameter(0)
+
+ // if this variable is true, then the test query flags from system/product/vendor
+ // if this variable is false, then the test query flags from updatable partitions
+ public boolean mIsPlatform;
+
+ @Test
+ public void timeAconfigPackageLoadOnePackage() {
+ String packageName = "";
+ for (parsed_flag flag : sFlags) {
+ if (mIsPlatform == PLATFORM_CONTAINERS.contains(flag.container)) {
+ packageName = flag.package_;
+ break;
+ }
+ }
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ AconfigPackage.load(packageName);
+ }
+ }
+
+ @Test
+ public void timeAconfigPackageLoadMultiplePackages() {
+ // load num packages
+ int packageNum = 25;
+ Set<String> packageSet = new HashSet<>();
+ for (parsed_flag flag : sFlags) {
+ if (mIsPlatform == PLATFORM_CONTAINERS.contains(flag.container)) {
+ packageSet.add(flag.package_);
+ }
+ if (packageSet.size() >= packageNum) {
+ break;
+ }
+ }
+ List<String> packageList = new ArrayList(packageSet);
+ assertThat(packageList.size()).isAtLeast(packageNum);
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ for (int i = 0; state.keepRunning(); i++) {
+ AconfigPackage.load(packageList.get(i % packageNum));
+ }
+ }
+
+ @Test
+ public void timeAconfigPackageGetBooleanFlagValue() {
+ // get one package contains num of flags
+ int flagNum = 20;
+ List<parsed_flag> l = findNumFlagsInSamePackage(flagNum, mIsPlatform);
+ List<String> flagName = new ArrayList<>();
+ String packageName = l.get(0).package_;
+ for (parsed_flag flag : l) {
+ flagName.add(flag.name);
+ }
+ assertThat(flagName.size()).isAtLeast(flagNum);
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ AconfigPackage ap = AconfigPackage.load(packageName);
+ for (int i = 0; state.keepRunning(); i++) {
+ ap.getBooleanFlagValue(flagName.get(i % flagNum), false);
+ }
+ }
+
+ private static List<parsed_flag> findNumFlagsInSamePackage(int num, boolean isPlatform) {
+ Map<String, List<parsed_flag>> packageToFlag = new HashMap<>();
+ List<parsed_flag> ret = new ArrayList<parsed_flag>();
+ for (parsed_flag flag : sFlags) {
+ if (isPlatform == PLATFORM_CONTAINERS.contains(flag.container)) {
+ ret =
+ packageToFlag.computeIfAbsent(
+ flag.package_, k -> new ArrayList<parsed_flag>());
+ ret.add(flag);
+ if (ret.size() >= num) {
+ break;
+ }
+ }
+ }
+ return ret;
+ }
+}
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index 6e4c28f..7a811a1 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -28,6 +28,7 @@
per-file Service* = file:/ACTIVITY_MANAGER_OWNERS
per-file SystemServiceRegistry.java = file:/ACTIVITY_MANAGER_OWNERS
per-file *UserSwitchObserver* = file:/ACTIVITY_MANAGER_OWNERS
+per-file UidObserver* = file:/ACTIVITY_MANAGER_OWNERS
# UI Automation
per-file *UiAutomation* = file:/services/accessibility/OWNERS
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index e98397d..cc6ec08 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -1716,20 +1716,14 @@
private static int checkPermissionUncached(@Nullable String permission, int pid, int uid,
int deviceId) {
+ final int appId = UserHandle.getAppId(uid);
+ if (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID) {
+ return PackageManager.PERMISSION_GRANTED;
+ }
final IActivityManager am = ActivityManager.getService();
if (am == null) {
- // Well this is super awkward; we somehow don't have an active ActivityManager
- // instance. If we're testing a root or system UID, then they totally have whatever
- // permission this is.
- final int appId = UserHandle.getAppId(uid);
- if (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID) {
- if (sShouldWarnMissingActivityManager) {
- Slog.w(LOG_TAG, "Missing ActivityManager; assuming " + uid + " holds "
- + permission);
- sShouldWarnMissingActivityManager = false;
- }
- return PackageManager.PERMISSION_GRANTED;
- }
+ // We don't have an active ActivityManager instance and the calling UID is not root or
+ // system, so we don't grant this permission.
Slog.w(LOG_TAG, "Missing ActivityManager; assuming " + uid + " does not hold "
+ permission);
return PackageManager.PERMISSION_DENIED;
diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
index 7ad18b8..917d501 100644
--- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
+++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
@@ -306,8 +306,9 @@
when, uncompLen, crc);
}
- ALOGE("Library '%s' is not PAGE(%zu)-aligned - will not be able to open it directly "
- "from apk.\n",
+ ALOGE("extractNativeLibs=false library '%s' is not PAGE(%zu)-"
+ "aligned within apk (APK alignment, not ELF alignment) -"
+ "will not be able to open it directly from apk.\n",
fileName, kPageSize);
return INSTALL_FAILED_INVALID_APK;
}
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index 1bc15d7..a13dd78 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -80,6 +80,7 @@
"LoadedArsc.cpp",
"Locale.cpp",
"LocaleData.cpp",
+ "LocaleDataLookup.cpp",
"misc.cpp",
"NinePatch.cpp",
"ObbFile.cpp",
@@ -224,6 +225,7 @@
"tests/Idmap_test.cpp",
"tests/LoadedArsc_test.cpp",
"tests/Locale_test.cpp",
+ "tests/LocaleDataLookup_test.cpp",
"tests/NinePatch_test.cpp",
"tests/ResourceTimer_test.cpp",
"tests/ResourceUtils_test.cpp",
diff --git a/libs/androidfw/LocaleData.cpp b/libs/androidfw/LocaleData.cpp
index 020cef6..1b23d90 100644
--- a/libs/androidfw/LocaleData.cpp
+++ b/libs/androidfw/LocaleData.cpp
@@ -23,39 +23,18 @@
#include <unordered_set>
#include <androidfw/LocaleData.h>
+#include <androidfw/LocaleDataLookup.h>
namespace android {
-#include "LocaleDataTables.cpp"
-
-inline uint32_t packLocale(const char* language, const char* region) {
- return (((uint8_t) language[0]) << 24u) | (((uint8_t) language[1]) << 16u) |
- (((uint8_t) region[0]) << 8u) | ((uint8_t) region[1]);
-}
-
-inline uint32_t dropRegion(uint32_t packed_locale) {
- return packed_locale & 0xFFFF0000LU;
-}
-
-inline bool hasRegion(uint32_t packed_locale) {
- return (packed_locale & 0x0000FFFFLU) != 0;
-}
-
-const size_t SCRIPT_LENGTH = 4;
-const size_t SCRIPT_PARENTS_COUNT = sizeof(SCRIPT_PARENTS)/sizeof(SCRIPT_PARENTS[0]);
const uint32_t PACKED_ROOT = 0; // to represent the root locale
+const uint32_t MAX_PARENT_DEPTH = getMaxAncestorTreeDepth();
uint32_t findParent(uint32_t packed_locale, const char* script) {
if (hasRegion(packed_locale)) {
- for (size_t i = 0; i < SCRIPT_PARENTS_COUNT; i++) {
- if (memcmp(script, SCRIPT_PARENTS[i].script, SCRIPT_LENGTH) == 0) {
- auto map = SCRIPT_PARENTS[i].map;
- auto lookup_result = map->find(packed_locale);
- if (lookup_result != map->end()) {
- return lookup_result->second;
- }
- break;
- }
+ auto parent_key = findParentLocalePackedKey(script, packed_locale);
+ if (parent_key != 0) {
+ return parent_key;
}
return dropRegion(packed_locale);
}
@@ -111,17 +90,6 @@
return supported_ancestor_count + request_ancestors_index - 1;
}
-inline bool isRepresentative(uint32_t language_and_region, const char* script) {
- const uint64_t packed_locale = (
- (((uint64_t) language_and_region) << 32u) |
- (((uint64_t) script[0]) << 24u) |
- (((uint64_t) script[1]) << 16u) |
- (((uint64_t) script[2]) << 8u) |
- ((uint64_t) script[3]));
-
- return (REPRESENTATIVE_LOCALES.count(packed_locale) != 0);
-}
-
const uint32_t US_SPANISH = 0x65735553LU; // es-US
const uint32_t MEXICAN_SPANISH = 0x65734D58LU; // es-MX
const uint32_t LATIN_AMERICAN_SPANISH = 0x6573A424LU; // es-419
@@ -185,8 +153,8 @@
// If we are here, left and right are equidistant from the request. We will
// try and see if any of them is a representative locale.
- const bool left_is_representative = isRepresentative(left, requested_script);
- const bool right_is_representative = isRepresentative(right, requested_script);
+ const bool left_is_representative = isLocaleRepresentative(left, requested_script);
+ const bool right_is_representative = isLocaleRepresentative(right, requested_script);
if (left_is_representative != right_is_representative) {
return (int) left_is_representative - (int) right_is_representative;
}
@@ -204,14 +172,14 @@
return;
}
uint32_t lookup_key = packLocale(language, region);
- auto lookup_result = LIKELY_SCRIPTS.find(lookup_key);
- if (lookup_result == LIKELY_SCRIPTS.end()) {
+ auto lookup_result = lookupLikelyScript(lookup_key);
+ if (lookup_result == nullptr) {
// We couldn't find the locale. Let's try without the region
if (region[0] != '\0') {
lookup_key = dropRegion(lookup_key);
- lookup_result = LIKELY_SCRIPTS.find(lookup_key);
- if (lookup_result != LIKELY_SCRIPTS.end()) {
- memcpy(out, SCRIPT_CODES[lookup_result->second], SCRIPT_LENGTH);
+ lookup_result = lookupLikelyScript(lookup_key);
+ if (lookup_result != nullptr) {
+ memcpy(out, lookup_result, SCRIPT_LENGTH);
return;
}
}
@@ -220,7 +188,7 @@
return;
} else {
// We found the locale.
- memcpy(out, SCRIPT_CODES[lookup_result->second], SCRIPT_LENGTH);
+ memcpy(out, lookup_result, SCRIPT_LENGTH);
}
}
diff --git a/libs/androidfw/LocaleDataLookup.cpp b/libs/androidfw/LocaleDataLookup.cpp
new file mode 100644
index 0000000..5441e22
--- /dev/null
+++ b/libs/androidfw/LocaleDataLookup.cpp
@@ -0,0 +1,64 @@
+/*
+ * 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.
+ */
+
+#include <unordered_map>
+#include <unordered_set>
+
+#include <androidfw/LocaleDataLookup.h>
+
+namespace android {
+
+#include "LocaleDataTables.cpp"
+
+const size_t SCRIPT_PARENTS_COUNT = sizeof(SCRIPT_PARENTS)/sizeof(SCRIPT_PARENTS[0]);
+
+const char* lookupLikelyScript(uint32_t packed_lang_region) {
+
+ auto lookup_result = LIKELY_SCRIPTS.find(packed_lang_region);
+ if (lookup_result == LIKELY_SCRIPTS.end()) {
+ return nullptr;
+ } else {
+ return SCRIPT_CODES[lookup_result->second];
+ }
+}
+
+uint32_t findParentLocalePackedKey(const char* script, uint32_t packed_lang_region) {
+ for (size_t i = 0; i < SCRIPT_PARENTS_COUNT; i++) {
+ if (memcmp(script, SCRIPT_PARENTS[i].script, SCRIPT_LENGTH) == 0) {
+ auto map = SCRIPT_PARENTS[i].map;
+ auto lookup_result = map->find(packed_lang_region);
+ if (lookup_result != map->end()) {
+ return lookup_result->second;
+ }
+ break;
+ }
+ }
+ return 0;
+}
+
+uint32_t getMaxAncestorTreeDepth() {
+ return MAX_PARENT_DEPTH;
+}
+
+namespace hidden {
+
+bool isRepresentative(uint64_t packed_locale) {
+ return (REPRESENTATIVE_LOCALES.count(packed_locale) != 0);
+}
+
+} // namespace hidden
+
+} // namespace android
diff --git a/libs/androidfw/include/androidfw/LocaleDataLookup.h b/libs/androidfw/include/androidfw/LocaleDataLookup.h
new file mode 100644
index 0000000..7fde712
--- /dev/null
+++ b/libs/androidfw/include/androidfw/LocaleDataLookup.h
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <stddef.h>
+#include <stdint.h>
+
+
+namespace android {
+
+namespace hidden {
+ bool isRepresentative(uint64_t packed_locale);
+}
+
+constexpr size_t SCRIPT_LENGTH = 4;
+
+constexpr inline uint32_t packLocale(const char* language, const char* region) {
+ const unsigned char* lang = reinterpret_cast<const unsigned char*>(language);
+ const unsigned char* reg = reinterpret_cast<const unsigned char*>(region);
+ return (static_cast<uint32_t>(lang[0]) << 24u) |
+ (static_cast<uint32_t>(lang[1]) << 16u) |
+ (static_cast<uint32_t>(reg[0]) << 8u) |
+ static_cast<uint32_t>(reg[1]);
+}
+
+constexpr inline uint32_t dropRegion(uint32_t packed_locale) {
+ return packed_locale & 0xFFFF0000LU;
+}
+
+constexpr inline bool hasRegion(uint32_t packed_locale) {
+ return (packed_locale & 0x0000FFFFLU) != 0;
+}
+
+/**
+ * Return nullptr if the key isn't found. The input packed_lang_region can be computed
+ * by android::packLocale.
+ * Note that the returned char* is either nullptr or 4-byte char seqeuence, but isn't
+ * a null-terminated string.
+ */
+const char* lookupLikelyScript(uint32_t packed_lang_region);
+/**
+ * Return false if the key isn't representative. The input lookup key can be computed
+ * by android::packLocale.
+ */
+bool inline isLocaleRepresentative(uint32_t language_and_region, const char* script) {
+ const unsigned char* s = reinterpret_cast<const unsigned char*>(script);
+ const uint64_t packed_locale = (
+ ((static_cast<uint64_t>(language_and_region)) << 32u) |
+ (static_cast<uint64_t>(s[0]) << 24u) |
+ (static_cast<uint64_t>(s[1]) << 16u) |
+ (static_cast<uint64_t>(s[2]) << 8u) |
+ static_cast<uint64_t>(s[3]));
+
+ return hidden::isRepresentative(packed_locale);
+}
+
+/**
+ * Return a parent packed key for a given script and child packed key. Return 0 if
+ * no parent is found.
+ */
+uint32_t findParentLocalePackedKey(const char* script, uint32_t packed_lang_region);
+
+uint32_t getMaxAncestorTreeDepth();
+
+} // namespace android
diff --git a/libs/androidfw/tests/LocaleDataLookup_test.cpp b/libs/androidfw/tests/LocaleDataLookup_test.cpp
new file mode 100644
index 0000000..26b220d
--- /dev/null
+++ b/libs/androidfw/tests/LocaleDataLookup_test.cpp
@@ -0,0 +1,108 @@
+/*
+ * 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.
+ */
+
+#include "androidfw/LocaleDataLookup.h"
+
+#include <cstddef>
+#include <string>
+
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+
+
+namespace android {
+
+constexpr const char NULL_SCRIPT[4] = {'\0', '\0', '\0','\0' };
+
+#define EXPECT_SCEIPT_EQ(ex, s) EXPECT_EQ(0, s == nullptr ? -1 : memcmp(ex, s, 4))
+
+// Similar to packLanguageOrRegion() in ResourceTypes.cpp
+static uint32_t encodeLanguageOrRegionLiteral(const char* in, const char base) {
+ size_t len = strlen(in);
+ if (len <= 1) {
+ return 0;
+ }
+
+ if (len == 2) {
+ return (((uint8_t) in[0]) << 8) | ((uint8_t) in[1]);
+ }
+ uint8_t first = (in[0] - base) & 0x007f;
+ uint8_t second = (in[1] - base) & 0x007f;
+ uint8_t third = (in[2] - base) & 0x007f;
+
+ return ((uint8_t) (0x80 | (third << 2) | (second >> 3)) << 8) | ((second << 5) | first);
+}
+
+static uint32_t encodeLocale(const char* language, const char* region) {
+ return (encodeLanguageOrRegionLiteral(language, 'a') << 16) |
+ encodeLanguageOrRegionLiteral(region, '0');
+}
+
+TEST(LocaleDataLookupTest, lookupLikelyScript) {
+ EXPECT_EQ(nullptr, lookupLikelyScript(encodeLocale("", "")));
+ EXPECT_SCEIPT_EQ("Latn", lookupLikelyScript(encodeLocale("en", "")));
+ EXPECT_EQ(nullptr, lookupLikelyScript(encodeLocale("en", "US")));
+ EXPECT_EQ(nullptr, lookupLikelyScript(encodeLocale("en", "GB")));
+ EXPECT_SCEIPT_EQ("Latn", lookupLikelyScript(encodeLocale("fr", "")));
+ EXPECT_EQ(nullptr, lookupLikelyScript(encodeLocale("fr", "FR")));
+
+
+ EXPECT_SCEIPT_EQ("~~~A", lookupLikelyScript(encodeLocale("en", "XA")));
+ EXPECT_SCEIPT_EQ("Latn", lookupLikelyScript(encodeLocale("ha", "")));
+ EXPECT_SCEIPT_EQ("Arab", lookupLikelyScript(encodeLocale("ha", "SD")));
+ EXPECT_EQ(nullptr, lookupLikelyScript(encodeLocale("ha", "Sd"))); // case sensitive
+ EXPECT_SCEIPT_EQ("Hans", lookupLikelyScript(encodeLocale("zh", "")));
+ EXPECT_EQ(nullptr, lookupLikelyScript(encodeLocale("zh", "CN")));
+ EXPECT_SCEIPT_EQ("Hant", lookupLikelyScript(encodeLocale("zh", "HK")));
+
+ EXPECT_SCEIPT_EQ("Nshu", lookupLikelyScript(encodeLocale("zhx", "")));
+ EXPECT_SCEIPT_EQ("Nshu", lookupLikelyScript(0xDCF90000u)); // encoded "zhx"
+}
+
+TEST(LocaleDataLookupTest, isLocaleRepresentative) {
+ EXPECT_TRUE(isLocaleRepresentative(encodeLocale("en", "US"), "Latn"));
+ EXPECT_TRUE(isLocaleRepresentative(encodeLocale("en", "GB"), "Latn"));
+ EXPECT_FALSE(isLocaleRepresentative(encodeLocale("en", "US"), NULL_SCRIPT));
+ EXPECT_FALSE(isLocaleRepresentative(encodeLocale("en", ""), "Latn"));
+ EXPECT_FALSE(isLocaleRepresentative(encodeLocale("en", ""), NULL_SCRIPT));
+ EXPECT_FALSE(isLocaleRepresentative(encodeLocale("en", "US"), "Arab"));
+
+ EXPECT_TRUE(isLocaleRepresentative(encodeLocale("fr", "FR"), "Latn"));
+
+ EXPECT_TRUE(isLocaleRepresentative(encodeLocale("zh", "CN"), "Hans"));
+ EXPECT_FALSE(isLocaleRepresentative(encodeLocale("zh", "TW"), "Hans"));
+ EXPECT_FALSE(isLocaleRepresentative(encodeLocale("zhx", "CN"), "Hans"));
+ EXPECT_FALSE(isLocaleRepresentative(0xDCF9434E, "Hans"));
+ EXPECT_TRUE(isLocaleRepresentative(encodeLocale("zhx", "CN"), "Nshu"));
+ EXPECT_TRUE(isLocaleRepresentative(0xDCF9434E, "Nshu"));
+}
+
+TEST(LocaleDataLookupTest, findParentLocalePackedKey) {
+ EXPECT_EQ(encodeLocale("en", "001"), findParentLocalePackedKey("Latn", encodeLocale("en", "GB")));
+ EXPECT_EQ(0x656E8400u, findParentLocalePackedKey("Latn", encodeLocale("en", "GB")));
+
+ EXPECT_EQ(encodeLocale("en", "IN"), findParentLocalePackedKey("Deva", encodeLocale("hi", "")));
+
+ EXPECT_EQ(encodeLocale("ar", "015"), findParentLocalePackedKey("Arab", encodeLocale("ar", "AE")));
+ EXPECT_EQ(0x61729420u, findParentLocalePackedKey("Arab", encodeLocale("ar", "AE")));
+
+ EXPECT_EQ(encodeLocale("ar", "015"), findParentLocalePackedKey("~~~B", encodeLocale("ar", "XB")));
+ EXPECT_EQ(0x61729420u, findParentLocalePackedKey("Arab", encodeLocale("ar", "AE")));
+
+ EXPECT_EQ(encodeLocale("zh", "HK"), findParentLocalePackedKey("Hant", encodeLocale("zh", "MO")));
+}
+
+} // namespace android
diff --git a/services/core/java/com/android/server/am/OWNERS b/services/core/java/com/android/server/am/OWNERS
index d731912..ebe1fe8 100644
--- a/services/core/java/com/android/server/am/OWNERS
+++ b/services/core/java/com/android/server/am/OWNERS
@@ -21,6 +21,7 @@
per-file HostingRecord.java = file:/ACTIVITY_MANAGER_OWNERS
per-file App*ExitInfo* = file:/ACTIVITY_MANAGER_OWNERS
per-file appexitinfo.proto = file:/ACTIVITY_MANAGER_OWNERS
+per-file UidObserverController* = file:/ACTIVITY_MANAGER_OWNERS
per-file App*StartInfo* = file:/PERFORMANCE_OWNERS
per-file appstartinfo.proto = file:/PERFORMANCE_OWNERS
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index c314ab0..3f91575 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -369,16 +369,7 @@
@Override
public void onBootPhase(int phase) {
super.onBootPhase(phase);
- if (phase == PHASE_ACTIVITY_MANAGER_READY) {
- mLockSettingsService.migrateOldDataAfterSystemReady();
- mLockSettingsService.deleteRepairModePersistentDataIfNeeded();
- } else if (phase == PHASE_BOOT_COMPLETED) {
- // In the case of an upgrade, PHASE_BOOT_COMPLETED means that a rollback to the old
- // build can no longer occur. This is the time to destroy any migrated protectors.
- mLockSettingsService.destroyMigratedProtectors();
-
- mLockSettingsService.loadEscrowData();
- }
+ mLockSettingsService.onBootPhase(phase);
}
@Override
@@ -397,6 +388,21 @@
}
}
+ private void onBootPhase(int phase) {
+ if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
+ migrateOldDataAfterSystemReady();
+ deleteRepairModePersistentDataIfNeeded();
+ } else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
+ mHandler.post(() -> {
+ // In the case of an upgrade, PHASE_BOOT_COMPLETED means that a rollback to the old
+ // build can no longer occur. This is the time to destroy any migrated protectors.
+ destroyMigratedProtectors();
+
+ loadEscrowData();
+ });
+ }
+ }
+
@VisibleForTesting
protected static class SynchronizedStrongAuthTracker
extends LockPatternUtils.StrongAuthTracker {
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index cb8e1a0..d009fa0 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -703,34 +703,29 @@
@Override
public void handleMessage(Message msg) {
BatteryCallback cb = mCallback;
+ if (cb == null) {
+ return;
+ }
switch (msg.what) {
case MSG_REPORT_CPU_UPDATE_NEEDED:
- if (cb != null) {
- cb.batteryNeedsCpuUpdate();
- }
+ cb.batteryNeedsCpuUpdate();
break;
case MSG_REPORT_POWER_CHANGE:
- if (cb != null) {
- cb.batteryPowerChanged(msg.arg1 != 0);
- }
+ cb.batteryPowerChanged(msg.arg1 != 0);
break;
case MSG_REPORT_CHARGING:
- if (cb != null) {
- final String action;
- synchronized (BatteryStatsImpl.this) {
- action = mCharging ? BatteryManager.ACTION_CHARGING
- : BatteryManager.ACTION_DISCHARGING;
- }
- Intent intent = new Intent(action);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- cb.batterySendBroadcast(intent);
+ final String action;
+ synchronized (BatteryStatsImpl.this) {
+ action = mCharging ? BatteryManager.ACTION_CHARGING
+ : BatteryManager.ACTION_DISCHARGING;
}
+ Intent intent = new Intent(action);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ cb.batterySendBroadcast(intent);
break;
case MSG_REPORT_RESET_STATS:
- if (cb != null) {
- cb.batteryStatsReset();
- }
- }
+ cb.batteryStatsReset();
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index e178203..8763c8f 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -100,7 +100,8 @@
// isLeashReadyForDispatching (used to dispatch the leash of the control) is
// depending on mGivenInsetsReady. Therefore, triggering notifyControlChanged here
// again, so that the control with leash can be eventually dispatched
- if (!mGivenInsetsReady && mServerVisible && !givenInsetsPending) {
+ if (!mGivenInsetsReady && mServerVisible && !givenInsetsPending
+ && mControlTarget != null) {
mGivenInsetsReady = true;
ImeTracker.forLogging().onProgress(mStatsToken,
ImeTracker.PHASE_WM_POST_LAYOUT_NOTIFY_CONTROLS_CHANGED);
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index d3cae4c..8d7447c 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -379,7 +379,7 @@
final boolean serverVisibleChanged = mServerVisible != isServerVisible;
setServerVisible(isServerVisible);
final boolean positionChanged = updateInsetsControlPosition(windowState);
- if (mControl != null && !positionChanged
+ if (mControl != null && mControlTarget != null && !positionChanged
// The insets hint would be updated if the position is changed. Here updates it for
// the possible change of the bounds or the server visibility.
&& (updateInsetsHint()
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 3e39a45..6ae2341 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -373,7 +373,7 @@
array.add(provider);
}
- void notifyControlChanged(InsetsControlTarget target, InsetsSourceProvider provider) {
+ void notifyControlChanged(@NonNull InsetsControlTarget target, InsetsSourceProvider provider) {
addToPendingControlMaps(target, provider);
notifyPendingInsetsControlChanged();
diff --git a/tools/systemfeatures/Android.bp b/tools/systemfeatures/Android.bp
index 2ebede3..87ea5db 100644
--- a/tools/systemfeatures/Android.bp
+++ b/tools/systemfeatures/Android.bp
@@ -100,3 +100,72 @@
unit_test: true,
},
}
+
+java_library_host {
+ name: "systemfeatures-errorprone-lib",
+ srcs: [
+ ":systemfeatures-gen-metadata-srcs",
+ "errorprone/java/**/*.java",
+ ],
+ static_libs: [
+ "//external/error_prone:error_prone_core",
+ "guava",
+ "jsr305",
+ ],
+ libs: [
+ "//external/auto:auto_service_annotations",
+ ],
+ javacflags: [
+ // These exports are needed because this errorprone plugin access some private classes
+ // of the java compiler.
+ "--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
+ "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
+ "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
+ ],
+ plugins: [
+ "//external/auto:auto_service_plugin",
+ ],
+}
+
+java_plugin {
+ name: "systemfeatures-errorprone",
+ static_libs: ["systemfeatures-errorprone-lib"],
+}
+
+java_test_host {
+ name: "systemfeatures-errorprone-tests",
+ srcs: [
+ "errorprone/tests/java/**/*.java",
+ ],
+ java_resource_dirs: ["tests/src"],
+ java_resources: [
+ ":systemfeatures-errorprone-tests-data",
+ ],
+ static_libs: [
+ "compile-testing-prebuilt",
+ "error_prone_test_helpers",
+ "framework-annotations-lib",
+ "hamcrest",
+ "hamcrest-library",
+ "junit",
+ "systemfeatures-errorprone-lib",
+ "truth",
+ ],
+ test_options: {
+ unit_test: true,
+ },
+}
+
+java_system_features_srcs {
+ name: "systemfeatures-gen-metadata-srcs",
+ full_class_name: "com.android.systemfeatures.RoSystemFeaturesMetadata",
+ metadata_only: true,
+ visibility: ["//visibility:private"],
+}
+
+filegroup {
+ name: "systemfeatures-errorprone-tests-data",
+ path: "tests/src",
+ srcs: ["tests/src/android/**/*.java"],
+ visibility: ["//visibility:private"],
+}
diff --git a/tools/systemfeatures/README.md b/tools/systemfeatures/README.md
index 5836f81..b1fec1a 100644
--- a/tools/systemfeatures/README.md
+++ b/tools/systemfeatures/README.md
@@ -4,8 +4,110 @@
System features exposed from `PackageManager` are defined and aggregated as
`<feature>` xml attributes across various partitions, and are currently queried
-at runtime through the framework. This directory contains tooling that will
-support *build-time* queries of select system features, enabling optimizations
+at runtime through the framework. This directory contains tooling that supports
+*build-time* queries of select system features, enabling optimizations
like code stripping and conditionally dependencies when so configured.
-### TODO(b/203143243): Expand readme after landing codegen.
+### System Feature Codegen
+
+As not all system features can be fully specified or defined at build time (e.g.
+updatable partitisions and apex modules can change/remove such features), we
+use a conditional, build flag approach that allows a given device to customize
+the subset of build-time defined system features that are immutable and cannot
+be updated.
+
+#### Build Flags
+
+System features that can be fixed at build-time are declared in a common
+location, `build/release/flag_declarations/`. These have the form
+`RELEASE_SYSTEM_FEATURE_${X}`, where `${X}` corresponds to a feature defined in
+`PackageManager`, e.g., `TELEVISION` or `WATCH`.
+
+Build flag values can then be defined per device (or form factor), where such
+values either indicate the existence/version of the system feature, or that the
+feature is unavailable, e.g., for TV, we could define these build flag values:
+```
+name: "RELEASE_SYSTEM_FEATURE_TELEVISION"
+value: {
+ string_value: "0" # Feature version = 0
+}
+```
+```
+name: "RELEASE_SYSTEM_FEATURE_WATCH"
+value: {
+ string_value: "UNAVAILABLE"
+}
+```
+
+See also [SystemFeaturesGenerator](src/com/android/systemfeatures/SystemFeaturesGenerator.kt)
+for more details.
+
+#### Runtime Queries
+
+Each declared build flag system feature is routed into codegen, generating a
+getter API in the internal class, `com.android.internal.pm.RoSystemFeatures`:
+```
+class RoSystemFeatures {
+ ...
+ public static boolean hasFeatureX(Context context);
+ ...
+}
+```
+By default, these queries simply fall back to the usual
+`PackageManager.hasSystemFeature(...)` runtime queries. However, if a device
+defines these features via build flags, the generated code will add annotations
+indicating fixed value for this query, and adjust the generated code to return
+the value directly. This in turn enables build-time stripping and optimization.
+
+> **_NOTE:_** Any build-time defined system features will also be implicitly
+used to accelerate calls to `PackageManager.hasSystemFeature(...)` for the
+feature, avoiding binder calls when possible.
+
+#### Lint
+
+A new `ErrorProne` rule is introduced to assist with migration and maintenance
+of codegen APIs for build-time defined system features. This is defined in the
+`systemfeatures-errorprone` build rule, which can be added to any Java target's
+`plugins` list.
+
+// TODO(b/203143243): Add plugin to key system targets after initial migration.
+
+1) Add the plugin dependency to a given `${TARGET}`:
+```
+java_library {
+ name: "${TARGET}",
+ plugins: ["systemfeatures-errorprone"],
+}
+```
+2) Run locally:
+```
+RUN_ERROR_PRONE=true m ${TARGET}
+```
+3) (Optional) Update the target rule to generate in-place patch files:
+```
+java_library {
+ name: "${TARGET}",
+ plugins: ["systemfeatures-errorprone"],
+ // DO NOT SUBMIT: GENERATE IN-PLACE PATCH FILES
+ errorprone: {
+ javacflags: [
+ "-XepPatchChecks:RoSystemFeaturesChecker",
+ "-XepPatchLocation:IN_PLACE",
+ ],
+ }
+ ...
+}
+```
+```
+RUN_ERROR_PRONE=true m ${TARGET}
+```
+
+See also [RoSystemFeaturesChecker](errorprone/java/com/android/systemfeatures/errorprone/RoSystemFeaturesChecker.java)
+for more details.
+
+> **_NOTE:_** Not all system feature queries or targets need or should be
+migrated. Only system features that are explicitly declared with build flags,
+and only targets that are built with the platform (i.e., not updatable), are
+candidates for this linting and migration, e.g., SystemUI, System Server, etc...
+
+// TODO(b/203143243): Wrap the in-place lint updates with a simple script for convenience.
diff --git a/tools/systemfeatures/errorprone/java/com/android/systemfeatures/errorprone/RoSystemFeaturesChecker.java b/tools/systemfeatures/errorprone/java/com/android/systemfeatures/errorprone/RoSystemFeaturesChecker.java
new file mode 100644
index 0000000..7812377
--- /dev/null
+++ b/tools/systemfeatures/errorprone/java/com/android/systemfeatures/errorprone/RoSystemFeaturesChecker.java
@@ -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.systemfeatures.errorprone;
+
+import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
+
+import com.android.systemfeatures.RoSystemFeaturesMetadata;
+
+import com.google.auto.service.AutoService;
+import com.google.errorprone.BugPattern;
+import com.google.errorprone.VisitorState;
+import com.google.errorprone.bugpatterns.BugChecker;
+import com.google.errorprone.fixes.SuggestedFix;
+import com.google.errorprone.matchers.Description;
+import com.google.errorprone.matchers.Matcher;
+import com.google.errorprone.matchers.Matchers;
+import com.google.errorprone.util.ASTHelpers;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.tools.javac.code.Symbol;
+
+@AutoService(BugChecker.class)
+@BugPattern(
+ name = "RoSystemFeaturesChecker",
+ summary = "Use RoSystemFeature instead of PackageManager.hasSystemFeature",
+ explanation =
+ "Directly invoking `PackageManager.hasSystemFeature` is less efficient than using"
+ + " the `RoSystemFeatures` helper class. This check flags invocations like"
+ + " `context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FOO)`"
+ + " and suggests replacing them with"
+ + " `com.android.internal.pm.RoSystemFeatures.hasFeatureFoo(context)`.",
+ severity = WARNING)
+public class RoSystemFeaturesChecker extends BugChecker
+ implements BugChecker.MethodInvocationTreeMatcher {
+
+ private static final String PACKAGE_MANAGER_CLASS = "android.content.pm.PackageManager";
+ private static final String CONTEXT_CLASS = "android.content.Context";
+ private static final String RO_SYSTEM_FEATURE_SIMPLE_CLASS = "RoSystemFeatures";
+ private static final String RO_SYSTEM_FEATURE_CLASS =
+ "com.android.internal.pm." + RO_SYSTEM_FEATURE_SIMPLE_CLASS;
+ private static final String GET_PACKAGE_MANAGER_METHOD = "getPackageManager";
+ private static final String HAS_SYSTEM_FEATURE_METHOD = "hasSystemFeature";
+ private static final String FEATURE_PREFIX = "FEATURE_";
+
+ private static final Matcher<ExpressionTree> HAS_SYSTEM_FEATURE_MATCHER =
+ Matchers.instanceMethod()
+ .onDescendantOf(PACKAGE_MANAGER_CLASS)
+ .named(HAS_SYSTEM_FEATURE_METHOD)
+ .withParameters(String.class.getName());
+
+ private static final Matcher<ExpressionTree> GET_PACKAGE_MANAGER_MATCHER =
+ Matchers.instanceMethod()
+ .onDescendantOf(CONTEXT_CLASS)
+ .named(GET_PACKAGE_MANAGER_METHOD);
+
+ @Override
+ public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
+ if (!HAS_SYSTEM_FEATURE_MATCHER.matches(tree, state)) {
+ return Description.NO_MATCH;
+ }
+
+ // Check if the PackageManager was obtained from a Context instance.
+ ExpressionTree packageManager = ASTHelpers.getReceiver(tree);
+ if (!GET_PACKAGE_MANAGER_MATCHER.matches(packageManager, state)) {
+ return Description.NO_MATCH;
+ }
+
+ // Get the feature argument and check if it's a PackageManager.FEATURE_X constant.
+ ExpressionTree feature = tree.getArguments().isEmpty() ? null : tree.getArguments().get(0);
+ Symbol featureSymbol = ASTHelpers.getSymbol(feature);
+ if (featureSymbol == null
+ || !featureSymbol.isStatic()
+ || !featureSymbol.getSimpleName().toString().startsWith(FEATURE_PREFIX)
+ || ASTHelpers.enclosingClass(featureSymbol) == null
+ || !ASTHelpers.enclosingClass(featureSymbol)
+ .getQualifiedName()
+ .contentEquals(PACKAGE_MANAGER_CLASS)) {
+ return Description.NO_MATCH;
+ }
+
+ // Check if the feature argument is part of the RoSystemFeatures API surface.
+ String featureName = featureSymbol.getSimpleName().toString();
+ String methodName = RoSystemFeaturesMetadata.getMethodNameForFeatureName(featureName);
+ if (methodName == null) {
+ return Description.NO_MATCH;
+ }
+
+ // Generate the appropriate fix.
+ String replacement =
+ String.format(
+ "%s.%s(%s)",
+ RO_SYSTEM_FEATURE_SIMPLE_CLASS,
+ methodName,
+ state.getSourceForNode(ASTHelpers.getReceiver(packageManager)));
+ // Note that ErrorProne doesn't offer a seamless way of removing the `PackageManager` import
+ // if unused after fix application, so for now we only offer best effort import suggestions.
+ SuggestedFix fix =
+ SuggestedFix.builder()
+ .replace(tree, replacement)
+ .addImport(RO_SYSTEM_FEATURE_CLASS)
+ .removeStaticImport(PACKAGE_MANAGER_CLASS + "." + featureName)
+ .build();
+ return describeMatch(tree, fix);
+ }
+}
diff --git a/tools/systemfeatures/errorprone/tests/java/com/android/systemfeatures/errorprone/RoSystemFeaturesCheckerTest.java b/tools/systemfeatures/errorprone/tests/java/com/android/systemfeatures/errorprone/RoSystemFeaturesCheckerTest.java
new file mode 100644
index 0000000..c517b24
--- /dev/null
+++ b/tools/systemfeatures/errorprone/tests/java/com/android/systemfeatures/errorprone/RoSystemFeaturesCheckerTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemfeatures.errorprone;
+
+import com.google.errorprone.BugCheckerRefactoringTestHelper;
+import com.google.errorprone.CompilationTestHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class RoSystemFeaturesCheckerTest {
+ private BugCheckerRefactoringTestHelper mRefactoringHelper;
+ private CompilationTestHelper mCompilationHelper;
+
+ @Before
+ public void setUp() {
+ mCompilationHelper =
+ CompilationTestHelper.newInstance(RoSystemFeaturesChecker.class, getClass());
+ mRefactoringHelper =
+ BugCheckerRefactoringTestHelper.newInstance(
+ RoSystemFeaturesChecker.class, getClass());
+ }
+
+ @Test
+ public void testNoDiagnostic() {
+ mCompilationHelper
+ .addSourceFile("/android/content/Context.java")
+ .addSourceFile("/android/content/pm/PackageManager.java")
+ .addSourceLines("Example.java",
+ """
+ import android.content.Context;
+ import android.content.pm.PackageManager;
+ public class Example {
+ void test(Context context) {
+ boolean hasCustomFeature = context.getPackageManager()
+ .hasSystemFeature("my.custom.feature");
+ boolean hasNonAnnotatedFeature = context.getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_NOT_ANNOTATED);
+ boolean hasNonRoApiFeature = context.getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_NOT_IN_RO_FEATURE_API);
+ }
+ }
+ """)
+ .doTest();
+ }
+
+ @Test
+ public void testDiagnostic() {
+ mCompilationHelper
+ .addSourceFile("/android/content/Context.java")
+ .addSourceFile("/android/content/pm/PackageManager.java")
+ .addSourceLines("Example.java",
+ """
+ import android.content.Context;
+ import android.content.pm.PackageManager;
+ public class Example {
+ void test(Context context) {
+ boolean hasFeature = context.getPackageManager()
+ // BUG: Diagnostic contains:
+ .hasSystemFeature(PackageManager.FEATURE_PC);
+ }
+ }
+ """)
+ .doTest();
+ }
+
+ @Test
+ public void testFix() {
+ mRefactoringHelper
+ .addInputLines("Example.java",
+ """
+ import static android.content.pm.PackageManager.FEATURE_WATCH;
+
+ import android.content.Context;
+ import android.content.pm.PackageManager;
+ public class Example {
+ static class CustomContext extends Context {};
+ private CustomContext mContext;
+ void test(Context context) {
+ boolean hasPc = mContext.getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_PC);
+ boolean hasWatch = context.getPackageManager()
+ .hasSystemFeature(FEATURE_WATCH);
+ }
+ }
+ """)
+ .addOutputLines("Example.java",
+ """
+ import android.content.Context;
+ import android.content.pm.PackageManager;
+ import com.android.internal.pm.RoSystemFeatures;
+ public class Example {
+ static class CustomContext extends Context {};
+ private CustomContext mContext;
+ void test(Context context) {
+ boolean hasPc = RoSystemFeatures.hasFeaturePc(mContext);
+ boolean hasWatch = RoSystemFeatures.hasFeatureWatch(context);
+ }
+ }
+ """)
+ // Don't try compiling the output, as it requires pulling in the full set of code
+ // dependencies.
+ .allowBreakingChanges()
+ .doTest();
+ }
+}
diff --git a/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt b/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt
index f260e27..ea660b0 100644
--- a/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt
+++ b/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt
@@ -53,11 +53,20 @@
* public static ArrayMap<String, FeatureInfo> getReadOnlySystemEnabledFeatures();
* }
* </pre>
+ *
+ * <p> If `--metadata-only=true` is set, the resulting class would simply be:
+ * <pre>
+ * package com.foo;
+ * public final class RoSystemFeatures {
+ * public static String getMethodNameForFeatureName(String featureName);
+ * }
+ * </pre>
*/
object SystemFeaturesGenerator {
private const val FEATURE_ARG = "--feature="
private const val FEATURE_APIS_ARG = "--feature-apis="
private const val READONLY_ARG = "--readonly="
+ private const val METADATA_ONLY_ARG = "--metadata-only="
private val PACKAGEMANAGER_CLASS = ClassName.get("android.content.pm", "PackageManager")
private val CONTEXT_CLASS = ClassName.get("android.content", "Context")
private val FEATUREINFO_CLASS = ClassName.get("android.content.pm", "FeatureInfo")
@@ -84,6 +93,8 @@
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.")
+ println(" --metadata-only=true|false Whether to simply output metadata about the")
+ println(" generated API surface.")
}
/** Main entrypoint for build-time system feature codegen. */
@@ -106,6 +117,7 @@
}
var readonly = false
+ var metadataOnly = false
var outputClassName: ClassName? = null
val featureArgs = mutableListOf<FeatureInfo>()
// We could just as easily hardcode this list, as the static API surface should change
@@ -115,6 +127,8 @@
when {
arg.startsWith(READONLY_ARG) ->
readonly = arg.substring(READONLY_ARG.length).toBoolean()
+ arg.startsWith(METADATA_ONLY_ARG) ->
+ metadataOnly = arg.substring(METADATA_ONLY_ARG.length).toBoolean()
arg.startsWith(FEATURE_ARG) -> {
featureArgs.add(parseFeatureArg(arg))
}
@@ -155,9 +169,13 @@
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addJavadoc("@hide")
- addFeatureMethodsToClass(classBuilder, features.values)
- addMaybeFeatureMethodToClass(classBuilder, features.values)
- addGetFeaturesMethodToClass(classBuilder, features.values)
+ if (metadataOnly) {
+ addMetadataMethodToClass(classBuilder, features.values)
+ } else {
+ addFeatureMethodsToClass(classBuilder, features.values)
+ addMaybeFeatureMethodToClass(classBuilder, features.values)
+ addGetFeaturesMethodToClass(classBuilder, features.values)
+ }
// TODO(b/203143243): Add validation of build vs runtime values to ensure consistency.
JavaFile.builder(outputClassName.packageName(), classBuilder.build())
@@ -214,11 +232,8 @@
features: Collection<FeatureInfo>,
) {
for (feature in features) {
- // Turn "FEATURE_FOO" into "hasFeatureFoo".
- val methodName =
- "has" + CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, feature.name)
val methodBuilder =
- MethodSpec.methodBuilder(methodName)
+ MethodSpec.methodBuilder(feature.methodName)
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.addJavadoc("Check for ${feature.name}.\n\n@hide")
.returns(Boolean::class.java)
@@ -341,5 +356,32 @@
builder.addMethod(methodBuilder.build())
}
- private data class FeatureInfo(val name: String, val version: Int?, val readonly: Boolean)
+ /*
+ * Adds a metadata helper method that maps FEATURE_FOO names to their generated hasFeatureFoo()
+ * API counterpart, if defined.
+ */
+ private fun addMetadataMethodToClass(
+ builder: TypeSpec.Builder,
+ features: Collection<FeatureInfo>,
+ ) {
+ val methodBuilder =
+ MethodSpec.methodBuilder("getMethodNameForFeatureName")
+ .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
+ .addJavadoc("@return \"hasFeatureFoo\" if FEATURE_FOO is in the API, else null")
+ .returns(String::class.java)
+ .addParameter(String::class.java, "featureVarName")
+
+ methodBuilder.beginControlFlow("switch (featureVarName)")
+ for (feature in features) {
+ methodBuilder.addStatement("case \$S: return \$S", feature.name, feature.methodName)
+ }
+ methodBuilder.addStatement("default: return null").endControlFlow()
+
+ builder.addMethod(methodBuilder.build())
+ }
+
+ private data class FeatureInfo(val name: String, val version: Int?, val readonly: Boolean) {
+ // Turn "FEATURE_FOO" into "hasFeatureFoo".
+ val methodName get() = "has" + CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, name)
+ }
}
diff --git a/tools/systemfeatures/tests/src/SystemFeaturesMetadataProcessorTest.java b/tools/systemfeatures/tests/src/SystemFeaturesMetadataProcessorTest.java
index 74ce6da..560454b 100644
--- a/tools/systemfeatures/tests/src/SystemFeaturesMetadataProcessorTest.java
+++ b/tools/systemfeatures/tests/src/SystemFeaturesMetadataProcessorTest.java
@@ -36,8 +36,8 @@
@Test
public void testSdkFeatureCount() {
// See the fake PackageManager definition in this directory.
- // It defines 5 annotated features, and any/all other constants should be ignored.
- assertThat(SystemFeaturesMetadata.SDK_FEATURE_COUNT).isEqualTo(5);
+ // It defines 6 annotated features, and any/all other constants should be ignored.
+ assertThat(SystemFeaturesMetadata.SDK_FEATURE_COUNT).isEqualTo(6);
}
@Test
diff --git a/tools/systemfeatures/tests/src/Context.java b/tools/systemfeatures/tests/src/android/content/Context.java
similarity index 100%
rename from tools/systemfeatures/tests/src/Context.java
rename to tools/systemfeatures/tests/src/android/content/Context.java
diff --git a/tools/systemfeatures/tests/src/FeatureInfo.java b/tools/systemfeatures/tests/src/android/content/pm/FeatureInfo.java
similarity index 100%
rename from tools/systemfeatures/tests/src/FeatureInfo.java
rename to tools/systemfeatures/tests/src/android/content/pm/FeatureInfo.java
diff --git a/tools/systemfeatures/tests/src/PackageManager.java b/tools/systemfeatures/tests/src/android/content/pm/PackageManager.java
similarity index 86%
rename from tools/systemfeatures/tests/src/PackageManager.java
rename to tools/systemfeatures/tests/src/android/content/pm/PackageManager.java
index 839a937..4a9edd6 100644
--- a/tools/systemfeatures/tests/src/PackageManager.java
+++ b/tools/systemfeatures/tests/src/android/content/pm/PackageManager.java
@@ -36,6 +36,9 @@
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_WIFI = "wifi";
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_NOT_IN_RO_FEATURE_API = "not_in_ro_feature_api";
+
@SdkConstant(SdkConstantType.INTENT_CATEGORY)
public static final String FEATURE_INTENT_CATEGORY = "intent_category_with_feature_name_prefix";
@@ -47,4 +50,9 @@
public boolean hasSystemFeature(String featureName, int version) {
return false;
}
+
+ /** @hide */
+ public boolean hasSystemFeature(String featureName) {
+ return hasSystemFeature(featureName, 0);
+ }
}
diff --git a/tools/systemfeatures/tests/src/ArrayMap.java b/tools/systemfeatures/tests/src/android/util/ArrayMap.java
similarity index 100%
rename from tools/systemfeatures/tests/src/ArrayMap.java
rename to tools/systemfeatures/tests/src/android/util/ArrayMap.java
diff --git a/tools/systemfeatures/tests/src/ArraySet.java b/tools/systemfeatures/tests/src/android/util/ArraySet.java
similarity index 100%
rename from tools/systemfeatures/tests/src/ArraySet.java
rename to tools/systemfeatures/tests/src/android/util/ArraySet.java