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 static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLED;
19 
20 import android.annotation.DrawableRes;
21 import android.annotation.Nullable;
22 import android.app.admin.DevicePolicyManager;
23 import android.content.ComponentName;
24 import android.content.ContentResolver;
25 import android.content.Context;
26 import android.content.pm.PackageManager;
27 import android.net.NetworkCapabilities;
28 import android.net.NetworkInfo;
29 import android.net.wifi.WifiConfiguration;
30 import android.net.wifi.WifiManager;
31 import android.provider.Settings;
32 import android.text.TextUtils;
33 import android.widget.Toast;
34 
35 import androidx.annotation.StringRes;
36 
37 import com.android.car.settings.R;
38 import com.android.car.settings.common.Logger;
39 import com.android.settingslib.wifi.AccessPoint;
40 
41 import java.util.regex.Pattern;
42 
43 /**
44  * A collections of util functions for WIFI.
45  */
46 public class WifiUtil {
47 
48     private static final Logger LOG = new Logger(WifiUtil.class);
49 
50     /** Value that is returned when we fail to connect wifi. */
51     public static final int INVALID_NET_ID = -1;
52     private static final Pattern HEX_PATTERN = Pattern.compile("^[0-9A-F]+$");
53 
54     @DrawableRes
getIconRes(int state)55     public static int getIconRes(int state) {
56         switch (state) {
57             case WifiManager.WIFI_STATE_ENABLING:
58             case WifiManager.WIFI_STATE_DISABLED:
59                 return R.drawable.ic_settings_wifi_disabled;
60             default:
61                 return R.drawable.ic_settings_wifi;
62         }
63     }
64 
isWifiOn(int state)65     public static boolean isWifiOn(int state) {
66         switch (state) {
67             case WifiManager.WIFI_STATE_ENABLING:
68             case WifiManager.WIFI_STATE_DISABLED:
69                 return false;
70             default:
71                 return true;
72         }
73     }
74 
75     /**
76      * @return 0 if no proper description can be found.
77      */
78     @StringRes
getStateDesc(int state)79     public static Integer getStateDesc(int state) {
80         switch (state) {
81             case WifiManager.WIFI_STATE_ENABLING:
82                 return R.string.wifi_starting;
83             case WifiManager.WIFI_STATE_DISABLING:
84                 return R.string.wifi_stopping;
85             case WifiManager.WIFI_STATE_DISABLED:
86                 return R.string.wifi_disabled;
87             default:
88                 return 0;
89         }
90     }
91 
92     /**
93      * Returns {@Code true} if wifi is available on this device.
94      */
isWifiAvailable(Context context)95     public static boolean isWifiAvailable(Context context) {
96         return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI);
97     }
98 
99     /**
100      * Gets a unique key for a {@link AccessPoint}.
101      */
getKey(AccessPoint accessPoint)102     public static String getKey(AccessPoint accessPoint) {
103         return String.valueOf(accessPoint.hashCode());
104     }
105 
106     /**
107      * This method is a stripped and negated version of WifiConfigStore.canModifyNetwork.
108      *
109      * @param context Context of caller
110      * @param config  The WiFi config.
111      * @return {@code true} if Settings cannot modify the config due to lockDown.
112      */
isNetworkLockedDown(Context context, WifiConfiguration config)113     public static boolean isNetworkLockedDown(Context context, WifiConfiguration config) {
114         if (config == null) {
115             return false;
116         }
117 
118         final DevicePolicyManager dpm =
119                 (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
120         final PackageManager pm = context.getPackageManager();
121 
122         // Check if device has DPM capability. If it has and dpm is still null, then we
123         // treat this case with suspicion and bail out.
124         if (pm.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN) && dpm == null) {
125             return true;
126         }
127 
128         boolean isConfigEligibleForLockdown = false;
129         if (dpm != null) {
130             final ComponentName deviceOwner = dpm.getDeviceOwnerComponentOnAnyUser();
131             if (deviceOwner != null) {
132                 final int deviceOwnerUserId = dpm.getDeviceOwnerUserId();
133                 try {
134                     final int deviceOwnerUid = pm.getPackageUidAsUser(deviceOwner.getPackageName(),
135                             deviceOwnerUserId);
136                     isConfigEligibleForLockdown = deviceOwnerUid == config.creatorUid;
137                 } catch (PackageManager.NameNotFoundException e) {
138                     // don't care
139                 }
140             }
141         }
142         if (!isConfigEligibleForLockdown) {
143             return false;
144         }
145 
146         final ContentResolver resolver = context.getContentResolver();
147         final boolean isLockdownFeatureEnabled = Settings.Global.getInt(resolver,
148                 Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0) != 0;
149         return isLockdownFeatureEnabled;
150     }
151 
152     /**
153      * Returns {@code true} if the network security type doesn't require authentication.
154      */
isOpenNetwork(int security)155     public static boolean isOpenNetwork(int security) {
156         return security == AccessPoint.SECURITY_NONE || security == AccessPoint.SECURITY_OWE;
157     }
158 
159     /**
160      * Returns {@code true} if the provided NetworkCapabilities indicate a captive portal network.
161      */
canSignIntoNetwork(NetworkCapabilities capabilities)162     public static boolean canSignIntoNetwork(NetworkCapabilities capabilities) {
163         return (capabilities != null
164                 && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL));
165     }
166 
167     /**
168      * Attempts to connect to a specified access point
169      * @param listener for callbacks on success or failure of connection attempt (can be null)
170      */
connectToAccessPoint(Context context, String ssid, int security, String password, boolean hidden, @Nullable WifiManager.ActionListener listener)171     public static void connectToAccessPoint(Context context, String ssid, int security,
172             String password, boolean hidden, @Nullable WifiManager.ActionListener listener) {
173         WifiManager wifiManager = context.getSystemService(WifiManager.class);
174         WifiConfiguration wifiConfig = getWifiConfig(ssid, security, password, hidden);
175         wifiManager.connect(wifiConfig, listener);
176     }
177 
getWifiConfig(String ssid, int security, String password, boolean hidden)178     private static WifiConfiguration getWifiConfig(String ssid, int security,
179             String password, boolean hidden) {
180         WifiConfiguration wifiConfig = new WifiConfiguration();
181         wifiConfig.SSID = String.format("\"%s\"", ssid);
182         wifiConfig.hiddenSSID = hidden;
183         switch (security) {
184             case AccessPoint.SECURITY_NONE:
185                 wifiConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OPEN);
186                 break;
187             case AccessPoint.SECURITY_WEP:
188                 wifiConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_WEP);
189                 if (!TextUtils.isEmpty(password)) {
190                     int length = password.length();
191                     // WEP-40, WEP-104, and 256-bit WEP (WEP-232?)
192                     if ((length == 10 || length == 26 || length == 58)
193                             && password.matches("[0-9A-Fa-f]*")) {
194                         wifiConfig.wepKeys[0] = password;
195                     } else {
196                         wifiConfig.wepKeys[0] = '"' + password + '"';
197                     }
198                 }
199                 break;
200             case AccessPoint.SECURITY_PSK:
201                 wifiConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
202                 if (!TextUtils.isEmpty(password)) {
203                     if (password.matches("[0-9A-Fa-f]{64}")) {
204                         wifiConfig.preSharedKey = password;
205                     } else {
206                         wifiConfig.preSharedKey = '"' + password + '"';
207                     }
208                 }
209                 break;
210             case AccessPoint.SECURITY_EAP:
211             case AccessPoint.SECURITY_EAP_SUITE_B:
212                 if (security == AccessPoint.SECURITY_EAP_SUITE_B) {
213                     // allowedSuiteBCiphers will be set according to certificate type
214                     wifiConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP_SUITE_B);
215                 } else {
216                     wifiConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP);
217                 }
218                 if (!TextUtils.isEmpty(password)) {
219                     wifiConfig.enterpriseConfig.setPassword(password);
220                 }
221                 break;
222             case AccessPoint.SECURITY_SAE:
223                 wifiConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE);
224                 if (!TextUtils.isEmpty(password)) {
225                     wifiConfig.preSharedKey = '"' + password + '"';
226                 }
227                 break;
228             case AccessPoint.SECURITY_OWE:
229                 wifiConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OWE);
230                 break;
231             default:
232                 throw new IllegalArgumentException("unknown security type " + security);
233         }
234         return wifiConfig;
235     }
236 
237 
238     /** Forget the network specified by {@code accessPoint}. */
forget(Context context, AccessPoint accessPoint)239     public static void forget(Context context, AccessPoint accessPoint) {
240         WifiManager wifiManager = context.getSystemService(WifiManager.class);
241         if (!accessPoint.isSaved()) {
242             if (accessPoint.getNetworkInfo() != null
243                     && accessPoint.getNetworkInfo().getState() != NetworkInfo.State.DISCONNECTED) {
244                 // Network is active but has no network ID - must be ephemeral.
245                 wifiManager.disableEphemeralNetwork(
246                         AccessPoint.convertToQuotedString(accessPoint.getSsidStr()));
247             } else {
248                 // Should not happen, but a monkey seems to trigger it
249                 LOG.e("Failed to forget invalid network " + accessPoint.getConfig());
250                 return;
251             }
252         } else {
253             wifiManager.forget(accessPoint.getConfig().networkId, new WifiManager.ActionListener() {
254                 @Override
255                 public void onSuccess() {
256                     LOG.d("Network successfully forgotten");
257                 }
258 
259                 @Override
260                 public void onFailure(int reason) {
261                     LOG.d("Could not forget network. Failure code: " + reason);
262                     Toast.makeText(context, R.string.wifi_failed_forget_message,
263                             Toast.LENGTH_SHORT).show();
264                 }
265             });
266         }
267     }
268 
269     /** Returns {@code true} if the access point was disabled due to the wrong password. */
isAccessPointDisabledByWrongPassword(AccessPoint accessPoint)270     public static boolean isAccessPointDisabledByWrongPassword(AccessPoint accessPoint) {
271         WifiConfiguration config = accessPoint.getConfig();
272         if (config == null) {
273             return false;
274         }
275         WifiConfiguration.NetworkSelectionStatus networkStatus =
276                 config.getNetworkSelectionStatus();
277         if (networkStatus == null
278                 || networkStatus.getNetworkSelectionStatus() == NETWORK_SELECTION_ENABLED) {
279             return false;
280         }
281         return networkStatus.getNetworkSelectionDisableReason()
282                 == WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WRONG_PASSWORD;
283     }
284 
isHexString(String password)285     private static boolean isHexString(String password) {
286         return HEX_PATTERN.matcher(password).matches();
287     }
288 }
289