1 /*
2  * Copyright (C) 2017 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.settings.wifi;
18 
19 import android.app.admin.DevicePolicyManager;
20 import android.content.ComponentName;
21 import android.content.ContentResolver;
22 import android.content.Context;
23 import android.content.pm.PackageManager;
24 import android.net.NetworkCapabilities;
25 import android.net.TetheringManager;
26 import android.net.wifi.ScanResult;
27 import android.net.wifi.SoftApConfiguration;
28 import android.net.wifi.WifiConfiguration;
29 import android.net.wifi.WifiManager;
30 import android.os.UserHandle;
31 import android.os.UserManager;
32 import android.provider.Settings;
33 import android.text.TextUtils;
34 import android.util.Log;
35 
36 import androidx.annotation.VisibleForTesting;
37 
38 import com.android.settings.R;
39 import com.android.settings.Utils;
40 import com.android.wifitrackerlib.WifiEntry;
41 
42 import java.nio.charset.StandardCharsets;
43 
44 /** A utility class for Wi-Fi functions. */
45 public class WifiUtils extends com.android.settingslib.wifi.WifiUtils {
46 
47     static final String TAG = "WifiUtils";
48 
49     private static final int SSID_ASCII_MIN_LENGTH = 1;
50     private static final int SSID_ASCII_MAX_LENGTH = 32;
51 
52     private static final int PSK_PASSPHRASE_ASCII_MIN_LENGTH = 8;
53     private static final int PSK_PASSPHRASE_ASCII_MAX_LENGTH = 63;
54 
55     private static Boolean sCanShowWifiHotspotCached;
56 
isSSIDTooLong(String ssid)57     public static boolean isSSIDTooLong(String ssid) {
58         if (TextUtils.isEmpty(ssid)) {
59             return false;
60         }
61         return ssid.getBytes(StandardCharsets.UTF_8).length > SSID_ASCII_MAX_LENGTH;
62     }
63 
isSSIDTooShort(String ssid)64     public static boolean isSSIDTooShort(String ssid) {
65         if (TextUtils.isEmpty(ssid)) {
66             return true;
67         }
68         return ssid.length() < SSID_ASCII_MIN_LENGTH;
69     }
70 
71     /**
72      * Check if the hotspot password is valid.
73      */
isHotspotPasswordValid(String password, int securityType)74     public static boolean isHotspotPasswordValid(String password, int securityType) {
75         final SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder();
76         try {
77             if (securityType == SoftApConfiguration.SECURITY_TYPE_WPA2_PSK
78                     || securityType == SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION) {
79                 if (password.length() < PSK_PASSPHRASE_ASCII_MIN_LENGTH
80                         || password.length() > PSK_PASSPHRASE_ASCII_MAX_LENGTH) {
81                     return false;
82                 }
83             }
84             configBuilder.setPassphrase(password, securityType);
85         } catch (Exception e) {
86             return false;
87         }
88         return true;
89     }
90 
91     /**
92      * This method is a stripped and negated version of WifiConfigStore.canModifyNetwork.
93      *
94      * @param context Context of caller
95      * @param config  The WiFi config.
96      * @return true if Settings cannot modify the config due to lockDown.
97      */
isNetworkLockedDown(Context context, WifiConfiguration config)98     public static boolean isNetworkLockedDown(Context context, WifiConfiguration config) {
99         if (context == null || config == null) {
100             return false;
101         }
102 
103         final DevicePolicyManager dpm =
104                 (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
105         final PackageManager pm = context.getPackageManager();
106         final UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
107 
108         // Check if device has DPM capability. If it has and dpm is still null, then we
109         // treat this case with suspicion and bail out.
110         if (pm.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN) && dpm == null) {
111             return true;
112         }
113 
114         boolean isConfigEligibleForLockdown = false;
115         if (dpm != null) {
116             final ComponentName deviceOwner = dpm.getDeviceOwnerComponentOnAnyUser();
117             if (deviceOwner != null) {
118                 final int deviceOwnerUserId = dpm.getDeviceOwnerUserId();
119                 try {
120                     final int deviceOwnerUid = pm.getPackageUidAsUser(deviceOwner.getPackageName(),
121                             deviceOwnerUserId);
122                     isConfigEligibleForLockdown = deviceOwnerUid == config.creatorUid;
123                 } catch (PackageManager.NameNotFoundException e) {
124                     // don't care
125                 }
126             } else if (dpm.isOrganizationOwnedDeviceWithManagedProfile()) {
127                 int profileOwnerUserId = Utils.getManagedProfileId(um, UserHandle.myUserId());
128                 final ComponentName profileOwner = dpm.getProfileOwnerAsUser(profileOwnerUserId);
129                 if (profileOwner != null) {
130                     try {
131                         final int profileOwnerUid = pm.getPackageUidAsUser(
132                                 profileOwner.getPackageName(), profileOwnerUserId);
133                         isConfigEligibleForLockdown = profileOwnerUid == config.creatorUid;
134                     } catch (PackageManager.NameNotFoundException e) {
135                         // don't care
136                     }
137                 }
138             }
139         }
140         if (!isConfigEligibleForLockdown) {
141             return false;
142         }
143 
144         final ContentResolver resolver = context.getContentResolver();
145         final boolean isLockdownFeatureEnabled = Settings.Global.getInt(resolver,
146                 Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0) != 0;
147         return isLockdownFeatureEnabled;
148     }
149 
150     /** Returns true if the provided NetworkCapabilities indicate a captive portal network. */
canSignIntoNetwork(NetworkCapabilities capabilities)151     public static boolean canSignIntoNetwork(NetworkCapabilities capabilities) {
152         return (capabilities != null
153                 && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL));
154     }
155 
156     /**
157      * Provides a simple way to generate a new {@link WifiConfiguration} obj from
158      * {@link ScanResult} or {@link WifiEntry}. Either {@code wifiEntry} or {@code scanResult
159      * } input should be not null for retrieving information, otherwise will throw
160      * IllegalArgumentException.
161      * This method prefers to take {@link WifiEntry} input in priority. Therefore this method
162      * will take {@link WifiEntry} input as preferred data extraction source when you input
163      * both {@link WifiEntry} and {@link ScanResult}, and ignore {@link ScanResult} input.
164      *
165      * Duplicated and simplified method from {@link WifiConfigController#getConfig()}.
166      * TODO(b/120827021): Should be removed if the there is have a common one in shared place (e.g.
167      * SettingsLib).
168      *
169      * @param wifiEntry Input data for retrieving WifiConfiguration.
170      * @param scanResult  Input data for retrieving WifiConfiguration.
171      * @return WifiConfiguration obj based on input.
172      */
getWifiConfig(WifiEntry wifiEntry, ScanResult scanResult)173     public static WifiConfiguration getWifiConfig(WifiEntry wifiEntry, ScanResult scanResult) {
174         if (wifiEntry == null && scanResult == null) {
175             throw new IllegalArgumentException(
176                     "At least one of WifiEntry and ScanResult input is required.");
177         }
178 
179         final WifiConfiguration config = new WifiConfiguration();
180         final int security;
181 
182         if (wifiEntry == null) {
183             config.SSID = "\"" + scanResult.SSID + "\"";
184             security = getWifiEntrySecurity(scanResult);
185         } else {
186             if (wifiEntry.getWifiConfiguration() == null) {
187                 config.SSID = "\"" + wifiEntry.getSsid() + "\"";
188             } else {
189                 config.networkId = wifiEntry.getWifiConfiguration().networkId;
190                 config.hiddenSSID = wifiEntry.getWifiConfiguration().hiddenSSID;
191             }
192             security = wifiEntry.getSecurity();
193         }
194 
195         switch (security) {
196             case WifiEntry.SECURITY_NONE:
197                 config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OPEN);
198                 break;
199 
200             case WifiEntry.SECURITY_WEP:
201                 config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_WEP);
202                 break;
203 
204             case WifiEntry.SECURITY_PSK:
205                 config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
206                 break;
207 
208             case WifiEntry.SECURITY_EAP_SUITE_B:
209                 config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP_SUITE_B);
210                 break;
211 
212             case WifiEntry.SECURITY_EAP:
213                 config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP);
214                 break;
215 
216             case WifiEntry.SECURITY_SAE:
217                 config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE);
218                 break;
219 
220             case WifiEntry.SECURITY_OWE:
221                 config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OWE);
222                 break;
223 
224             default:
225                 break;
226         }
227         return config;
228     }
229 
230     /**
231      * Gets security value from ScanResult.
232      *
233      * @param result ScanResult
234      * @return Related security value based on {@link WifiEntry}.
235      */
getWifiEntrySecurity(ScanResult result)236     public static int getWifiEntrySecurity(ScanResult result) {
237         if (result.capabilities.contains("WEP")) {
238             return WifiEntry.SECURITY_WEP;
239         } else if (result.capabilities.contains("SAE")) {
240             return WifiEntry.SECURITY_SAE;
241         } else if (result.capabilities.contains("PSK")) {
242             return WifiEntry.SECURITY_PSK;
243         } else if (result.capabilities.contains("EAP_SUITE_B_192")) {
244             return WifiEntry.SECURITY_EAP_SUITE_B;
245         } else if (result.capabilities.contains("EAP")) {
246             return WifiEntry.SECURITY_EAP;
247         } else if (result.capabilities.contains("OWE")) {
248             return WifiEntry.SECURITY_OWE;
249         }
250 
251         return WifiEntry.SECURITY_NONE;
252     }
253 
254     /**
255      * Check if Wi-Fi hotspot settings can be displayed.
256      *
257      * @param context Context of caller
258      * @return true if Wi-Fi hotspot settings can be displayed
259      */
checkShowWifiHotspot(Context context)260     public static boolean checkShowWifiHotspot(Context context) {
261         if (context == null) return false;
262 
263         boolean showWifiHotspotSettings =
264                 context.getResources().getBoolean(R.bool.config_show_wifi_hotspot_settings);
265         if (!showWifiHotspotSettings) {
266             Log.w(TAG, "config_show_wifi_hotspot_settings:false");
267             return false;
268         }
269 
270         WifiManager wifiManager = context.getSystemService(WifiManager.class);
271         if (wifiManager == null) {
272             Log.e(TAG, "WifiManager is null");
273             return false;
274         }
275 
276         TetheringManager tetheringManager = context.getSystemService(TetheringManager.class);
277         if (tetheringManager == null) {
278             Log.e(TAG, "TetheringManager is null");
279             return false;
280         }
281         String[] wifiRegexs = tetheringManager.getTetherableWifiRegexs();
282         if (wifiRegexs == null || wifiRegexs.length == 0) {
283             Log.w(TAG, "TetherableWifiRegexs is empty");
284             return false;
285         }
286         return true;
287     }
288 
289     /**
290      * Return the cached result to see if Wi-Fi hotspot settings can be displayed.
291      *
292      * @param context Context of caller
293      * @return true if Wi-Fi hotspot settings can be displayed
294      */
canShowWifiHotspot(Context context)295     public static boolean canShowWifiHotspot(Context context) {
296         if (sCanShowWifiHotspotCached == null) {
297             sCanShowWifiHotspotCached = checkShowWifiHotspot(context);
298         }
299         return sCanShowWifiHotspotCached;
300     }
301 
302     /**
303      * Sets the sCanShowWifiHotspotCached for testing purposes.
304      *
305      * @param cached Cached value for #canShowWifiHotspot()
306      */
307     @VisibleForTesting
setCanShowWifiHotspotCached(Boolean cached)308     public static void setCanShowWifiHotspotCached(Boolean cached) {
309         sCanShowWifiHotspotCached = cached;
310     }
311 }
312