• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.ConnectivityManager;
28 import android.net.NetworkCapabilities;
29 import android.net.NetworkScoreManager;
30 import android.net.wifi.ScanResult;
31 import android.net.wifi.WifiConfiguration;
32 import android.net.wifi.WifiManager;
33 import android.os.Handler;
34 import android.os.SimpleClock;
35 import android.provider.Settings;
36 import android.text.TextUtils;
37 
38 import androidx.annotation.NonNull;
39 import androidx.annotation.StringRes;
40 import androidx.lifecycle.Lifecycle;
41 
42 import com.android.car.settings.R;
43 import com.android.car.settings.common.Logger;
44 import com.android.wifitrackerlib.NetworkDetailsTracker;
45 import com.android.wifitrackerlib.WifiEntry;
46 import com.android.wifitrackerlib.WifiPickerTracker;
47 
48 import java.time.Clock;
49 import java.time.ZoneOffset;
50 import java.util.regex.Pattern;
51 
52 /**
53  * A collections of util functions for WIFI.
54  */
55 public class WifiUtil {
56 
57     private static final Logger LOG = new Logger(WifiUtil.class);
58 
59     /** Value that is returned when we fail to connect wifi. */
60     public static final int INVALID_NET_ID = -1;
61     /** Max age of tracked WifiEntries. */
62     private static final long DEFAULT_MAX_SCAN_AGE_MILLIS = 15_000;
63     /** Interval between initiating WifiPickerTracker scans. */
64     private static final long DEFAULT_SCAN_INTERVAL_MILLIS = 10_000;
65     private static final Pattern HEX_PATTERN = Pattern.compile("^[0-9A-F]+$");
66 
67     /** Clock used for evaluating the age of WiFi scans */
68     private static final Clock ELAPSED_REALTIME_CLOCK = new SimpleClock(ZoneOffset.UTC) {
69         @Override
70         public long millis() {
71             return android.os.SystemClock.elapsedRealtime();
72         }
73     };
74 
75     @DrawableRes
getIconRes(int state)76     public static int getIconRes(int state) {
77         switch (state) {
78             case WifiManager.WIFI_STATE_ENABLING:
79             case WifiManager.WIFI_STATE_DISABLED:
80                 return R.drawable.ic_settings_wifi_disabled;
81             default:
82                 return R.drawable.ic_settings_wifi;
83         }
84     }
85 
isWifiOn(int state)86     public static boolean isWifiOn(int state) {
87         switch (state) {
88             case WifiManager.WIFI_STATE_ENABLING:
89             case WifiManager.WIFI_STATE_DISABLED:
90                 return false;
91             default:
92                 return true;
93         }
94     }
95 
96     /**
97      * @return 0 if no proper description can be found.
98      */
99     @StringRes
getStateDesc(int state)100     public static Integer getStateDesc(int state) {
101         switch (state) {
102             case WifiManager.WIFI_STATE_ENABLING:
103                 return R.string.wifi_starting;
104             case WifiManager.WIFI_STATE_DISABLING:
105                 return R.string.wifi_stopping;
106             case WifiManager.WIFI_STATE_DISABLED:
107                 return R.string.wifi_disabled;
108             default:
109                 return 0;
110         }
111     }
112 
113     /**
114      * Returns {@Code true} if wifi is available on this device.
115      */
isWifiAvailable(Context context)116     public static boolean isWifiAvailable(Context context) {
117         return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI);
118     }
119 
120     /**
121      * Gets a unique key for a {@link WifiEntry}.
122      */
getKey(WifiEntry wifiEntry)123     public static String getKey(WifiEntry wifiEntry) {
124         return String.valueOf(wifiEntry.hashCode());
125     }
126 
127     /**
128      * This method is a stripped and negated version of WifiConfigStore.canModifyNetwork.
129      *
130      * @param context Context of caller
131      * @param config  The WiFi config.
132      * @return {@code true} if Settings cannot modify the config due to lockDown.
133      */
isNetworkLockedDown(Context context, WifiConfiguration config)134     public static boolean isNetworkLockedDown(Context context, WifiConfiguration config) {
135         if (config == null) {
136             return false;
137         }
138 
139         final DevicePolicyManager dpm =
140                 (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
141         final PackageManager pm = context.getPackageManager();
142 
143         // Check if device has DPM capability. If it has and dpm is still null, then we
144         // treat this case with suspicion and bail out.
145         if (pm.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN) && dpm == null) {
146             return true;
147         }
148 
149         boolean isConfigEligibleForLockdown = false;
150         if (dpm != null) {
151             final ComponentName deviceOwner = dpm.getDeviceOwnerComponentOnAnyUser();
152             if (deviceOwner != null) {
153                 final int deviceOwnerUserId = dpm.getDeviceOwnerUserId();
154                 try {
155                     final int deviceOwnerUid = pm.getPackageUidAsUser(deviceOwner.getPackageName(),
156                             deviceOwnerUserId);
157                     isConfigEligibleForLockdown = deviceOwnerUid == config.creatorUid;
158                 } catch (PackageManager.NameNotFoundException e) {
159                     // don't care
160                 }
161             }
162         }
163         if (!isConfigEligibleForLockdown) {
164             return false;
165         }
166 
167         final ContentResolver resolver = context.getContentResolver();
168         final boolean isLockdownFeatureEnabled = Settings.Global.getInt(resolver,
169                 Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0) != 0;
170         return isLockdownFeatureEnabled;
171     }
172 
173     /**
174      * Returns {@code true} if the network security type doesn't require authentication.
175      */
isOpenNetwork(int security)176     public static boolean isOpenNetwork(int security) {
177         return security == WifiEntry.SECURITY_NONE || security == WifiEntry.SECURITY_OWE;
178     }
179 
180     /**
181      * Returns {@code true} if the provided NetworkCapabilities indicate a captive portal network.
182      */
canSignIntoNetwork(NetworkCapabilities capabilities)183     public static boolean canSignIntoNetwork(NetworkCapabilities capabilities) {
184         return (capabilities != null
185                 && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL));
186     }
187 
188     /**
189      * Attempts to connect to a specified Wi-Fi entry.
190      *
191      * @param listener for callbacks on success or failure of connection attempt (can be null)
192      */
connectToWifiEntry(Context context, String ssid, int security, String password, boolean hidden, @Nullable WifiManager.ActionListener listener)193     public static void connectToWifiEntry(Context context, String ssid, int security,
194             String password, boolean hidden, @Nullable WifiManager.ActionListener listener) {
195         WifiManager wifiManager = context.getSystemService(WifiManager.class);
196         WifiConfiguration wifiConfig = getWifiConfig(ssid, security, password, hidden);
197         wifiManager.connect(wifiConfig, listener);
198     }
199 
getWifiConfig(String ssid, int security, String password, boolean hidden)200     private static WifiConfiguration getWifiConfig(String ssid, int security,
201             String password, boolean hidden) {
202         WifiConfiguration wifiConfig = new WifiConfiguration();
203         wifiConfig.SSID = String.format("\"%s\"", ssid);
204         wifiConfig.hiddenSSID = hidden;
205 
206         return finishWifiConfig(wifiConfig, security, password);
207     }
208 
209     /** Similar to above, but uses WifiEntry to get additional relevant information. */
getWifiConfig(@onNull WifiEntry wifiEntry, String password)210     public static WifiConfiguration getWifiConfig(@NonNull WifiEntry wifiEntry,
211             String password) {
212         WifiConfiguration wifiConfig = new WifiConfiguration();
213         if (wifiEntry.getWifiConfiguration() == null) {
214             wifiConfig.SSID = "\"" + wifiEntry.getSsid() + "\"";
215         } else {
216             wifiConfig.networkId = wifiEntry.getWifiConfiguration().networkId;
217             wifiConfig.hiddenSSID = wifiEntry.getWifiConfiguration().hiddenSSID;
218         }
219 
220         return finishWifiConfig(wifiConfig, wifiEntry.getSecurity(), password);
221     }
222 
finishWifiConfig(WifiConfiguration wifiConfig, int security, String password)223     private static WifiConfiguration finishWifiConfig(WifiConfiguration wifiConfig, int security,
224             String password) {
225         switch (security) {
226             case WifiEntry.SECURITY_NONE:
227                 wifiConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OPEN);
228                 break;
229             case WifiEntry.SECURITY_WEP:
230                 wifiConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_WEP);
231                 if (!TextUtils.isEmpty(password)) {
232                     int length = password.length();
233                     // WEP-40, WEP-104, and 256-bit WEP (WEP-232?)
234                     if ((length == 10 || length == 26 || length == 58)
235                             && password.matches("[0-9A-Fa-f]*")) {
236                         wifiConfig.wepKeys[0] = password;
237                     } else {
238                         wifiConfig.wepKeys[0] = '"' + password + '"';
239                     }
240                 }
241                 break;
242             case WifiEntry.SECURITY_PSK:
243                 wifiConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
244                 if (!TextUtils.isEmpty(password)) {
245                     if (password.matches("[0-9A-Fa-f]{64}")) {
246                         wifiConfig.preSharedKey = password;
247                     } else {
248                         wifiConfig.preSharedKey = '"' + password + '"';
249                     }
250                 }
251                 break;
252             case WifiEntry.SECURITY_EAP:
253             case WifiEntry.SECURITY_EAP_SUITE_B:
254                 if (security == WifiEntry.SECURITY_EAP_SUITE_B) {
255                     // allowedSuiteBCiphers will be set according to certificate type
256                     wifiConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP_SUITE_B);
257                 } else {
258                     wifiConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP);
259                 }
260                 if (!TextUtils.isEmpty(password)) {
261                     wifiConfig.enterpriseConfig.setPassword(password);
262                 }
263                 break;
264             case WifiEntry.SECURITY_SAE:
265                 wifiConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE);
266                 if (!TextUtils.isEmpty(password)) {
267                     wifiConfig.preSharedKey = '"' + password + '"';
268                 }
269                 break;
270             case WifiEntry.SECURITY_OWE:
271                 wifiConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OWE);
272                 break;
273             default:
274                 throw new IllegalArgumentException("unknown security type " + security);
275         }
276         return wifiConfig;
277     }
278 
279     /** Returns {@code true} if the Wi-Fi entry is connected or connecting. */
isWifiEntryConnectedOrConnecting(WifiEntry wifiEntry)280     public static boolean isWifiEntryConnectedOrConnecting(WifiEntry wifiEntry) {
281         if (wifiEntry == null) {
282             return false;
283         }
284         return wifiEntry.getConnectedState() != WifiEntry.CONNECTED_STATE_DISCONNECTED;
285     }
286 
287     /** Returns {@code true} if the Wi-Fi entry was disabled due to the wrong password. */
isWifiEntryDisabledByWrongPassword(WifiEntry wifiEntry)288     public static boolean isWifiEntryDisabledByWrongPassword(WifiEntry wifiEntry) {
289         WifiConfiguration config = wifiEntry.getWifiConfiguration();
290         if (config == null) {
291             return false;
292         }
293         WifiConfiguration.NetworkSelectionStatus networkStatus =
294                 config.getNetworkSelectionStatus();
295         if (networkStatus == null
296                 || networkStatus.getNetworkSelectionStatus() == NETWORK_SELECTION_ENABLED) {
297             return false;
298         }
299         return networkStatus.getNetworkSelectionDisableReason()
300                 == WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WRONG_PASSWORD;
301     }
302 
isHexString(String password)303     private static boolean isHexString(String password) {
304         return HEX_PATTERN.matcher(password).matches();
305     }
306 
307     /**
308      * Gets the security value from a ScanResult.
309      *
310      * @return related security value based on {@link WifiEntry}
311      */
getWifiEntrySecurity(ScanResult result)312     public static int getWifiEntrySecurity(ScanResult result) {
313         if (result.capabilities.contains("WEP")) {
314             return WifiEntry.SECURITY_WEP;
315         } else if (result.capabilities.contains("SAE")) {
316             return WifiEntry.SECURITY_SAE;
317         } else if (result.capabilities.contains("PSK")) {
318             return WifiEntry.SECURITY_PSK;
319         } else if (result.capabilities.contains("EAP_SUITE_B_192")) {
320             return WifiEntry.SECURITY_EAP_SUITE_B;
321         } else if (result.capabilities.contains("EAP")) {
322             return WifiEntry.SECURITY_EAP;
323         } else if (result.capabilities.contains("OWE")) {
324             return WifiEntry.SECURITY_OWE;
325         }
326         return WifiEntry.SECURITY_NONE;
327     }
328 
329     /**
330      * Creates an instance of WifiPickerTracker using the default MAX_SCAN_AGE and
331      * SCAN_INTERVAL values.
332      */
createWifiPickerTracker( Lifecycle lifecycle, Context context, Handler mainHandler, Handler workerHandler, WifiPickerTracker.WifiPickerTrackerCallback listener)333     public static WifiPickerTracker createWifiPickerTracker(
334             Lifecycle lifecycle, Context context,
335             Handler mainHandler, Handler workerHandler,
336             WifiPickerTracker.WifiPickerTrackerCallback listener) {
337         return createWifiPickerTracker(lifecycle, context, mainHandler, workerHandler,
338                 DEFAULT_MAX_SCAN_AGE_MILLIS, DEFAULT_SCAN_INTERVAL_MILLIS, listener);
339     }
340 
341     /**
342      * Creates an instance of WifiPickerTracker.
343      */
createWifiPickerTracker( Lifecycle lifecycle, Context context, Handler mainHandler, Handler workerHandler, long maxScanAgeMillis, long scanIntervalMillis, WifiPickerTracker.WifiPickerTrackerCallback listener)344     public static WifiPickerTracker createWifiPickerTracker(
345             Lifecycle lifecycle, Context context,
346             Handler mainHandler, Handler workerHandler,
347             long maxScanAgeMillis, long scanIntervalMillis,
348             WifiPickerTracker.WifiPickerTrackerCallback listener) {
349         return new WifiPickerTracker(
350                 lifecycle, context,
351                 context.getSystemService(WifiManager.class),
352                 context.getSystemService(ConnectivityManager.class),
353                 context.getSystemService(NetworkScoreManager.class),
354                 mainHandler, workerHandler, ELAPSED_REALTIME_CLOCK,
355                 maxScanAgeMillis, scanIntervalMillis,
356                 listener);
357     }
358 
359     /**
360      * Creates an instance of NetworkDetailsTracker using the default MAX_SCAN_AGE and
361      * SCAN_INTERVAL values.
362      */
createNetworkDetailsTracker( Lifecycle lifecycle, Context context, Handler mainHandler, Handler workerHandler, String key)363     public static NetworkDetailsTracker createNetworkDetailsTracker(
364             Lifecycle lifecycle, Context context,
365             Handler mainHandler, Handler workerHandler,
366             String key) {
367         return createNetworkDetailsTracker(lifecycle, context, mainHandler, workerHandler,
368                 DEFAULT_MAX_SCAN_AGE_MILLIS, DEFAULT_SCAN_INTERVAL_MILLIS, key);
369     }
370 
371     /**
372      * Creates an instance of NetworkDetailsTracker.
373      */
createNetworkDetailsTracker( Lifecycle lifecycle, Context context, Handler mainHandler, Handler workerHandler, long maxScanAgeMillis, long scanIntervalMillis, String key)374     public static NetworkDetailsTracker createNetworkDetailsTracker(
375             Lifecycle lifecycle, Context context,
376             Handler mainHandler, Handler workerHandler,
377             long maxScanAgeMillis, long scanIntervalMillis,
378             String key) {
379         return NetworkDetailsTracker.createNetworkDetailsTracker(
380                 lifecycle, context,
381                 context.getSystemService(WifiManager.class),
382                 context.getSystemService(ConnectivityManager.class),
383                 context.getSystemService(NetworkScoreManager.class),
384                 mainHandler, workerHandler, ELAPSED_REALTIME_CLOCK,
385                 maxScanAgeMillis, scanIntervalMillis,
386                 key);
387     }
388 }
389