1 /*
2  * Copyright (C) 2015 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 static android.Manifest.permission.ACCESS_FINE_LOCATION;
20 import static android.os.UserManager.DISALLOW_ADD_WIFI_CONFIG;
21 import static android.os.UserManager.DISALLOW_CONFIG_WIFI;
22 
23 import android.app.KeyguardManager;
24 import android.content.DialogInterface;
25 import android.content.Intent;
26 import android.content.pm.PackageManager;
27 import android.net.NetworkInfo;
28 import android.net.wifi.WifiConfiguration;
29 import android.net.wifi.WifiManager;
30 import android.os.Bundle;
31 import android.os.Handler;
32 import android.os.HandlerThread;
33 import android.os.Looper;
34 import android.os.Process;
35 import android.os.SimpleClock;
36 import android.os.SystemClock;
37 import android.os.UserManager;
38 import android.text.TextUtils;
39 import android.util.EventLog;
40 import android.util.Log;
41 
42 import androidx.annotation.VisibleForTesting;
43 
44 import com.android.settings.R;
45 import com.android.settings.SetupWizardUtils;
46 import com.android.settings.Utils;
47 import com.android.settings.overlay.FeatureFactory;
48 import com.android.settings.wifi.dpp.WifiDppUtils;
49 import com.android.settingslib.core.lifecycle.ObservableActivity;
50 import com.android.settingslib.wifi.AccessPoint;
51 import com.android.wifitrackerlib.NetworkDetailsTracker;
52 import com.android.wifitrackerlib.WifiEntry;
53 
54 import com.google.android.setupcompat.util.WizardManagerHelper;
55 import com.google.android.setupdesign.util.ThemeHelper;
56 
57 import java.lang.ref.WeakReference;
58 import java.time.Clock;
59 import java.time.ZoneOffset;
60 
61 /**
62  * The activity shows a Wi-fi editor dialog.
63  *
64  * TODO(b/152571756): This activity supports both WifiTrackerLib and SettingsLib because this is an
65  *                    exported UI component, some other APPs (e.g., SetupWizard) still use
66  *                    SettingsLib. Remove the SettingsLib compatible part after these APPs use
67  *                    WifiTrackerLib.
68  */
69 public class WifiDialogActivity extends ObservableActivity implements WifiDialog.WifiDialogListener,
70         WifiDialog2.WifiDialog2Listener, DialogInterface.OnDismissListener {
71 
72     private static final String TAG = "WifiDialogActivity";
73 
74     // For the callers which support WifiTrackerLib.
75     public static final String KEY_CHOSEN_WIFIENTRY_KEY = "key_chosen_wifientry_key";
76 
77     // For the callers which support SettingsLib.
78     public static final String KEY_ACCESS_POINT_STATE = "access_point_state";
79 
80     /**
81      * Boolean extra indicating whether this activity should connect to an access point on the
82      * caller's behalf. If this is set to false, the caller should check
83      * {@link #KEY_WIFI_CONFIGURATION} in the result data and save that using
84      * {@link WifiManager#connect(WifiConfiguration, ActionListener)}. Default is true.
85      */
86     @VisibleForTesting
87     static final String KEY_CONNECT_FOR_CALLER = "connect_for_caller";
88 
89     public static final String KEY_WIFI_CONFIGURATION = "wifi_configuration";
90 
91     @VisibleForTesting
92     static final int RESULT_CONNECTED = RESULT_FIRST_USER;
93     private static final int RESULT_FORGET = RESULT_FIRST_USER + 1;
94 
95     @VisibleForTesting
96     static final int REQUEST_CODE_WIFI_DPP_ENROLLEE_QR_CODE_SCANNER = 0;
97 
98     // Max age of tracked WifiEntries.
99     private static final long MAX_SCAN_AGE_MILLIS = 15_000;
100     // Interval between initiating NetworkDetailsTracker scans.
101     private static final long SCAN_INTERVAL_MILLIS = 10_000;
102 
103     @VisibleForTesting
104     WifiDialog mDialog;
105     private AccessPoint mAccessPoint;
106 
107     @VisibleForTesting
108     WifiDialog2 mDialog2;
109 
110     // The received intent supports a key of WifiTrackerLib or SettingsLib.
111     private boolean mIsWifiTrackerLib;
112 
113     private Intent mIntent;
114     private NetworkDetailsTracker mNetworkDetailsTracker;
115     private HandlerThread mWorkerThread;
116     private WifiManager mWifiManager;
117     private LockScreenMonitor mLockScreenMonitor;
118 
119     @Override
onCreate(Bundle savedInstanceState)120     protected void onCreate(Bundle savedInstanceState) {
121         mIntent = getIntent();
122         if (WizardManagerHelper.isSetupWizardIntent(mIntent)) {
123             setTheme(SetupWizardUtils.getTransparentTheme(this, mIntent));
124         }
125 
126         super.onCreate(savedInstanceState);
127         if (!isConfigWifiAllowed() || !isAddWifiConfigAllowed()) {
128             finish();
129             return;
130         }
131 
132         mIsWifiTrackerLib = !TextUtils.isEmpty(mIntent.getStringExtra(KEY_CHOSEN_WIFIENTRY_KEY));
133 
134         if (mIsWifiTrackerLib) {
135             mWorkerThread = new HandlerThread(
136                     TAG + "{" + Integer.toHexString(System.identityHashCode(this)) + "}",
137                     Process.THREAD_PRIORITY_BACKGROUND);
138             mWorkerThread.start();
139             final Clock elapsedRealtimeClock = new SimpleClock(ZoneOffset.UTC) {
140                 @Override
141                 public long millis() {
142                     return SystemClock.elapsedRealtime();
143                 }
144             };
145             mNetworkDetailsTracker = FeatureFactory.getFeatureFactory()
146                     .getWifiTrackerLibProvider()
147                     .createNetworkDetailsTracker(
148                             getLifecycle(),
149                             this,
150                             new Handler(Looper.getMainLooper()),
151                             mWorkerThread.getThreadHandler(),
152                             elapsedRealtimeClock,
153                             MAX_SCAN_AGE_MILLIS,
154                             SCAN_INTERVAL_MILLIS,
155                             mIntent.getStringExtra(KEY_CHOSEN_WIFIENTRY_KEY));
156         } else {
157             final Bundle accessPointState = mIntent.getBundleExtra(KEY_ACCESS_POINT_STATE);
158             if (accessPointState != null) {
159                 mAccessPoint = new AccessPoint(this, accessPointState);
160             }
161         }
162     }
163 
164     @Override
onStart()165     protected void onStart() {
166         super.onStart();
167         if (mDialog2 != null || mDialog != null || !hasWifiManager()) {
168             return;
169         }
170 
171         if (WizardManagerHelper.isAnySetupWizard(getIntent())) {
172             createDialogWithSuwTheme();
173         } else {
174             if (mIsWifiTrackerLib) {
175                 mDialog2 = new WifiDialog2(this, this,
176                         mNetworkDetailsTracker.getWifiEntry(), WifiConfigUiBase2.MODE_CONNECT,
177                         0 /* style */, false /* hideSubmitButton */,
178                         false /* hideMeteredAndPrivacy */,
179                         Utils.SYSTEMUI_PACKAGE_NAME.equals(getLaunchedFromPackage()));
180             } else {
181                 mDialog = WifiDialog.createModal(
182                         this, this, mAccessPoint, WifiConfigUiBase.MODE_CONNECT);
183             }
184         }
185 
186         if (mIsWifiTrackerLib) {
187             if (mDialog2 != null) {
188                 mDialog2.show();
189                 mDialog2.setOnDismissListener(this);
190             }
191         } else {
192             if (mDialog != null) {
193                 mDialog.show();
194                 mDialog.setOnDismissListener(this);
195             }
196         }
197 
198         if (mDialog2 != null || mDialog != null) {
199             mLockScreenMonitor = new LockScreenMonitor(this);
200         }
201     }
202 
203     @VisibleForTesting
createDialogWithSuwTheme()204     protected void createDialogWithSuwTheme() {
205         final int targetStyle = ThemeHelper.isSetupWizardDayNightEnabled(this)
206                 ? R.style.SuwAlertDialogThemeCompat_DayNight :
207                 R.style.SuwAlertDialogThemeCompat_Light;
208         if (mIsWifiTrackerLib) {
209             mDialog2 = new WifiDialog2(this, this,
210                     mNetworkDetailsTracker.getWifiEntry(),
211                     WifiConfigUiBase2.MODE_CONNECT, targetStyle);
212         } else {
213             mDialog = WifiDialog.createModal(this, this, mAccessPoint,
214                     WifiConfigUiBase.MODE_CONNECT, targetStyle);
215         }
216     }
217 
218     @Override
finish()219     public void finish() {
220         overridePendingTransition(0, 0);
221 
222         super.finish();
223     }
224 
225     @Override
onDestroy()226     public void onDestroy() {
227         if (mIsWifiTrackerLib) {
228             if (mDialog2 != null && mDialog2.isShowing()) {
229                 mDialog2 = null;
230             }
231             mWorkerThread.quit();
232         } else {
233             if (mDialog != null && mDialog.isShowing()) {
234                 mDialog = null;
235             }
236         }
237 
238         if (mLockScreenMonitor != null) {
239             mLockScreenMonitor.release();
240             mLockScreenMonitor = null;
241         }
242         super.onDestroy();
243     }
244 
245     @Override
onForget(WifiDialog2 dialog)246     public void onForget(WifiDialog2 dialog) {
247         final WifiEntry wifiEntry = dialog.getController().getWifiEntry();
248         if (wifiEntry != null && wifiEntry.canForget()) {
249             wifiEntry.forget(null /* callback */);
250         }
251 
252         setResult(RESULT_FORGET);
253         finish();
254     }
255 
256     @Override
onForget(WifiDialog dialog)257     public void onForget(WifiDialog dialog) {
258         if (!hasWifiManager()) return;
259         final AccessPoint accessPoint = dialog.getController().getAccessPoint();
260         if (accessPoint != null) {
261             if (!accessPoint.isSaved()) {
262                 if (accessPoint.getNetworkInfo() != null &&
263                         accessPoint.getNetworkInfo().getState() != NetworkInfo.State.DISCONNECTED) {
264                     // Network is active but has no network ID - must be ephemeral.
265                     mWifiManager.disableEphemeralNetwork(
266                             AccessPoint.convertToQuotedString(accessPoint.getSsidStr()));
267                 } else {
268                     // Should not happen, but a monkey seems to trigger it
269                     Log.e(TAG, "Failed to forget invalid network " + accessPoint.getConfig());
270                 }
271             } else {
272                 mWifiManager.forget(accessPoint.getConfig().networkId, null /* listener */);
273             }
274         }
275 
276         Intent resultData = new Intent();
277         if (accessPoint != null) {
278             Bundle accessPointState = new Bundle();
279             accessPoint.saveWifiState(accessPointState);
280             resultData.putExtra(KEY_ACCESS_POINT_STATE, accessPointState);
281         }
282         setResult(RESULT_FORGET);
283         finish();
284     }
285 
286     @Override
onSubmit(WifiDialog2 dialog)287     public void onSubmit(WifiDialog2 dialog) {
288         if (!hasWifiManager()) return;
289         final WifiEntry wifiEntry = dialog.getController().getWifiEntry();
290         final WifiConfiguration config = dialog.getController().getConfig();
291 
292         if (getIntent().getBooleanExtra(KEY_CONNECT_FOR_CALLER, true)) {
293             if (config == null && wifiEntry != null && wifiEntry.canConnect()) {
294                 wifiEntry.connect(null /* callback */);
295             } else {
296                 mWifiManager.connect(config, null /* listener */);
297             }
298         }
299 
300         Intent resultData = hasPermissionForResult() ? createResultData(config, null) : null;
301         setResult(RESULT_CONNECTED, resultData);
302         finish();
303     }
304 
305     @Override
onSubmit(WifiDialog dialog)306     public void onSubmit(WifiDialog dialog) {
307         if (!hasWifiManager()) return;
308         final WifiConfiguration config = dialog.getController().getConfig();
309         final AccessPoint accessPoint = dialog.getController().getAccessPoint();
310 
311         if (getIntent().getBooleanExtra(KEY_CONNECT_FOR_CALLER, true)) {
312             if (config == null) {
313                 if (accessPoint != null && accessPoint.isSaved()) {
314                     mWifiManager.connect(accessPoint.getConfig(), null /* listener */);
315                 }
316             } else {
317                 mWifiManager.save(config, null /* listener */);
318                 if (accessPoint != null) {
319                     // accessPoint is null for "Add network"
320                     NetworkInfo networkInfo = accessPoint.getNetworkInfo();
321                     if (networkInfo == null || !networkInfo.isConnected()) {
322                         mWifiManager.connect(config, null /* listener */);
323                     }
324                 }
325             }
326         }
327 
328         Intent resultData = hasPermissionForResult() ? createResultData(config, accessPoint) : null;
329         setResult(RESULT_CONNECTED, resultData);
330         finish();
331     }
332 
createResultData(WifiConfiguration config, AccessPoint accessPoint)333     protected Intent createResultData(WifiConfiguration config, AccessPoint accessPoint) {
334         Intent result = new Intent();
335         if (accessPoint != null) {
336             Bundle accessPointState = new Bundle();
337             accessPoint.saveWifiState(accessPointState);
338             result.putExtra(KEY_ACCESS_POINT_STATE, accessPointState);
339         }
340         if (config != null) {
341             result.putExtra(KEY_WIFI_CONFIGURATION, config);
342         }
343         return result;
344     }
345 
346     @Override
onDismiss(DialogInterface dialogInterface)347     public void onDismiss(DialogInterface dialogInterface) {
348         mDialog2 = null;
349         mDialog = null;
350         finish();
351     }
352 
353     @Override
onScan(WifiDialog2 dialog, String ssid)354     public void onScan(WifiDialog2 dialog, String ssid) {
355         Intent intent = WifiDppUtils.getEnrolleeQrCodeScannerIntent(dialog.getContext(), ssid);
356         WizardManagerHelper.copyWizardManagerExtras(mIntent, intent);
357 
358         // Launch QR code scanner to join a network.
359         startActivityForResult(intent, REQUEST_CODE_WIFI_DPP_ENROLLEE_QR_CODE_SCANNER);
360     }
361 
362     @Override
onScan(WifiDialog dialog, String ssid)363     public void onScan(WifiDialog dialog, String ssid) {
364         Intent intent = WifiDppUtils.getEnrolleeQrCodeScannerIntent(dialog.getContext(), ssid);
365         WizardManagerHelper.copyWizardManagerExtras(mIntent, intent);
366 
367         // Launch QR code scanner to join a network.
368         startActivityForResult(intent, REQUEST_CODE_WIFI_DPP_ENROLLEE_QR_CODE_SCANNER);
369     }
370 
371     @Override
onActivityResult(int requestCode, int resultCode, Intent data)372     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
373         super.onActivityResult(requestCode, resultCode, data);
374 
375         if (requestCode == REQUEST_CODE_WIFI_DPP_ENROLLEE_QR_CODE_SCANNER) {
376             if (resultCode != RESULT_OK) {
377                 return;
378             }
379             if (hasPermissionForResult()) {
380                 setResult(RESULT_CONNECTED, data);
381             } else {
382                 setResult(RESULT_CONNECTED);
383             }
384             finish();
385         }
386     }
387 
388     @VisibleForTesting
isConfigWifiAllowed()389     boolean isConfigWifiAllowed() {
390         UserManager userManager = getSystemService(UserManager.class);
391         if (userManager == null) return true;
392         final boolean isConfigWifiAllowed = !userManager.hasUserRestriction(DISALLOW_CONFIG_WIFI);
393         if (!isConfigWifiAllowed) {
394             Log.e(TAG, "The user is not allowed to configure Wi-Fi.");
395             EventLog.writeEvent(0x534e4554, "226133034", getApplicationContext().getUserId(),
396                     "The user is not allowed to configure Wi-Fi.");
397         }
398         return isConfigWifiAllowed;
399     }
400 
401     @VisibleForTesting
isAddWifiConfigAllowed()402     boolean isAddWifiConfigAllowed() {
403         UserManager userManager = getSystemService(UserManager.class);
404         if (userManager != null && userManager.hasUserRestriction(DISALLOW_ADD_WIFI_CONFIG)) {
405             Log.e(TAG, "The user is not allowed to add Wi-Fi configuration.");
406             return false;
407         }
408         return true;
409     }
410 
hasWifiManager()411     private boolean hasWifiManager() {
412         if (mWifiManager != null) return true;
413         mWifiManager = getSystemService(WifiManager.class);
414         return (mWifiManager != null);
415     }
416 
hasPermissionForResult()417     protected boolean hasPermissionForResult() {
418         final String callingPackage = getCallingPackage();
419         if (callingPackage == null) {
420             Log.d(TAG, "Failed to get the calling package, don't return the result.");
421             EventLog.writeEvent(0x534e4554, "185126813", -1 /* UID */, "no calling package");
422             return false;
423         }
424 
425         if (getPackageManager().checkPermission(ACCESS_FINE_LOCATION, callingPackage)
426                 == PackageManager.PERMISSION_GRANTED) {
427             Log.d(TAG, "The calling package has ACCESS_FINE_LOCATION permission for result.");
428             return true;
429         }
430 
431         Log.d(TAG, "The calling package does not have the necessary permissions for result.");
432         try {
433             EventLog.writeEvent(0x534e4554, "185126813",
434                     getPackageManager().getPackageUid(callingPackage, 0 /* flags */),
435                     "no permission");
436         } catch (PackageManager.NameNotFoundException e) {
437             EventLog.writeEvent(0x534e4554, "185126813", -1 /* UID */, "no permission");
438             Log.w(TAG, "Cannot find the UID, calling package: " + callingPackage, e);
439         }
440         return false;
441     }
442 
dismissDialog()443     void dismissDialog() {
444         if (mDialog != null) {
445             mDialog.dismiss();
446             mDialog = null;
447         }
448         if (mDialog2 != null) {
449             mDialog2.dismiss();
450             mDialog2 = null;
451         }
452     }
453 
454     @VisibleForTesting
455     static final class LockScreenMonitor implements KeyguardManager.KeyguardLockedStateListener {
456         private final WeakReference<WifiDialogActivity> mWifiDialogActivity;
457         private KeyguardManager mKeyguardManager;
458 
LockScreenMonitor(WifiDialogActivity activity)459         LockScreenMonitor(WifiDialogActivity activity) {
460             mWifiDialogActivity = new WeakReference<>(activity);
461             mKeyguardManager = activity.getSystemService(KeyguardManager.class);
462             mKeyguardManager.addKeyguardLockedStateListener(activity.getMainExecutor(), this);
463         }
464 
release()465         void release() {
466             if (mKeyguardManager == null) return;
467             mKeyguardManager.removeKeyguardLockedStateListener(this);
468             mKeyguardManager = null;
469         }
470 
471         @Override
onKeyguardLockedStateChanged(boolean isKeyguardLocked)472         public void onKeyguardLockedStateChanged(boolean isKeyguardLocked) {
473             if (!isKeyguardLocked) return;
474             WifiDialogActivity activity = mWifiDialogActivity.get();
475             if (activity == null) return;
476             activity.dismissDialog();
477 
478             Log.e(TAG, "Dismiss Wi-Fi dialog to prevent leaking user data on lock screen!");
479             EventLog.writeEvent(0x534e4554, "231583603", -1 /* UID */,
480                     "Leak Wi-Fi dialog on lock screen");
481         }
482     }
483 }
484