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