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