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 package com.android.car.settings.wifi; 17 18 import android.annotation.DrawableRes; 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.NetworkInfo; 26 import android.net.wifi.WifiConfiguration; 27 import android.net.wifi.WifiManager; 28 import android.provider.Settings; 29 import android.widget.Toast; 30 31 import androidx.annotation.StringRes; 32 33 import com.android.car.settings.R; 34 import com.android.car.settings.common.Logger; 35 import com.android.settingslib.wifi.AccessPoint; 36 37 import java.util.regex.Pattern; 38 39 /** 40 * A collections of util functions for WIFI. 41 */ 42 public class WifiUtil { 43 44 private static final Logger LOG = new Logger(WifiUtil.class); 45 46 /** Value that is returned when we fail to connect wifi. */ 47 public static final int INVALID_NET_ID = -1; 48 private static final Pattern HEX_PATTERN = Pattern.compile("^[0-9A-F]+$"); 49 50 @DrawableRes getIconRes(int state)51 public static int getIconRes(int state) { 52 switch (state) { 53 case WifiManager.WIFI_STATE_ENABLING: 54 case WifiManager.WIFI_STATE_DISABLED: 55 return R.drawable.ic_settings_wifi_disabled; 56 default: 57 return R.drawable.ic_settings_wifi; 58 } 59 } 60 isWifiOn(int state)61 public static boolean isWifiOn(int state) { 62 switch (state) { 63 case WifiManager.WIFI_STATE_ENABLING: 64 case WifiManager.WIFI_STATE_DISABLED: 65 return false; 66 default: 67 return true; 68 } 69 } 70 71 /** 72 * @return 0 if no proper description can be found. 73 */ 74 @StringRes getStateDesc(int state)75 public static Integer getStateDesc(int state) { 76 switch (state) { 77 case WifiManager.WIFI_STATE_ENABLING: 78 return R.string.wifi_starting; 79 case WifiManager.WIFI_STATE_DISABLING: 80 return R.string.wifi_stopping; 81 case WifiManager.WIFI_STATE_DISABLED: 82 return R.string.wifi_disabled; 83 default: 84 return 0; 85 } 86 } 87 88 /** 89 * Returns {@Code true} if wifi is available on this device. 90 */ isWifiAvailable(Context context)91 public static boolean isWifiAvailable(Context context) { 92 return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI); 93 } 94 95 /** 96 * Gets a unique key for a {@link AccessPoint}. 97 */ getKey(AccessPoint accessPoint)98 public static String getKey(AccessPoint accessPoint) { 99 return String.valueOf(accessPoint.hashCode()); 100 } 101 102 /** 103 * This method is a stripped and negated version of WifiConfigStore.canModifyNetwork. 104 * 105 * @param context Context of caller 106 * @param config The WiFi config. 107 * @return {@code true} if Settings cannot modify the config due to lockDown. 108 */ isNetworkLockedDown(Context context, WifiConfiguration config)109 public static boolean isNetworkLockedDown(Context context, WifiConfiguration config) { 110 if (config == null) { 111 return false; 112 } 113 114 final DevicePolicyManager dpm = 115 (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE); 116 final PackageManager pm = context.getPackageManager(); 117 118 // Check if device has DPM capability. If it has and dpm is still null, then we 119 // treat this case with suspicion and bail out. 120 if (pm.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN) && dpm == null) { 121 return true; 122 } 123 124 boolean isConfigEligibleForLockdown = false; 125 if (dpm != null) { 126 final ComponentName deviceOwner = dpm.getDeviceOwnerComponentOnAnyUser(); 127 if (deviceOwner != null) { 128 final int deviceOwnerUserId = dpm.getDeviceOwnerUserId(); 129 try { 130 final int deviceOwnerUid = pm.getPackageUidAsUser(deviceOwner.getPackageName(), 131 deviceOwnerUserId); 132 isConfigEligibleForLockdown = deviceOwnerUid == config.creatorUid; 133 } catch (PackageManager.NameNotFoundException e) { 134 // don't care 135 } 136 } 137 } 138 if (!isConfigEligibleForLockdown) { 139 return false; 140 } 141 142 final ContentResolver resolver = context.getContentResolver(); 143 final boolean isLockdownFeatureEnabled = Settings.Global.getInt(resolver, 144 Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0) != 0; 145 return isLockdownFeatureEnabled; 146 } 147 148 /** 149 * Returns {@code true} if the provided NetworkCapabilities indicate a captive portal network. 150 */ 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 * Returns netId. -1 if connection fails. 158 */ connectToAccessPoint(Context context, String ssid, int security, String password, boolean hidden)159 public static int connectToAccessPoint(Context context, String ssid, int security, 160 String password, boolean hidden) { 161 WifiManager wifiManager = context.getSystemService(WifiManager.class); 162 WifiConfiguration wifiConfig = new WifiConfiguration(); 163 wifiConfig.SSID = String.format("\"%s\"", ssid); 164 wifiConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP); 165 wifiConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP); 166 wifiConfig.allowedProtocols.set(WifiConfiguration.Protocol.RSN); 167 wifiConfig.allowedProtocols.set(WifiConfiguration.Protocol.WPA); 168 wifiConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40); 169 wifiConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP104); 170 wifiConfig.hiddenSSID = hidden; 171 switch (security) { 172 case AccessPoint.SECURITY_NONE: 173 wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); 174 wifiConfig.allowedAuthAlgorithms.clear(); 175 wifiConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP); 176 wifiConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP); 177 break; 178 case AccessPoint.SECURITY_WEP: 179 wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); 180 wifiConfig.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN); 181 wifiConfig.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED); 182 wifiConfig.wepKeys[0] = isHexString(password) ? password 183 : "\"" + password + "\""; 184 wifiConfig.wepTxKeyIndex = 0; 185 break; 186 case AccessPoint.SECURITY_PSK: 187 case AccessPoint.SECURITY_EAP: 188 wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); 189 wifiConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP); 190 wifiConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP); 191 wifiConfig.preSharedKey = String.format("\"%s\"", password); 192 break; 193 default: 194 throw new IllegalArgumentException("invalid security type"); 195 } 196 int netId = wifiManager.addNetwork(wifiConfig); 197 // This only means wifiManager failed writing the new wifiConfig to the db. It doesn't mean 198 // the network is invalid. 199 if (netId == INVALID_NET_ID) { 200 Toast.makeText(context, R.string.wifi_failed_connect_message, 201 Toast.LENGTH_SHORT).show(); 202 } else { 203 wifiManager.enableNetwork(netId, true); 204 } 205 return netId; 206 } 207 208 /** Forget the network specified by {@code accessPoint}. */ forget(Context context, AccessPoint accessPoint)209 public static void forget(Context context, AccessPoint accessPoint) { 210 WifiManager wifiManager = context.getSystemService(WifiManager.class); 211 if (!accessPoint.isSaved()) { 212 if (accessPoint.getNetworkInfo() != null 213 && accessPoint.getNetworkInfo().getState() != NetworkInfo.State.DISCONNECTED) { 214 // Network is active but has no network ID - must be ephemeral. 215 wifiManager.disableEphemeralNetwork( 216 AccessPoint.convertToQuotedString(accessPoint.getSsidStr())); 217 } else { 218 // Should not happen, but a monkey seems to trigger it 219 LOG.e("Failed to forget invalid network " + accessPoint.getConfig()); 220 return; 221 } 222 } else { 223 wifiManager.forget(accessPoint.getConfig().networkId, 224 new ActionFailedListener(context, R.string.wifi_failed_forget_message)); 225 } 226 } 227 228 /** Returns {@code true} if the access point was disabled due to the wrong password. */ isAccessPointDisabledByWrongPassword(AccessPoint accessPoint)229 public static boolean isAccessPointDisabledByWrongPassword(AccessPoint accessPoint) { 230 WifiConfiguration config = accessPoint.getConfig(); 231 if (config == null) { 232 return false; 233 } 234 WifiConfiguration.NetworkSelectionStatus networkStatus = 235 config.getNetworkSelectionStatus(); 236 if (networkStatus == null || networkStatus.isNetworkEnabled()) { 237 return false; 238 } 239 return networkStatus.getNetworkSelectionDisableReason() 240 == WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WRONG_PASSWORD; 241 } 242 isHexString(String password)243 private static boolean isHexString(String password) { 244 return HEX_PATTERN.matcher(password).matches(); 245 } 246 247 /** 248 * A shared implementation of {@link WifiManager.ActionListener} which shows a failure message 249 * in a toast. 250 */ 251 public static class ActionFailedListener implements WifiManager.ActionListener { 252 private final Context mContext; 253 @StringRes 254 private final int mFailureMessage; 255 ActionFailedListener(Context context, @StringRes int failureMessage)256 public ActionFailedListener(Context context, @StringRes int failureMessage) { 257 mContext = context; 258 mFailureMessage = failureMessage; 259 } 260 261 @Override onSuccess()262 public void onSuccess() { 263 } 264 265 @Override onFailure(int reason)266 public void onFailure(int reason) { 267 Toast.makeText(mContext, mFailureMessage, Toast.LENGTH_SHORT).show(); 268 } 269 } 270 } 271