1 /*
2  * Copyright (C) 2018 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.dpp;
18 
19 import static android.os.UserManager.DISALLOW_ADD_WIFI_CONFIG;
20 
21 import android.app.settings.SettingsEnums;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.net.Uri;
25 import android.net.wifi.WifiConfiguration;
26 import android.net.wifi.WifiInfo;
27 import android.net.wifi.WifiManager;
28 import android.os.Bundle;
29 import android.os.UserManager;
30 import android.provider.Settings;
31 import android.util.EventLog;
32 import android.util.Log;
33 
34 import androidx.annotation.VisibleForTesting;
35 import androidx.fragment.app.FragmentTransaction;
36 
37 import com.android.settings.R;
38 
39 import java.util.List;
40 
41 /**
42  * To provision "other" device with specified Wi-Fi network.
43  *
44  * Uses different intents to specify different provisioning ways.
45  *
46  * For intent action {@code ACTION_CONFIGURATOR_QR_CODE_SCANNER} and
47  * {@code android.settings.WIFI_DPP_CONFIGURATOR_QR_CODE_GENERATOR}, specify the Wi-Fi network to be
48  * provisioned in:
49  *
50  * {@code WifiDppUtils.EXTRA_WIFI_SECURITY}
51  * {@code WifiDppUtils.EXTRA_WIFI_SSID}
52  * {@code WifiDppUtils.EXTRA_WIFI_PRE_SHARED_KEY}
53  * {@code WifiDppUtils.EXTRA_WIFI_HIDDEN_SSID}
54  *
55  * For intent action {@link Settings#ACTION_PROCESS_WIFI_EASY_CONNECT_URI}, specify Wi-Fi
56  * Easy Connect bootstrapping information string in Intent's data URI.
57  */
58 public class WifiDppConfiguratorActivity extends WifiDppBaseActivity implements
59         WifiNetworkConfig.Retriever,
60         WifiDppQrCodeScannerFragment.OnScanWifiDppSuccessListener,
61         WifiDppAddDeviceFragment.OnClickChooseDifferentNetworkListener,
62         WifiNetworkListFragment.OnChooseNetworkListener {
63 
64     private static final String TAG = "WifiDppConfiguratorActivity";
65 
66     static final String ACTION_CONFIGURATOR_QR_CODE_SCANNER =
67             "android.settings.WIFI_DPP_CONFIGURATOR_QR_CODE_SCANNER";
68     static final String ACTION_CONFIGURATOR_QR_CODE_GENERATOR =
69             "android.settings.WIFI_DPP_CONFIGURATOR_QR_CODE_GENERATOR";
70 
71     // Key for Bundle usage
72     private static final String KEY_QR_CODE = "key_qr_code";
73     private static final String KEY_WIFI_SECURITY = "key_wifi_security";
74     private static final String KEY_WIFI_SSID = "key_wifi_ssid";
75     private static final String KEY_WIFI_PRESHARED_KEY = "key_wifi_preshared_key";
76     private static final String KEY_WIFI_HIDDEN_SSID = "key_wifi_hidden_ssid";
77     private static final String KEY_WIFI_NETWORK_ID = "key_wifi_network_id";
78     private static final String KEY_IS_HOTSPOT = "key_is_hotspot";
79 
80     /** The Wi-Fi network which will be configured */
81     private WifiNetworkConfig mWifiNetworkConfig;
82 
83     /** The Wi-Fi DPP QR code from intent ACTION_PROCESS_WIFI_EASY_CONNECT_URI */
84     private WifiQrCode mWifiDppQrCode;
85 
86     /**
87      * The remote device's band support obtained as an (optional) extra
88      * EXTRA_EASY_CONNECT_BAND_LIST from the intent ACTION_PROCESS_WIFI_EASY_CONNECT_URI.
89      *
90      * The band support is provided as IEEE 802.11 Global Operating Classes. There may be a single
91      * or multiple operating classes specified. The array may also be a null if the extra wasn't
92      * specified.
93      */
94     private int[] mWifiDppRemoteBandSupport;
95 
96     @Override
getMetricsCategory()97     public int getMetricsCategory() {
98         return SettingsEnums.SETTINGS_WIFI_DPP_CONFIGURATOR;
99     }
100 
101     @Override
onCreate(Bundle savedInstanceState)102     protected void onCreate(Bundle savedInstanceState) {
103         super.onCreate(savedInstanceState);
104         if (!isAddWifiConfigAllowed(getApplicationContext())) {
105             finish();
106             return;
107         }
108 
109         if (savedInstanceState != null) {
110             String qrCode = savedInstanceState.getString(KEY_QR_CODE);
111 
112             mWifiDppQrCode = WifiQrCode.getValidWifiDppQrCodeOrNull(qrCode);
113 
114             final String security = savedInstanceState.getString(KEY_WIFI_SECURITY);
115             final String ssid = savedInstanceState.getString(KEY_WIFI_SSID);
116             final String preSharedKey = savedInstanceState.getString(KEY_WIFI_PRESHARED_KEY);
117             final boolean hiddenSsid = savedInstanceState.getBoolean(KEY_WIFI_HIDDEN_SSID);
118             final int networkId = savedInstanceState.getInt(KEY_WIFI_NETWORK_ID);
119             final boolean isHotspot = savedInstanceState.getBoolean(KEY_IS_HOTSPOT);
120 
121             mWifiNetworkConfig = WifiNetworkConfig.getValidConfigOrNull(security, ssid,
122                     preSharedKey, hiddenSsid, networkId, isHotspot);
123         }
124     }
125 
126     @Override
handleIntent(Intent intent)127     protected void handleIntent(Intent intent) {
128         if (!isAddWifiConfigAllowed(getApplicationContext())) {
129             finish();
130             return;
131         }
132         if (isGuestUser(getApplicationContext())) {
133             Log.e(TAG, "Guest user is not allowed to configure Wi-Fi!");
134             EventLog.writeEvent(0x534e4554, "224772890", -1 /* UID */, "User is a guest");
135             finish();
136             return;
137         }
138 
139         String action = intent != null ? intent.getAction() : null;
140         if (action == null) {
141             finish();
142             return;
143         }
144 
145         boolean cancelActivity = false;
146         WifiNetworkConfig config;
147         switch (action) {
148             case ACTION_CONFIGURATOR_QR_CODE_SCANNER:
149                 config = WifiNetworkConfig.getValidConfigOrNull(intent);
150                 if (config == null) {
151                     cancelActivity = true;
152                 } else {
153                     mWifiNetworkConfig = config;
154                     showQrCodeScannerFragment();
155                 }
156                 break;
157             case ACTION_CONFIGURATOR_QR_CODE_GENERATOR:
158                 config = WifiNetworkConfig.getValidConfigOrNull(intent);
159                 if (config == null) {
160                     cancelActivity = true;
161                 } else {
162                     mWifiNetworkConfig = config;
163                     showQrCodeGeneratorFragment();
164                 }
165                 break;
166             case Settings.ACTION_PROCESS_WIFI_EASY_CONNECT_URI:
167                 WifiDppUtils.showLockScreen(this,
168                         () -> handleActionProcessWifiEasyConnectUriIntent(intent));
169                 break;
170             default:
171                 cancelActivity = true;
172                 Log.e(TAG, "Launch with an invalid action");
173         }
174 
175         if (cancelActivity) {
176             finish();
177         }
178     }
179 
handleActionProcessWifiEasyConnectUriIntent(Intent intent)180     private void handleActionProcessWifiEasyConnectUriIntent(Intent intent) {
181         final Uri uri = intent.getData();
182         final String uriString = (uri == null) ? null : uri.toString();
183         mWifiDppQrCode = WifiQrCode.getValidWifiDppQrCodeOrNull(uriString);
184         mWifiDppRemoteBandSupport = intent.getIntArrayExtra(
185                 Settings.EXTRA_EASY_CONNECT_BAND_LIST); // returns null if none
186         final boolean isDppSupported = WifiDppUtils.isWifiDppEnabled(this);
187         if (!isDppSupported) {
188             Log.e(TAG,
189                     "ACTION_PROCESS_WIFI_EASY_CONNECT_URI for a device that doesn't "
190                             + "support Wifi DPP - use WifiManager#isEasyConnectSupported");
191         }
192         if (mWifiDppQrCode == null) {
193             Log.e(TAG, "ACTION_PROCESS_WIFI_EASY_CONNECT_URI with null URI!");
194         }
195         if (mWifiDppQrCode == null || !isDppSupported) {
196             finish();
197         } else {
198             final WifiNetworkConfig connectedConfig = getConnectedWifiNetworkConfigOrNull();
199             if (connectedConfig == null || !connectedConfig.isSupportWifiDpp(this)) {
200                 showChooseSavedWifiNetworkFragment(/* addToBackStack */ false);
201             } else {
202                 mWifiNetworkConfig = connectedConfig;
203                 showAddDeviceFragment(/* addToBackStack */ false);
204             }
205         }
206     }
207 
208     @VisibleForTesting
showQrCodeScannerFragment()209     void showQrCodeScannerFragment() {
210         WifiDppQrCodeScannerFragment fragment =
211                 (WifiDppQrCodeScannerFragment) mFragmentManager.findFragmentByTag(
212                         WifiDppUtils.TAG_FRAGMENT_QR_CODE_SCANNER);
213 
214         if (fragment == null) {
215             fragment = new WifiDppQrCodeScannerFragment();
216         } else {
217             if (fragment.isVisible()) {
218                 return;
219             }
220 
221             // When the fragment in back stack but not on top of the stack, we can simply pop
222             // stack because current fragment transactions are arranged in an order
223             mFragmentManager.popBackStackImmediate();
224             return;
225         }
226         final FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
227 
228         fragmentTransaction.replace(R.id.fragment_container, fragment,
229                 WifiDppUtils.TAG_FRAGMENT_QR_CODE_SCANNER);
230         fragmentTransaction.commit();
231     }
232 
showQrCodeGeneratorFragment()233     private void showQrCodeGeneratorFragment() {
234         WifiDppQrCodeGeneratorFragment fragment =
235                 (WifiDppQrCodeGeneratorFragment) mFragmentManager.findFragmentByTag(
236                         WifiDppUtils.TAG_FRAGMENT_QR_CODE_GENERATOR);
237 
238         if (fragment == null) {
239             fragment = new WifiDppQrCodeGeneratorFragment();
240         } else {
241             if (fragment.isVisible()) {
242                 return;
243             }
244 
245             // When the fragment in back stack but not on top of the stack, we can simply pop
246             // stack because current fragment transactions are arranged in an order
247             mFragmentManager.popBackStackImmediate();
248             return;
249         }
250         final FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
251 
252         fragmentTransaction.replace(R.id.fragment_container, fragment,
253                 WifiDppUtils.TAG_FRAGMENT_QR_CODE_GENERATOR);
254         fragmentTransaction.commit();
255     }
256 
showChooseSavedWifiNetworkFragment(boolean addToBackStack)257     private void showChooseSavedWifiNetworkFragment(boolean addToBackStack) {
258         WifiDppChooseSavedWifiNetworkFragment fragment =
259                 (WifiDppChooseSavedWifiNetworkFragment) mFragmentManager.findFragmentByTag(
260                         WifiDppUtils.TAG_FRAGMENT_CHOOSE_SAVED_WIFI_NETWORK);
261 
262         if (fragment == null) {
263             fragment = new WifiDppChooseSavedWifiNetworkFragment();
264         } else {
265             if (fragment.isVisible()) {
266                 return;
267             }
268 
269             // When the fragment in back stack but not on top of the stack, we can simply pop
270             // stack because current fragment transactions are arranged in an order
271             mFragmentManager.popBackStackImmediate();
272             return;
273         }
274         final FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
275 
276         fragmentTransaction.replace(R.id.fragment_container, fragment,
277                 WifiDppUtils.TAG_FRAGMENT_CHOOSE_SAVED_WIFI_NETWORK);
278         if (addToBackStack) {
279             fragmentTransaction.addToBackStack(/* name */ null);
280         }
281         fragmentTransaction.commit();
282     }
283 
showAddDeviceFragment(boolean addToBackStack)284     private void showAddDeviceFragment(boolean addToBackStack) {
285         WifiDppAddDeviceFragment fragment =
286                 (WifiDppAddDeviceFragment) mFragmentManager.findFragmentByTag(
287                         WifiDppUtils.TAG_FRAGMENT_ADD_DEVICE);
288 
289         if (fragment == null) {
290             fragment = new WifiDppAddDeviceFragment();
291         } else {
292             if (fragment.isVisible()) {
293                 return;
294             }
295 
296             // When the fragment in back stack but not on top of the stack, we can simply pop
297             // stack because current fragment transactions are arranged in an order
298             mFragmentManager.popBackStackImmediate();
299             return;
300         }
301         final FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
302 
303         fragmentTransaction.replace(R.id.fragment_container, fragment,
304                 WifiDppUtils.TAG_FRAGMENT_ADD_DEVICE);
305         if (addToBackStack) {
306             fragmentTransaction.addToBackStack(/* name */ null);
307         }
308         fragmentTransaction.commit();
309     }
310 
311     @Override
getWifiNetworkConfig()312     public WifiNetworkConfig getWifiNetworkConfig() {
313         return mWifiNetworkConfig;
314     }
315 
getWifiDppQrCode()316     WifiQrCode getWifiDppQrCode() {
317         return mWifiDppQrCode;
318     }
319 
320     @VisibleForTesting
setWifiNetworkConfig(WifiNetworkConfig config)321     boolean setWifiNetworkConfig(WifiNetworkConfig config) {
322         if(!WifiNetworkConfig.isValidConfig(config)) {
323             return false;
324         } else {
325             mWifiNetworkConfig = new WifiNetworkConfig(config);
326             return true;
327         }
328     }
329 
330     @VisibleForTesting
setWifiDppQrCode(WifiQrCode wifiQrCode)331     boolean setWifiDppQrCode(WifiQrCode wifiQrCode) {
332         if (wifiQrCode == null) {
333             return false;
334         }
335 
336         if (!WifiQrCode.SCHEME_DPP.equals(wifiQrCode.getScheme())) {
337             return false;
338         }
339 
340         mWifiDppQrCode = new WifiQrCode(wifiQrCode.getQrCode());
341         return true;
342     }
343 
344     @Override
onScanWifiDppSuccess(WifiQrCode wifiQrCode)345     public void onScanWifiDppSuccess(WifiQrCode wifiQrCode) {
346         mWifiDppQrCode = wifiQrCode;
347 
348         showAddDeviceFragment(/* addToBackStack */ true);
349     }
350 
351     @Override
onClickChooseDifferentNetwork()352     public void onClickChooseDifferentNetwork() {
353         showChooseSavedWifiNetworkFragment(/* addToBackStack */ true);
354     }
355 
356     @Override
onSaveInstanceState(Bundle outState)357     public void onSaveInstanceState(Bundle outState) {
358         if (mWifiDppQrCode != null) {
359             outState.putString(KEY_QR_CODE, mWifiDppQrCode.getQrCode());
360         }
361 
362         if (mWifiNetworkConfig != null) {
363             outState.putString(KEY_WIFI_SECURITY, mWifiNetworkConfig.getSecurity());
364             outState.putString(KEY_WIFI_SSID, mWifiNetworkConfig.getSsid());
365             outState.putString(KEY_WIFI_PRESHARED_KEY, mWifiNetworkConfig.getPreSharedKey());
366             outState.putBoolean(KEY_WIFI_HIDDEN_SSID, mWifiNetworkConfig.getHiddenSsid());
367             outState.putInt(KEY_WIFI_NETWORK_ID, mWifiNetworkConfig.getNetworkId());
368             outState.putBoolean(KEY_IS_HOTSPOT, mWifiNetworkConfig.isHotspot());
369         }
370 
371         super.onSaveInstanceState(outState);
372     }
373 
374     @Override
onChooseNetwork(WifiNetworkConfig wifiNetworkConfig)375     public void onChooseNetwork(WifiNetworkConfig wifiNetworkConfig) {
376         mWifiNetworkConfig = new WifiNetworkConfig(wifiNetworkConfig);
377 
378         showAddDeviceFragment(/* addToBackStack */ true);
379     }
380 
getConnectedWifiNetworkConfigOrNull()381     private WifiNetworkConfig getConnectedWifiNetworkConfigOrNull() {
382         final WifiManager wifiManager = getSystemService(WifiManager.class);
383         if (!wifiManager.isWifiEnabled()) {
384             return null;
385         }
386 
387         final WifiInfo connectionInfo = wifiManager.getConnectionInfo();
388         if (connectionInfo == null) {
389             return null;
390         }
391 
392         final int connectionNetworkId = connectionInfo.getNetworkId();
393         final List<WifiConfiguration> configs = wifiManager.getConfiguredNetworks();
394         for (WifiConfiguration wifiConfiguration : configs) {
395             if (wifiConfiguration.networkId == connectionNetworkId) {
396                 return WifiNetworkConfig.getValidConfigOrNull(
397                     WifiDppUtils.getSecurityString(wifiConfiguration),
398                     wifiConfiguration.getPrintableSsid(),
399                     wifiConfiguration.preSharedKey,
400                     wifiConfiguration.hiddenSSID,
401                     wifiConfiguration.networkId,
402                     /* isHotspot */ false);
403             }
404         }
405 
406         return null;
407     }
408 
isGuestUser(Context context)409     private static boolean isGuestUser(Context context) {
410         if (context == null) return false;
411         final UserManager userManager = context.getSystemService(UserManager.class);
412         if (userManager == null) return false;
413         return userManager.isGuestUser();
414     }
415 
416     @VisibleForTesting
isAddWifiConfigAllowed(Context context)417     static boolean isAddWifiConfigAllowed(Context context) {
418         UserManager userManager = context.getSystemService(UserManager.class);
419         if (userManager != null && userManager.hasUserRestriction(DISALLOW_ADD_WIFI_CONFIG)) {
420             Log.e(TAG, "The user is not allowed to add Wi-Fi configuration.");
421             return false;
422         }
423         return true;
424     }
425 }
426