• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.safetycenter.resources;
18 
19 import static java.util.Objects.requireNonNull;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.content.Context;
24 import android.content.ContextWrapper;
25 import android.content.Intent;
26 import android.content.pm.PackageManager;
27 import android.content.pm.ResolveInfo;
28 import android.content.res.AssetManager;
29 import android.content.res.Resources;
30 import android.util.Log;
31 
32 import com.android.internal.annotations.VisibleForTesting;
33 
34 import java.io.File;
35 import java.io.InputStream;
36 import java.util.List;
37 
38 /**
39  * Wrapper for context to override getResources method. Resources for the Safety Center that need to
40  * be fetched from the dedicated resources APK.
41  */
42 public class SafetyCenterResourcesContext extends ContextWrapper {
43     private static final String TAG = "SafetyCenterResContext";
44 
45     /** Intent action that is used to identify the Safety Center resources APK */
46     private static final String RESOURCES_APK_ACTION =
47             "com.android.safetycenter.intent.action.SAFETY_CENTER_RESOURCES_APK";
48 
49     /** Permission APEX name */
50     private static final String APEX_MODULE_NAME = "com.android.permission";
51 
52     /**
53      * The path where the Permission apex is mounted.
54      * Current value = "/apex/com.android.permission"
55      */
56     private static final String APEX_MODULE_PATH =
57             new File("/apex", APEX_MODULE_NAME).getAbsolutePath();
58 
59     /** Raw XML config resource name */
60     private static final String CONFIG_NAME = "safety_center_config";
61 
62     /** Intent action that is used to identify the Safety Center resources APK */
63     @NonNull
64     private final String mResourcesApkAction;
65 
66     /** The path where the Safety Center resources APK is expected to be installed */
67     @Nullable
68     private final String mResourcesApkPath;
69 
70     /** Raw XML config resource name */
71     @NonNull
72     private final String mConfigName;
73 
74     /** Specific flags used for retrieving resolve info */
75     private final int mFlags;
76 
77     // Cached package name and resources from the resources APK
78     @Nullable
79     private String mResourcesApkPkgName;
80     @Nullable
81     private AssetManager mAssetsFromApk;
82     @Nullable
83     private Resources mResourcesFromApk;
84     @Nullable
85     private Resources.Theme mThemeFromApk;
86 
SafetyCenterResourcesContext(@onNull Context contextBase)87     public SafetyCenterResourcesContext(@NonNull Context contextBase) {
88         super(contextBase);
89         mResourcesApkAction = RESOURCES_APK_ACTION;
90         mResourcesApkPath = APEX_MODULE_PATH;
91         mConfigName = CONFIG_NAME;
92         mFlags = PackageManager.MATCH_SYSTEM_ONLY;
93     }
94 
SafetyCenterResourcesContext(@onNull Context contextBase, @NonNull String resourcesApkAction, @Nullable String resourcesApkPath, @NonNull String configName, int flags)95     SafetyCenterResourcesContext(@NonNull Context contextBase, @NonNull String resourcesApkAction,
96             @Nullable String resourcesApkPath, @NonNull String configName, int flags) {
97         super(contextBase);
98         mResourcesApkAction = requireNonNull(resourcesApkAction);
99         mResourcesApkPath = resourcesApkPath;
100         mConfigName = requireNonNull(configName);
101         mFlags = flags;
102     }
103 
104     /** Get the package name of the Safety Center resources APK. */
105     @VisibleForTesting
106     @Nullable
getResourcesApkPkgName()107     String getResourcesApkPkgName() {
108         if (mResourcesApkPkgName != null) {
109             return mResourcesApkPkgName;
110         }
111 
112         List<ResolveInfo> resolveInfos = getPackageManager().queryIntentActivities(
113                 new Intent(mResourcesApkAction), mFlags);
114 
115         if (resolveInfos.size() > 1) {
116             // multiple apps found, log a warning, but continue
117             Log.w(TAG, "Found > 1 APK that can resolve Safety Center resources APK intent:");
118             final int resolveInfosSize = resolveInfos.size();
119             for (int i = 0; i < resolveInfosSize; i++) {
120                 ResolveInfo resolveInfo = resolveInfos.get(i);
121                 Log.w(TAG, String.format("- pkg:%s at:%s",
122                         resolveInfo.activityInfo.applicationInfo.packageName,
123                         resolveInfo.activityInfo.applicationInfo.sourceDir));
124             }
125         }
126 
127         ResolveInfo info = null;
128         // Assume the first good ResolveInfo is the one we're looking for
129         final int resolveInfosSize = resolveInfos.size();
130         for (int i = 0; i < resolveInfosSize; i++) {
131             ResolveInfo resolveInfo = resolveInfos.get(i);
132             if (mResourcesApkPath != null
133                     && !resolveInfo.activityInfo.applicationInfo.sourceDir.startsWith(
134                     mResourcesApkPath)) {
135                 // skip apps that don't live in the Permission apex
136                 continue;
137             }
138             info = resolveInfo;
139             break;
140         }
141 
142         if (info == null) {
143             // Resource APK not loaded yet, print a stack trace to see where this is called from
144             Log.e(TAG,
145                     "Attempted to fetch resources before Safety Center resources APK is loaded!",
146                     new IllegalStateException());
147             return null;
148         }
149 
150         mResourcesApkPkgName = info.activityInfo.applicationInfo.packageName;
151         Log.i(TAG, "Found Safety Center resources APK at: " + mResourcesApkPkgName);
152         return mResourcesApkPkgName;
153     }
154 
155     /**
156      * Get the raw XML resource representing the Safety Center configuration from the Safety Center
157      * resources APK.
158      */
159     @Nullable
getSafetyCenterConfig()160     public InputStream getSafetyCenterConfig() {
161         String resourcePkgName = getResourcesApkPkgName();
162         if (resourcePkgName == null) {
163             return null;
164         }
165         Resources resources = getResources();
166         if (resources == null) {
167             return null;
168         }
169         int id = resources.getIdentifier(mConfigName, "raw", resourcePkgName);
170         if (id == Resources.ID_NULL) {
171             return null;
172         }
173         return resources.openRawResource(id);
174     }
175 
176     @Nullable
getResourcesApkContext()177     private Context getResourcesApkContext() {
178         String name = getResourcesApkPkgName();
179         if (name == null) {
180             return null;
181         }
182         try {
183             return createPackageContext(name, 0);
184         } catch (PackageManager.NameNotFoundException e) {
185             Log.wtf(TAG, "Failed to load resources", e);
186         }
187         return null;
188     }
189 
190     /** Retrieve assets held in the Safety Center resources APK. */
191     @Override
getAssets()192     public AssetManager getAssets() {
193         if (mAssetsFromApk == null) {
194             Context resourcesApkContext = getResourcesApkContext();
195             if (resourcesApkContext != null) {
196                 mAssetsFromApk = resourcesApkContext.getAssets();
197             }
198         }
199         return mAssetsFromApk;
200     }
201 
202     /** Retrieve resources held in the Safety Center resources APK. */
203     @Override
getResources()204     public Resources getResources() {
205         if (mResourcesFromApk == null) {
206             Context resourcesApkContext = getResourcesApkContext();
207             if (resourcesApkContext != null) {
208                 mResourcesFromApk = resourcesApkContext.getResources();
209             }
210         }
211         return mResourcesFromApk;
212     }
213 
214     /** Retrieve theme held in the Safety Center resources APK. */
215     @Override
getTheme()216     public Resources.Theme getTheme() {
217         if (mThemeFromApk == null) {
218             Context resourcesApkContext = getResourcesApkContext();
219             if (resourcesApkContext != null) {
220                 mThemeFromApk = resourcesApkContext.getTheme();
221             }
222         }
223         return mThemeFromApk;
224     }
225 }
226