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