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 android.net.wifi;
18 
19 import android.annotation.NonNull;
20 import android.app.ActivityManager;
21 import android.content.Context;
22 import android.content.ContextWrapper;
23 import android.content.Intent;
24 import android.content.pm.PackageManager;
25 import android.content.pm.ResolveInfo;
26 import android.content.res.AssetManager;
27 import android.content.res.Resources;
28 import android.net.wifi.util.Environment;
29 import android.net.wifi.util.WifiResourceCache;
30 import android.os.UserHandle;
31 import android.util.Log;
32 import android.util.SparseArray;
33 
34 import androidx.annotation.Nullable;
35 
36 import java.util.List;
37 import java.util.stream.Collectors;
38 
39 /**
40  * Wrapper for context to override getResources method. Resources for wifi mainline jar needs to be
41  * fetched from the resources APK.
42  *
43  * @hide
44  */
45 public class WifiContext extends ContextWrapper {
46     private static final String TAG = "WifiContext";
47     /** Intent action that is used to identify ServiceWifiResources.apk */
48     private static final String ACTION_RESOURCES_APK =
49             "com.android.server.wifi.intent.action.SERVICE_WIFI_RESOURCES_APK";
50     /** Intent action that is used to identify WifiDialog.apk */
51     private static final String ACTION_WIFI_DIALOG_APK =
52             "com.android.server.wifi.intent.action.WIFI_DIALOG_APK";
53 
54     /** Since service-wifi runs within system_server, its package name is "android". */
55     private static final String SERVICE_WIFI_PACKAGE_NAME = "android";
56 
57     private String mWifiOverlayApkPkgName;
58     private String mWifiDialogApkPkgName;
59 
60     // Cached resources from the resources APK.
61     private AssetManager mWifiAssetsFromApk;
62     private Resources mWifiResourcesFromApk;
63     private Resources.Theme mWifiThemeFromApk;
64     private Context mResourcesApkContext;
65     private SparseArray<WifiStringResourceWrapper> mWifiStringResourceWrapperSparseArray =
66             new SparseArray<>();
67     private WifiResourceCache mWifiResourceCache;
68 
WifiContext(@onNull Context contextBase)69     public WifiContext(@NonNull Context contextBase) {
70         super(contextBase);
71         mWifiResourceCache = new WifiResourceCache(this);
72     }
73 
74     /** Get the package name of ServiceWifiResources.apk */
getWifiOverlayApkPkgName()75     public @Nullable String getWifiOverlayApkPkgName() {
76         if (mWifiOverlayApkPkgName != null) {
77             return mWifiOverlayApkPkgName;
78         }
79         mWifiOverlayApkPkgName = getApkPkgNameForAction(ACTION_RESOURCES_APK, null);
80         if (mWifiOverlayApkPkgName == null) {
81             // Resource APK not loaded yet, print a stack trace to see where this is called from
82             Log.e(TAG, "Attempted to fetch resources before Wifi Resources APK is loaded!",
83                     new IllegalStateException());
84             return null;
85         }
86         Log.i(TAG, "Found Wifi Resources APK at: " + mWifiOverlayApkPkgName);
87         return mWifiOverlayApkPkgName;
88     }
89 
90     /** Get the package name of WifiDialog.apk */
getWifiDialogApkPkgName()91     public @Nullable String getWifiDialogApkPkgName() {
92         if (mWifiDialogApkPkgName != null) {
93             return mWifiDialogApkPkgName;
94         }
95         mWifiDialogApkPkgName = getApkPkgNameForAction(ACTION_WIFI_DIALOG_APK,
96                 UserHandle.of(ActivityManager.getCurrentUser()));
97         if (mWifiDialogApkPkgName == null) {
98             // WifiDialog APK not loaded yet, print a stack trace to see where this is called from
99             Log.e(TAG, "Attempted to fetch WifiDialog apk before it is loaded!",
100                     new IllegalStateException());
101             return null;
102         }
103         Log.i(TAG, "Found Wifi Dialog APK at: " + mWifiDialogApkPkgName);
104         return mWifiDialogApkPkgName;
105     }
106 
107     /** Gets the package name of the apk responding to the given intent action */
getApkPkgNameForAction(@onNull String action, UserHandle userHandle)108     private String getApkPkgNameForAction(@NonNull String action, UserHandle userHandle) {
109 
110         List<ResolveInfo> resolveInfos;
111         if (userHandle != null) {
112             resolveInfos = getPackageManager().queryIntentActivitiesAsUser(
113                     new Intent(action),
114                     PackageManager.MATCH_SYSTEM_ONLY,
115                     userHandle);
116         } else {
117             resolveInfos = getPackageManager().queryIntentActivities(
118                     new Intent(action),
119                     PackageManager.MATCH_SYSTEM_ONLY);
120         }
121         Log.i(TAG, "Got resolveInfos for " + action + ": " + resolveInfos);
122 
123         // remove apps that don't live in the Wifi apex
124         resolveInfos.removeIf(info ->
125                 !Environment.isAppInWifiApex(info.activityInfo.applicationInfo));
126 
127         if (resolveInfos.isEmpty()) {
128             return null;
129         }
130 
131         if (resolveInfos.size() > 1) {
132             // multiple apps found, log a warning, but continue
133             Log.w(TAG, "Found > 1 APK that can resolve " + action + ": "
134                     + resolveInfos.stream()
135                     .map(info -> info.activityInfo.applicationInfo.packageName)
136                     .collect(Collectors.joining(", ")));
137         }
138 
139         // Assume the first ResolveInfo is the one we're looking for
140         ResolveInfo info = resolveInfos.get(0);
141         return info.activityInfo.applicationInfo.packageName;
142     }
143 
144     /** Get the Resource APK context */
getResourcesApkContext()145     public Context getResourcesApkContext() {
146         if (mResourcesApkContext != null) {
147             return mResourcesApkContext;
148         }
149         try {
150             String packageName = getWifiOverlayApkPkgName();
151             if (packageName != null) {
152                 mResourcesApkContext = createPackageContext(packageName, 0);
153             }
154         } catch (PackageManager.NameNotFoundException e) {
155             Log.wtf(TAG, "Failed to load resources", e);
156         }
157         return mResourcesApkContext;
158     }
159 
160     /**
161      * Retrieve assets held in the wifi resources APK.
162      */
163     @Override
getAssets()164     public AssetManager getAssets() {
165         if (mWifiAssetsFromApk == null) {
166             Context resourcesApkContext = getResourcesApkContext();
167             if (resourcesApkContext != null) {
168                 mWifiAssetsFromApk = resourcesApkContext.getAssets();
169             }
170         }
171         return mWifiAssetsFromApk;
172     }
173 
174     /**
175      * Retrieve resources held in the wifi resources APK.
176      */
177     @Override
getResources()178     public Resources getResources() {
179         if (mWifiResourcesFromApk == null) {
180             Context resourcesApkContext = getResourcesApkContext();
181             if (resourcesApkContext != null) {
182                 mWifiResourcesFromApk = resourcesApkContext.getResources();
183             }
184         }
185         return mWifiResourcesFromApk;
186     }
187 
188     /**
189      * Retrieve theme held in the wifi resources APK.
190      */
191     @Override
getTheme()192     public Resources.Theme getTheme() {
193         if (mWifiThemeFromApk == null) {
194             Context resourcesApkContext = getResourcesApkContext();
195             if (resourcesApkContext != null) {
196                 mWifiThemeFromApk = resourcesApkContext.getTheme();
197             }
198         }
199         return mWifiThemeFromApk;
200     }
201 
getResourceCache()202     public WifiResourceCache getResourceCache() {
203         return mWifiResourceCache;
204     }
205 
206     /** Get the package name that service-wifi runs under. */
getServiceWifiPackageName()207     public String getServiceWifiPackageName() {
208         return SERVICE_WIFI_PACKAGE_NAME;
209     }
210 
211     /**
212      * Reset the resource cache which will cause it to be reloaded next time it is accessed.
213      */
resetResourceCache()214     public void resetResourceCache() {
215         mWifiOverlayApkPkgName = null;
216         mWifiAssetsFromApk = null;
217         mWifiResourcesFromApk = null;
218         mWifiThemeFromApk = null;
219         mResourcesApkContext = null;
220         mWifiStringResourceWrapperSparseArray.clear();
221         mWifiResourceCache.reset();
222     }
223 
224     /**
225      * Returns an instance of WifiStringResourceWrapper with the given subId and carrierId.
226      */
getStringResourceWrapper(int subId, int carrierId)227     public WifiStringResourceWrapper getStringResourceWrapper(int subId, int carrierId) {
228         if (mWifiStringResourceWrapperSparseArray.contains(subId)) {
229             return mWifiStringResourceWrapperSparseArray.get(subId);
230         }
231         WifiStringResourceWrapper wrapper = new WifiStringResourceWrapper(this, subId, carrierId);
232         mWifiStringResourceWrapperSparseArray.append(subId, wrapper);
233         return wrapper;
234     }
235 }
236