summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--SafetyCenter/Resources/Android.bp34
-rw-r--r--SafetyCenter/Resources/AndroidManifest.xml37
-rw-r--r--SafetyCenter/Resources/com.android.safetycenter.resources.pem52
-rw-r--r--SafetyCenter/Resources/com.android.safetycenter.resources.pk8bin0 -> 2374 bytes
-rw-r--r--SafetyCenter/Resources/com.android.safetycenter.resources.x509.pem36
-rw-r--r--SafetyCenter/Resources/res/raw/safety_center_config.xml29
-rw-r--r--SafetyCenter/Resources/res/values/strings.xml26
-rw-r--r--SafetyCenter/ResourcesLib/Android.bp37
-rw-r--r--SafetyCenter/ResourcesLib/java/com/android/safetycenter/resources/SafetyCenterResourcesContext.java219
-rw-r--r--SafetyCenter/ResourcesLib/tests/Android.bp38
-rw-r--r--SafetyCenter/ResourcesLib/tests/AndroidManifest.xml30
-rw-r--r--SafetyCenter/ResourcesLib/tests/AndroidTest.xml36
-rw-r--r--SafetyCenter/ResourcesLib/tests/SafetyCenterResourcesLibTestResources/Android.bp26
-rw-r--r--SafetyCenter/ResourcesLib/tests/SafetyCenterResourcesLibTestResources/AndroidManifest.xml30
-rw-r--r--SafetyCenter/ResourcesLib/tests/SafetyCenterResourcesLibTestResources/res/raw/test.txt1
-rw-r--r--SafetyCenter/ResourcesLib/tests/java/com/android/safetycenter/resources/SafetyCenterResourcesContextTest.kt125
-rw-r--r--service/Android.bp2
-rw-r--r--service/java/com/android/safetycenter/SafetyCenterService.java37
18 files changed, 794 insertions, 1 deletions
diff --git a/SafetyCenter/Resources/Android.bp b/SafetyCenter/Resources/Android.bp
new file mode 100644
index 000000000..e82392646
--- /dev/null
+++ b/SafetyCenter/Resources/Android.bp
@@ -0,0 +1,34 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+// APK to hold all the Safety Center overlayable resources.
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_app {
+ name: "SafetyCenterResources",
+ privileged: true,
+ sdk_version: "system_current",
+ min_sdk_version: "30",
+ apex_available: ["com.android.permission"],
+ certificate: ":com.android.safetycenter.resources.certificate",
+}
+
+android_app_certificate {
+ name: "com.android.safetycenter.resources.certificate",
+ certificate: "com.android.safetycenter.resources"
+}
diff --git a/SafetyCenter/Resources/AndroidManifest.xml b/SafetyCenter/Resources/AndroidManifest.xml
new file mode 100644
index 000000000..9804f476b
--- /dev/null
+++ b/SafetyCenter/Resources/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<!-- Manifest for Safety Center resources APK -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.safetycenter.resources"
+ coreApp="true"
+ android:versionCode="319999900"
+ android:versionName="31 system image">
+ <application
+ android:label="@string/safetyCenterResourcesAppLabel"
+ android:defaultToDeviceProtectedStorage="true"
+ android:directBootAware="true">
+ <!-- This is only used to identify this app by resolving the action.
+ The activity is never actually triggered. -->
+ <activity
+ android:name="android.app.Activity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="com.android.safetycenter.intent.action.SAFETY_CENTER_RESOURCES_APK" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest> \ No newline at end of file
diff --git a/SafetyCenter/Resources/com.android.safetycenter.resources.pem b/SafetyCenter/Resources/com.android.safetycenter.resources.pem
new file mode 100644
index 000000000..3c9f83c29
--- /dev/null
+++ b/SafetyCenter/Resources/com.android.safetycenter.resources.pem
@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+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
+YnA8BkENvW/4fVCfFP+nH0+jq8IxtQ==
+-----END PRIVATE KEY-----
diff --git a/SafetyCenter/Resources/com.android.safetycenter.resources.pk8 b/SafetyCenter/Resources/com.android.safetycenter.resources.pk8
new file mode 100644
index 000000000..74fbd4523
--- /dev/null
+++ b/SafetyCenter/Resources/com.android.safetycenter.resources.pk8
Binary files differ
diff --git a/SafetyCenter/Resources/com.android.safetycenter.resources.x509.pem b/SafetyCenter/Resources/com.android.safetycenter.resources.x509.pem
new file mode 100644
index 000000000..e22d1aad3
--- /dev/null
+++ b/SafetyCenter/Resources/com.android.safetycenter.resources.x509.pem
@@ -0,0 +1,36 @@
+-----BEGIN CERTIFICATE-----
+MIIGQzCCBCugAwIBAgIUfp6Dp8jfBYD+u1qYkcMvYQKrNHYwDQYJKoZIhvcNAQEL
+BQAwga8xCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH
+DA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdBbmRyb2lkMRAwDgYDVQQLDAdBbmRy
+b2lkMSswKQYDVQQDDCJjb20uYW5kcm9pZC5zYWZldHljZW50ZXIucmVzb3VyY2Vz
+MSIwIAYJKoZIhvcNAQkBFhNhbmRyb2lkQGFuZHJvaWQuY29tMCAXDTIxMTIwNjE0
+Mjc0NVoYDzQ3NTkxMTAyMTQyNzQ1WjCBrzELMAkGA1UEBhMCVVMxEzARBgNVBAgM
+CkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxEDAOBgNVBAoMB0Fu
+ZHJvaWQxEDAOBgNVBAsMB0FuZHJvaWQxKzApBgNVBAMMImNvbS5hbmRyb2lkLnNh
+ZmV0eWNlbnRlci5yZXNvdXJjZXMxIjAgBgkqhkiG9w0BCQEWE2FuZHJvaWRAYW5k
+cm9pZC5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDP+8izeSt/
+AIWnVKIddgKU8gGOkZjlHN1B9E0uMolL15/qMDlkehwKrTVNjIZddV1R4lcDLJa6
+IMa/GsFlXeg5hn25O95vG9D5c2ypMYhxBXggXl4eII8eMPfvk7G3ryD01vdx+jAm
+tdNwJrdOKWOMncbj3Y7fPK7uA/FloF1sTkuw9Y7VLf/mX/11b7iwSma2LsqRbzcs
+gFItUXFjlF1myK9TA8dT01BrcwnJ5AXY+P8fr18encBJhBZm7unoS0sNVuHgwqu/
+n943bYoDZP9Jc33gL26qcyN/LRmY6HMOq0ZzRR0GfunaDemcBD/Z0k+dIpeKPWWf
+p3f0r2P1ZJKdQSPumU1LInHbGVoBRBaI94gtHyv0RTYtrBoqHcFGF5thgV9LTZFw
+W0r6sVlguzPTInmRs1m04df7OEmNBqeIeNb+5U35YpJMzxe124OK1Ex5rlYF25fd
+80fKqH8zBxSncAtju8WhVtLZrFc/WOvctcuJ91UJy7OkDz72Uy00jPm/0f7y7P4h
+nSoXfUgOjvniLwINhXF32dnI4WqyFJKoTP0Btn1ofmDjcUtfIwb4iTwKBlRb1PpA
+kwJQweqZ1pbab759KNfEg92Bdr2xqH6Iz0SQHLL2osa+Mofm9whhBjsErFtlxOZx
+GA3zgmuOnmCzzIpIa2BjuIOJA7QPi7EjlQIDAQABo1MwUTAdBgNVHQ4EFgQUVaQt
+WT53h5UIovqc/xyd94KOV5gwHwYDVR0jBBgwFoAUVaQtWT53h5UIovqc/xyd94KO
+V5gwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAw76A4zktQj4o
+PWCBRucrfauiYe4ENpybN0U13MCEOFKx9qRL0sXwV/ZN7QAvs62y8Lw3ytAlihwp
+4CM45/DBCwIwLkTS5sw9axzNKHoFdGLsMHH3p96skYNfTBKshL602ZOHHyt3CrpO
+ZLOPyvt0/TKajZ1K5O1lB4S60qcJkq9P6xg80qV66NbNjT84yKq8Y1lDJbJEnRB2
+BXjr02RhuAOe7jB8VKcBeKWsKBP95DBX+QV9wqSoS9Pnet3Etb66ddfWQ5m/bkoh
+lRcjpruYnEUk/7jYhJRzpmivyAnMVSas/oQZnSFUgFVl71RO05gyrWMSQt4kCAKB
+ZwEcVI+2z8rzTtMM0PtRgp6TCQ7QwoQQiGjakq2aFOR0gsUpjaFroP2q73uB2c2Y
+vS9VY78tPCI3RXkspA5sRJRafZ7KIBqqpUvKSd7woGDL7fQxtaq8XSUOyPP3CfZa
+fhjQ1PAKYnjlUGmcWAKHaXo2uWsh2sNa9ycS2p05AwC8lNqs1p1Fde07Zw4PclVY
+QN+N31b+2Byw9vI5bnIE9NflgHry3bMeFxctq3VyrAnJffL2JtaodRwDbqDCHbcb
+pg573cqjzPppqw++zJWZFWNP06sTtF/Y7pDrrumJNgW0dtZWEN/pj0mtZdDSi+pa
+aHU1vVK/5HwBfqkyYfklVk1NHvAx6y8=
+-----END CERTIFICATE-----
diff --git a/SafetyCenter/Resources/res/raw/safety_center_config.xml b/SafetyCenter/Resources/res/raw/safety_center_config.xml
new file mode 100644
index 000000000..d3c0125cf
--- /dev/null
+++ b/SafetyCenter/Resources/res/raw/safety_center_config.xml
@@ -0,0 +1,29 @@
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<safety-center-config>
+ <safety-sources-config>
+ <!-- TODO(b/214567659): Finalize base XML config -->
+ <safety-sources-group
+ id="TODO"
+ title="@string/reference"
+ summary="@string/reference">
+ <safety-source
+ type="3"
+ id="TODO"/>
+ </safety-sources-group>
+ </safety-sources-config>
+</safety-center-config>
diff --git a/SafetyCenter/Resources/res/values/strings.xml b/SafetyCenter/Resources/res/values/strings.xml
new file mode 100644
index 000000000..594d20d7b
--- /dev/null
+++ b/SafetyCenter/Resources/res/values/strings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- The Safety Center resources package is an internal system package that provides
+ configuration values for the Safety Center. This is the name of the package to display in
+ the list of system apps. [CHAR LIMIT=40] -->
+ <string name="safetyCenterResourcesAppLabel">Safety Center Resources</string>
+
+ <!-- Temporary reference -->
+ <string name="reference" translatable="false">Reference</string>
+</resources>
diff --git a/SafetyCenter/ResourcesLib/Android.bp b/SafetyCenter/ResourcesLib/Android.bp
new file mode 100644
index 000000000..274386b22
--- /dev/null
+++ b/SafetyCenter/ResourcesLib/Android.bp
@@ -0,0 +1,37 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+// Library to manage all the Safety Center overlayable resources.
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_library {
+ name: "safety-center-resources-lib",
+ srcs: [
+ "java/**/*.java",
+ ],
+ libs: [
+ "framework-annotations-lib",
+ ],
+ apex_available: [
+ "com.android.permission",
+ "test_com.android.permission",
+ ],
+ installable: false,
+ min_sdk_version: "30",
+ sdk_version: "module_current",
+}
diff --git a/SafetyCenter/ResourcesLib/java/com/android/safetycenter/resources/SafetyCenterResourcesContext.java b/SafetyCenter/ResourcesLib/java/com/android/safetycenter/resources/SafetyCenterResourcesContext.java
new file mode 100644
index 000000000..672c57b45
--- /dev/null
+++ b/SafetyCenter/ResourcesLib/java/com/android/safetycenter/resources/SafetyCenterResourcesContext.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.safetycenter.resources;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.util.Log;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.List;
+
+/**
+ * Wrapper for context to override getResources method. Resources for the Safety Center that need to
+ * be fetched from the dedicated resources APK.
+ */
+public class SafetyCenterResourcesContext extends ContextWrapper {
+ private static final String TAG = "SafetyCenterResContext";
+
+ /** Intent action that is used to identify the Safety Center resources APK */
+ private static final String RESOURCES_APK_ACTION =
+ "com.android.safetycenter.intent.action.SAFETY_CENTER_RESOURCES_APK";
+
+ /** Permission APEX name */
+ private static final String APEX_MODULE_NAME = "com.android.permission";
+
+ /**
+ * The path where the Permission apex is mounted.
+ * Current value = "/apex/com.android.permission"
+ */
+ private static final String APEX_MODULE_PATH =
+ new File("/apex", APEX_MODULE_NAME).getAbsolutePath();
+
+ /** Raw XML config resource name */
+ private static final String CONFIG_NAME = "safety_center_config";
+
+ /** Intent action that is used to identify the Safety Center resources APK */
+ @NonNull
+ private final String mResourcesApkAction;
+
+ /** The path where the Safety Center resources APK is expected to be installed */
+ @Nullable
+ private final String mResourcesApkPath;
+
+ /** Raw XML config resource name */
+ @NonNull
+ private final String mConfigName;
+
+ /** Specific flags used for retrieving resolve info */
+ private final int mFlags;
+
+ // Cached package name and resources from the resources APK
+ @Nullable
+ private String mResourcesApkPkgName;
+ @Nullable
+ private AssetManager mAssetsFromApk;
+ @Nullable
+ private Resources mResourcesFromApk;
+ @Nullable
+ private Resources.Theme mThemeFromApk;
+
+ public SafetyCenterResourcesContext(@NonNull Context contextBase) {
+ super(contextBase);
+ mResourcesApkAction = RESOURCES_APK_ACTION;
+ mResourcesApkPath = APEX_MODULE_PATH;
+ mConfigName = CONFIG_NAME;
+ mFlags = PackageManager.MATCH_SYSTEM_ONLY;
+ }
+
+ SafetyCenterResourcesContext(@NonNull Context contextBase, @NonNull String resourcesApkAction,
+ @Nullable String resourcesApkPath, @NonNull String configName, int flags) {
+ super(contextBase);
+ mResourcesApkAction = requireNonNull(resourcesApkAction);
+ mResourcesApkPath = resourcesApkPath;
+ mConfigName = requireNonNull(configName);
+ mFlags = flags;
+ }
+
+ /** Get the package name of the Safety Center resources APK. */
+ @Nullable
+ public String getResourcesApkPkgName() {
+ if (mResourcesApkPkgName != null) {
+ return mResourcesApkPkgName;
+ }
+
+ List<ResolveInfo> resolveInfos = getPackageManager().queryIntentActivities(
+ new Intent(mResourcesApkAction), mFlags);
+
+ if (resolveInfos.size() > 1) {
+ // multiple apps found, log a warning, but continue
+ Log.w(TAG, "Found > 1 APK that can resolve Safety Center resources APK intent:");
+ final int resolveInfosSize = resolveInfos.size();
+ for (int i = 0; i < resolveInfosSize; i++) {
+ ResolveInfo resolveInfo = resolveInfos.get(i);
+ Log.w(TAG, String.format("- pkg:%s at:%s",
+ resolveInfo.activityInfo.applicationInfo.packageName,
+ resolveInfo.activityInfo.applicationInfo.sourceDir));
+ }
+ }
+
+ ResolveInfo info = null;
+ // Assume the first good ResolveInfo is the one we're looking for
+ final int resolveInfosSize = resolveInfos.size();
+ for (int i = 0; i < resolveInfosSize; i++) {
+ ResolveInfo resolveInfo = resolveInfos.get(i);
+ if (mResourcesApkPath != null
+ && !resolveInfo.activityInfo.applicationInfo.sourceDir.startsWith(
+ mResourcesApkPath)) {
+ // skip apps that don't live in the Permission apex
+ continue;
+ }
+ info = resolveInfo;
+ break;
+ }
+
+ if (info == null) {
+ // Resource APK not loaded yet, print a stack trace to see where this is called from
+ Log.e(TAG,
+ "Attempted to fetch resources before Safety Center resources APK is loaded!",
+ new IllegalStateException());
+ return null;
+ }
+
+ mResourcesApkPkgName = info.activityInfo.applicationInfo.packageName;
+ Log.i(TAG, "Found Safety Center resources APK at: " + mResourcesApkPkgName);
+ return mResourcesApkPkgName;
+ }
+
+ /** Get the Safety Center config in the Safety Center resources APK. */
+ @Nullable
+ public InputStream getSafetyCenterConfig() {
+ String resoursePkgName = getResourcesApkPkgName();
+ if (resoursePkgName == null) {
+ return null;
+ }
+ Resources resources = getResources();
+ if (resources == null) {
+ return null;
+ }
+ int id = resources.getIdentifier(mConfigName, "raw", resoursePkgName);
+ if (id == 0) {
+ return null;
+ }
+ return resources.openRawResource(id);
+ }
+
+ @Nullable
+ private Context getResourcesApkContext() {
+ String name = getResourcesApkPkgName();
+ if (name == null) {
+ return null;
+ }
+ try {
+ return createPackageContext(name, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.wtf(TAG, "Failed to load resources", e);
+ }
+ return null;
+ }
+
+ /** Retrieve assets held in the Safety Center resources APK. */
+ @Override
+ public AssetManager getAssets() {
+ if (mAssetsFromApk == null) {
+ Context resourcesApkContext = getResourcesApkContext();
+ if (resourcesApkContext != null) {
+ mAssetsFromApk = resourcesApkContext.getAssets();
+ }
+ }
+ return mAssetsFromApk;
+ }
+
+ /** Retrieve resources held in the Safety Center resources APK. */
+ @Override
+ public Resources getResources() {
+ if (mResourcesFromApk == null) {
+ Context resourcesApkContext = getResourcesApkContext();
+ if (resourcesApkContext != null) {
+ mResourcesFromApk = resourcesApkContext.getResources();
+ }
+ }
+ return mResourcesFromApk;
+ }
+
+ /** Retrieve theme held in the Safety Center resources APK. */
+ @Override
+ public Resources.Theme getTheme() {
+ if (mThemeFromApk == null) {
+ Context resourcesApkContext = getResourcesApkContext();
+ if (resourcesApkContext != null) {
+ mThemeFromApk = resourcesApkContext.getTheme();
+ }
+ }
+ return mThemeFromApk;
+ }
+}
diff --git a/SafetyCenter/ResourcesLib/tests/Android.bp b/SafetyCenter/ResourcesLib/tests/Android.bp
new file mode 100644
index 000000000..37770eb61
--- /dev/null
+++ b/SafetyCenter/ResourcesLib/tests/Android.bp
@@ -0,0 +1,38 @@
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+ name: "SafetyCenterResourcesLibTests",
+ defaults: ["mts-target-sdk-version-current"],
+ sdk_version: "test_current",
+ min_sdk_version: "30",
+ srcs: [
+ "java/**/*.kt",
+ ],
+ static_libs: [
+ "safety-center-resources-lib",
+ "compatibility-device-util-axt",
+ ],
+ data: [
+ ":SafetyCenterResourcesLibTestResources",
+ ],
+ test_suites: [
+ "general-tests",
+ "mts-permission",
+ ],
+}
diff --git a/SafetyCenter/ResourcesLib/tests/AndroidManifest.xml b/SafetyCenter/ResourcesLib/tests/AndroidManifest.xml
new file mode 100644
index 000000000..505505751
--- /dev/null
+++ b/SafetyCenter/ResourcesLib/tests/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ 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.
+ -->
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.safetycenter.tests.resourceslib.safetycenterresourceslib">
+
+ <application android:label="Safety Center ResourcesLib Tests">
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.safetycenter.tests.resourceslib.safetycenterresourceslib"
+ android:label="Tests for the Safety Center ResourcesLib library" />
+</manifest>
diff --git a/SafetyCenter/ResourcesLib/tests/AndroidTest.xml b/SafetyCenter/ResourcesLib/tests/AndroidTest.xml
new file mode 100644
index 000000000..840f17d63
--- /dev/null
+++ b/SafetyCenter/ResourcesLib/tests/AndroidTest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ 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.
+ -->
+
+<configuration description="Runs unit tests for the Safety Center ResourcesLib library.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-instrumentation" />
+ <option name="test-tag" value="SafetyCenterConfigTests" />
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk30ModuleController" />
+
+ <!-- Install test and resources -->
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="SafetyCenterResourcesLibTests.apk" />
+ <option name="test-file-name" value="SafetyCenterResourcesLibTestResources.apk" />
+ <option name="cleanup-apks" value="true" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.safetycenter.tests.resourceslib.safetycenterresourceslib" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ </test>
+</configuration>
diff --git a/SafetyCenter/ResourcesLib/tests/SafetyCenterResourcesLibTestResources/Android.bp b/SafetyCenter/ResourcesLib/tests/SafetyCenterResourcesLibTestResources/Android.bp
new file mode 100644
index 000000000..18628805d
--- /dev/null
+++ b/SafetyCenter/ResourcesLib/tests/SafetyCenterResourcesLibTestResources/Android.bp
@@ -0,0 +1,26 @@
+//
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "SafetyCenterResourcesLibTestResources",
+ defaults: ["mts-target-sdk-version-current"],
+ sdk_version: "test_current",
+ min_sdk_version: "30",
+}
diff --git a/SafetyCenter/ResourcesLib/tests/SafetyCenterResourcesLibTestResources/AndroidManifest.xml b/SafetyCenter/ResourcesLib/tests/SafetyCenterResourcesLibTestResources/AndroidManifest.xml
new file mode 100644
index 000000000..0af1e0d85
--- /dev/null
+++ b/SafetyCenter/ResourcesLib/tests/SafetyCenterResourcesLibTestResources/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.safetycenter.tests.config.safetycenterresourceslibtestresources">
+ <application>
+ <!-- This is only used to identify this app by resolving the action.
+ The activity is never actually triggered. -->
+ <activity
+ android:name="android.app.Activity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="com.android.safetycenter.tests.intent.action.SAFETY_CENTER_TEST_RESOURCES_APK" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/SafetyCenter/ResourcesLib/tests/SafetyCenterResourcesLibTestResources/res/raw/test.txt b/SafetyCenter/ResourcesLib/tests/SafetyCenterResourcesLibTestResources/res/raw/test.txt
new file mode 100644
index 000000000..3b1246497
--- /dev/null
+++ b/SafetyCenter/ResourcesLib/tests/SafetyCenterResourcesLibTestResources/res/raw/test.txt
@@ -0,0 +1 @@
+TEST \ No newline at end of file
diff --git a/SafetyCenter/ResourcesLib/tests/java/com/android/safetycenter/resources/SafetyCenterResourcesContextTest.kt b/SafetyCenter/ResourcesLib/tests/java/com/android/safetycenter/resources/SafetyCenterResourcesContextTest.kt
new file mode 100644
index 000000000..b6fd732a6
--- /dev/null
+++ b/SafetyCenter/ResourcesLib/tests/java/com/android/safetycenter/resources/SafetyCenterResourcesContextTest.kt
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.safetycenter.resources
+
+import android.content.Context
+import android.content.pm.PackageManager
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertNull
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SafetyCenterResourcesContextTest {
+ private val context: Context = getApplicationContext()
+
+ @Test
+ fun validDataWithValidInputs() {
+ val safetyCenterResourcesContext = SafetyCenterResourcesContext(
+ context,
+ RESOURCES_APK_ACTION,
+ null,
+ CONFIG_NAME,
+ 0
+ )
+ assertThat(safetyCenterResourcesContext.resourcesApkPkgName).isEqualTo(
+ RESOURCES_APK_PKG_NAME
+ )
+ val configContent =
+ safetyCenterResourcesContext.safetyCenterConfig?.bufferedReader().use { it?.readText() }
+ assertThat(configContent).isEqualTo(CONFIG_CONTENT)
+ assertNotNull(safetyCenterResourcesContext.assets)
+ assertNotNull(safetyCenterResourcesContext.resources)
+ assertNotNull(safetyCenterResourcesContext.theme)
+ }
+
+ @Test
+ fun nullDataWithWrongAction() {
+ val safetyCenterResourcesContext = SafetyCenterResourcesContext(
+ context,
+ "wrong",
+ null,
+ CONFIG_NAME,
+ 0
+ )
+ assertNull(safetyCenterResourcesContext.resourcesApkPkgName)
+ assertNull(safetyCenterResourcesContext.safetyCenterConfig)
+ assertNull(safetyCenterResourcesContext.assets)
+ assertNull(safetyCenterResourcesContext.resources)
+ assertNull(safetyCenterResourcesContext.theme)
+ }
+
+ @Test
+ fun nullDataWithWrongPath() {
+ val safetyCenterResourcesContext = SafetyCenterResourcesContext(
+ context,
+ RESOURCES_APK_ACTION,
+ "/apex/com.android.permission",
+ CONFIG_NAME,
+ 0
+ )
+ assertNull(safetyCenterResourcesContext.resourcesApkPkgName)
+ assertNull(safetyCenterResourcesContext.safetyCenterConfig)
+ assertNull(safetyCenterResourcesContext.assets)
+ assertNull(safetyCenterResourcesContext.resources)
+ assertNull(safetyCenterResourcesContext.theme)
+ }
+
+ @Test
+ fun nullDataWithWrongFlag() {
+ val safetyCenterResourcesContext = SafetyCenterResourcesContext(
+ context,
+ RESOURCES_APK_ACTION,
+ null,
+ CONFIG_NAME,
+ PackageManager.MATCH_SYSTEM_ONLY
+ )
+ assertNull(safetyCenterResourcesContext.resourcesApkPkgName)
+ assertNull(safetyCenterResourcesContext.safetyCenterConfig)
+ assertNull(safetyCenterResourcesContext.assets)
+ assertNull(safetyCenterResourcesContext.resources)
+ assertNull(safetyCenterResourcesContext.theme)
+ }
+
+ @Test
+ fun nullConfigWithWrongConfigName() {
+ val safetyCenterResourcesContext = SafetyCenterResourcesContext(
+ context,
+ RESOURCES_APK_ACTION,
+ null,
+ "wrong",
+ 0
+ )
+ assertNotNull(safetyCenterResourcesContext.resourcesApkPkgName)
+ assertNull(safetyCenterResourcesContext.safetyCenterConfig)
+ assertNotNull(safetyCenterResourcesContext.assets)
+ assertNotNull(safetyCenterResourcesContext.resources)
+ assertNotNull(safetyCenterResourcesContext.theme)
+ }
+
+ companion object {
+ const val RESOURCES_APK_ACTION =
+ "com.android.safetycenter.tests.intent.action.SAFETY_CENTER_TEST_RESOURCES_APK"
+ const val RESOURCES_APK_PKG_NAME =
+ "com.android.safetycenter.tests.config.safetycenterresourceslibtestresources"
+ const val CONFIG_NAME = "test"
+ const val CONFIG_CONTENT = "TEST"
+ }
+} \ No newline at end of file
diff --git a/service/Android.bp b/service/Android.bp
index 07b5d58a8..fcb7f6739 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -104,6 +104,8 @@ java_sdk_library {
"kotlin-stdlib",
"modules-utils-backgroundthread",
"modules-utils-os",
+ "safety-center-config",
+ "safety-center-resources-lib",
"service-permission-shared",
],
exclude_kotlinc_generated_files: true,
diff --git a/service/java/com/android/safetycenter/SafetyCenterService.java b/service/java/com/android/safetycenter/SafetyCenterService.java
index 5b1cdcd9d..ff805f131 100644
--- a/service/java/com/android/safetycenter/SafetyCenterService.java
+++ b/service/java/com/android/safetycenter/SafetyCenterService.java
@@ -36,6 +36,7 @@ import android.safetycenter.ISafetyCenterManager;
import android.safetycenter.SafetyCenterData;
import android.safetycenter.SafetyCenterStatus;
import android.safetycenter.SafetySourceData;
+import android.util.Log;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
@@ -43,8 +44,12 @@ import androidx.annotation.RequiresApi;
import com.android.internal.annotations.GuardedBy;
import com.android.permission.util.PermissionUtils;
+import com.android.safetycenter.config.Parser;
+import com.android.safetycenter.config.parser.SafetyCenterConfig;
+import com.android.safetycenter.resources.SafetyCenterResourcesContext;
import com.android.server.SystemService;
+import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -59,16 +64,19 @@ import java.util.Objects;
@Keep
@RequiresApi(TIRAMISU)
public final class SafetyCenterService extends SystemService {
+ private static final String TAG = "SafetyCenterService";
/** Phenotype flag that determines whether SafetyCenter is enabled. */
private static final String PROPERTY_SAFETY_CENTER_ENABLED = "safety_center_is_enabled";
@NonNull
private final Object mLock = new Object();
- // TODO(b/206789604): Use persistent storage instead.
+ // TODO(b/202386571): Create a new data model to store both config and dynamic data in memory.
@GuardedBy("mLock")
@NonNull
private final Map<Key, SafetySourceData> mSafetySourceDataForKey = new HashMap<>();
+ @Nullable
+ private SafetyCenterConfig mSafetyCenterConfig;
@NonNull
private final AppOpsManager mAppOpsManager;
@@ -77,14 +85,41 @@ public final class SafetyCenterService extends SystemService {
private final List<IOnSafetyCenterDataChangedListener> mSafetyCenterDataChangedListeners =
new ArrayList<>();
+ @NonNull
+ private final SafetyCenterResourcesContext mSafetyCenterResourcesContext;
+
public SafetyCenterService(@NonNull Context context) {
super(context);
mAppOpsManager = requireNonNull(context.getSystemService(AppOpsManager.class));
+ mSafetyCenterResourcesContext = new SafetyCenterResourcesContext(context);
}
@Override
public void onStart() {
publishBinderService(Context.SAFETY_CENTER_SERVICE, new Stub());
+ readSafetyCenterConfig();
+ }
+
+ private void readSafetyCenterConfig() {
+ // TODO(b/214568975): Decide if we should disable Safety Center if there is a problem
+ // reading the config.
+ String resoursePkgName = mSafetyCenterResourcesContext.getResourcesApkPkgName();
+ if (resoursePkgName == null) {
+ Log.e(TAG, "Cannot get Safety Center resources");
+ return;
+ }
+ InputStream in = mSafetyCenterResourcesContext.getSafetyCenterConfig();
+ if (in == null) {
+ Log.e(TAG, "Cannot get Safety Center config");
+ return;
+ }
+ try {
+ mSafetyCenterConfig = Parser.parse(in, resoursePkgName,
+ mSafetyCenterResourcesContext.getResources());
+ Log.i(TAG, "Safety Center config read successfully");
+ } catch (Parser.ParseException e) {
+ Log.e(TAG, "Cannot read Safety Center config", e);
+ }
}
private static final class Key {