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