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.net.wifi.WifiInfo.sanitizeSsid; 20 21 import android.app.Activity; 22 import android.app.settings.SettingsEnums; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.graphics.Matrix; 26 import android.graphics.Rect; 27 import android.graphics.SurfaceTexture; 28 import android.net.wifi.EasyConnectStatusCallback; 29 import android.net.wifi.UriParserResults; 30 import android.net.wifi.WifiConfiguration; 31 import android.net.wifi.WifiManager; 32 import android.os.Bundle; 33 import android.os.Handler; 34 import android.os.HandlerThread; 35 import android.os.Looper; 36 import android.os.Message; 37 import android.os.Process; 38 import android.os.SimpleClock; 39 import android.os.SystemClock; 40 import android.text.TextUtils; 41 import android.util.EventLog; 42 import android.util.Log; 43 import android.util.Size; 44 import android.view.LayoutInflater; 45 import android.view.Menu; 46 import android.view.MenuInflater; 47 import android.view.TextureView; 48 import android.view.TextureView.SurfaceTextureListener; 49 import android.view.View; 50 import android.view.ViewGroup; 51 import android.view.accessibility.AccessibilityEvent; 52 import android.widget.TextView; 53 54 import androidx.annotation.StringRes; 55 import androidx.annotation.UiThread; 56 import androidx.annotation.VisibleForTesting; 57 import androidx.lifecycle.ViewModelProvider; 58 59 import com.android.settings.R; 60 import com.android.settings.overlay.FeatureFactory; 61 import com.android.settingslib.qrcode.QrCamera; 62 import com.android.settingslib.qrcode.QrDecorateView; 63 import com.android.settingslib.wifi.WifiPermissionChecker; 64 import com.android.wifitrackerlib.WifiEntry; 65 import com.android.wifitrackerlib.WifiPickerTracker; 66 67 import java.time.Clock; 68 import java.time.ZoneOffset; 69 import java.util.List; 70 71 public class WifiDppQrCodeScannerFragment extends WifiDppQrCodeBaseFragment implements 72 SurfaceTextureListener, 73 QrCamera.ScannerCallback, 74 WifiManager.ActionListener { 75 private static final String TAG = "WifiDppQrCodeScanner"; 76 77 /** Message sent to hide error message */ 78 private static final int MESSAGE_HIDE_ERROR_MESSAGE = 1; 79 80 /** Message sent to show error message */ 81 private static final int MESSAGE_SHOW_ERROR_MESSAGE = 2; 82 83 /** Message sent to manipulate Wi-Fi DPP QR code */ 84 private static final int MESSAGE_SCAN_WIFI_DPP_SUCCESS = 3; 85 86 /** Message sent to manipulate ZXing Wi-Fi QR code */ 87 private static final int MESSAGE_SCAN_ZXING_WIFI_FORMAT_SUCCESS = 4; 88 89 private static final long SHOW_ERROR_MESSAGE_INTERVAL = 10000; 90 private static final long SHOW_SUCCESS_SQUARE_INTERVAL = 1000; 91 92 // Key for Bundle usage 93 private static final String KEY_IS_CONFIGURATOR_MODE = "key_is_configurator_mode"; 94 private static final String KEY_LATEST_ERROR_CODE = "key_latest_error_code"; 95 public static final String KEY_WIFI_CONFIGURATION = "key_wifi_configuration"; 96 97 private static final int ARG_RESTART_CAMERA = 1; 98 99 // Max age of tracked WifiEntries. 100 private static final long MAX_SCAN_AGE_MILLIS = 15_000; 101 // Interval between initiating WifiPickerTracker scans. 102 private static final long SCAN_INTERVAL_MILLIS = 10_000; 103 104 private QrCamera mCamera; 105 private TextureView mTextureView; 106 private QrDecorateView mDecorateView; 107 private TextView mErrorMessage; 108 109 /** true if the fragment working for configurator, false enrollee*/ 110 private boolean mIsConfiguratorMode; 111 112 /** The SSID of the Wi-Fi network which the user specify to enroll */ 113 private String mSsid; 114 115 /** QR code data scanned by camera */ 116 private WifiQrCode mWifiQrCode; 117 118 /** The WifiConfiguration connecting for enrollee usage */ 119 private WifiConfiguration mEnrolleeWifiConfiguration; 120 121 private int mLatestStatusCode = WifiDppUtils.EASY_CONNECT_EVENT_FAILURE_NONE; 122 123 private WifiPickerTracker mWifiPickerTracker; 124 private HandlerThread mWorkerThread; 125 private WifiPermissionChecker mWifiPermissionChecker; 126 127 private final Handler mHandler = new Handler() { 128 @Override 129 public void handleMessage(Message msg) { 130 switch (msg.what) { 131 case MESSAGE_HIDE_ERROR_MESSAGE: 132 mErrorMessage.setVisibility(View.INVISIBLE); 133 break; 134 135 case MESSAGE_SHOW_ERROR_MESSAGE: 136 final String errorMessage = (String) msg.obj; 137 138 mErrorMessage.setVisibility(View.VISIBLE); 139 mErrorMessage.setText(errorMessage); 140 mErrorMessage.sendAccessibilityEvent( 141 AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); 142 143 // Cancel any pending messages to hide error view and requeue the message so 144 // user has time to see error 145 removeMessages(MESSAGE_HIDE_ERROR_MESSAGE); 146 sendEmptyMessageDelayed(MESSAGE_HIDE_ERROR_MESSAGE, 147 SHOW_ERROR_MESSAGE_INTERVAL); 148 149 if (msg.arg1 == ARG_RESTART_CAMERA) { 150 setProgressBarShown(false); 151 mDecorateView.setFocused(false); 152 restartCamera(); 153 } 154 break; 155 156 case MESSAGE_SCAN_WIFI_DPP_SUCCESS: 157 if (mScanWifiDppSuccessListener == null) { 158 // mScanWifiDppSuccessListener may be null after onDetach(), do nothing here 159 return; 160 } 161 mScanWifiDppSuccessListener.onScanWifiDppSuccess((WifiQrCode)msg.obj); 162 163 if (!mIsConfiguratorMode) { 164 setProgressBarShown(true); 165 startWifiDppEnrolleeInitiator((WifiQrCode)msg.obj); 166 updateEnrolleeSummary(); 167 mSummary.sendAccessibilityEvent( 168 AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); 169 } 170 171 notifyUserForQrCodeRecognition(); 172 break; 173 174 case MESSAGE_SCAN_ZXING_WIFI_FORMAT_SUCCESS: 175 final Context context = getContext(); 176 if (context == null) { 177 // Context may be null if the message is received after the Activity has 178 // been destroyed 179 Log.d(TAG, "Scan success but context is null"); 180 return; 181 } 182 183 // We may get 2 WifiConfiguration if the QR code has no password in it, 184 // one for open network and one for enhanced open network. 185 final WifiManager wifiManager = 186 context.getSystemService(WifiManager.class); 187 final WifiConfiguration qrCodeWifiConfiguration = (WifiConfiguration) msg.obj; 188 189 // Adds all Wi-Fi networks in QR code to the set of configured networks and 190 // connects to it if it's reachable. 191 boolean hasHiddenOrReachableWifiNetwork = false; 192 final int id = wifiManager.addNetwork(qrCodeWifiConfiguration); 193 if (id == -1) { 194 return; 195 } 196 197 if (!canConnectWifi(qrCodeWifiConfiguration.SSID)) { 198 return; 199 } 200 201 wifiManager.enableNetwork(id, /* attemptConnect */ false); 202 // WifiTracker only contains a hidden SSID Wi-Fi network if it's saved. 203 // We can't check if a hidden SSID Wi-Fi network is reachable in advance. 204 if (qrCodeWifiConfiguration.hiddenSSID 205 || isReachableWifiNetwork(qrCodeWifiConfiguration)) { 206 hasHiddenOrReachableWifiNetwork = true; 207 mEnrolleeWifiConfiguration = qrCodeWifiConfiguration; 208 wifiManager.connect(id, 209 /* listener */ WifiDppQrCodeScannerFragment.this); 210 } 211 212 if (!hasHiddenOrReachableWifiNetwork) { 213 showErrorMessageAndRestartCamera( 214 R.string.wifi_dpp_check_connection_try_again); 215 return; 216 } 217 218 mMetricsFeatureProvider.action( 219 mMetricsFeatureProvider.getAttribution(getActivity()), 220 SettingsEnums.ACTION_SETTINGS_ENROLL_WIFI_QR_CODE, 221 SettingsEnums.SETTINGS_WIFI_DPP_ENROLLEE, 222 /* key */ null, 223 /* value */ Integer.MIN_VALUE); 224 225 notifyUserForQrCodeRecognition(); 226 break; 227 228 default: 229 } 230 } 231 }; 232 233 @UiThread notifyUserForQrCodeRecognition()234 private void notifyUserForQrCodeRecognition() { 235 if (mCamera != null) { 236 mCamera.stop(); 237 } 238 239 mDecorateView.setFocused(true); 240 mErrorMessage.setVisibility(View.INVISIBLE); 241 242 WifiDppUtils.triggerVibrationForQrCodeRecognition(getContext()); 243 } 244 isReachableWifiNetwork(WifiConfiguration wifiConfiguration)245 private boolean isReachableWifiNetwork(WifiConfiguration wifiConfiguration) { 246 final List<WifiEntry> wifiEntries = mWifiPickerTracker.getWifiEntries(); 247 final WifiEntry connectedWifiEntry = mWifiPickerTracker.getConnectedWifiEntry(); 248 if (connectedWifiEntry != null) { 249 // Add connected WifiEntry to prevent fail toast to users when it's connected. 250 wifiEntries.add(connectedWifiEntry); 251 } 252 253 for (WifiEntry wifiEntry : wifiEntries) { 254 if (!TextUtils.equals(wifiEntry.getSsid(), sanitizeSsid(wifiConfiguration.SSID))) { 255 continue; 256 } 257 final int security = 258 WifiDppUtils.getSecurityTypeFromWifiConfiguration(wifiConfiguration); 259 if (security == wifiEntry.getSecurity()) { 260 return true; 261 } 262 263 // Default security type of PSK/SAE transition mode WifiEntry is SECURITY_PSK and 264 // there is no way to know if a WifiEntry is of transition mode. Give it a chance. 265 if (security == WifiEntry.SECURITY_SAE 266 && wifiEntry.getSecurity() == WifiEntry.SECURITY_PSK) { 267 return true; 268 } 269 } 270 return false; 271 } 272 273 @VisibleForTesting canConnectWifi(String ssid)274 boolean canConnectWifi(String ssid) { 275 final List<WifiEntry> wifiEntries = mWifiPickerTracker.getWifiEntries(); 276 for (WifiEntry wifiEntry : wifiEntries) { 277 if (!TextUtils.equals(wifiEntry.getSsid(), sanitizeSsid(ssid))) continue; 278 279 if (!wifiEntry.canConnect()) { 280 Log.w(TAG, "Wi-Fi is not allowed to connect by your organization. SSID:" + ssid); 281 showErrorMessageAndRestartCamera(R.string.not_allowed_by_ent); 282 return false; 283 } 284 } 285 return true; 286 } 287 288 @Override onCreate(Bundle savedInstanceState)289 public void onCreate(Bundle savedInstanceState) { 290 super.onCreate(savedInstanceState); 291 292 if (savedInstanceState != null) { 293 mIsConfiguratorMode = savedInstanceState.getBoolean(KEY_IS_CONFIGURATOR_MODE); 294 mLatestStatusCode = savedInstanceState.getInt(KEY_LATEST_ERROR_CODE); 295 mEnrolleeWifiConfiguration = savedInstanceState.getParcelable(KEY_WIFI_CONFIGURATION); 296 } 297 298 final WifiDppInitiatorViewModel model = 299 new ViewModelProvider(this).get(WifiDppInitiatorViewModel.class); 300 301 model.getEnrolleeSuccessNetworkId().observe(this, networkId -> { 302 // After configuration change, observe callback will be triggered, 303 // do nothing for this case if a handshake does not end 304 if (model.isWifiDppHandshaking()) { 305 return; 306 } 307 308 new EasyConnectEnrolleeStatusCallback().onEnrolleeSuccess(networkId.intValue()); 309 }); 310 311 model.getStatusCode().observe(this, statusCode -> { 312 // After configuration change, observe callback will be triggered, 313 // do nothing for this case if a handshake does not end 314 if (model.isWifiDppHandshaking()) { 315 return; 316 } 317 318 int code = statusCode.intValue(); 319 Log.d(TAG, "Easy connect enrollee callback onFailure " + code); 320 new EasyConnectEnrolleeStatusCallback().onFailure(code); 321 }); 322 } 323 324 @Override onPause()325 public void onPause() { 326 if (mCamera != null) { 327 mCamera.stop(); 328 } 329 330 super.onPause(); 331 } 332 333 @Override onResume()334 public void onResume() { 335 super.onResume(); 336 337 if (!isWifiDppHandshaking()) { 338 restartCamera(); 339 } 340 } 341 342 @Override getMetricsCategory()343 public int getMetricsCategory() { 344 if (mIsConfiguratorMode) { 345 return SettingsEnums.SETTINGS_WIFI_DPP_CONFIGURATOR; 346 } else { 347 return SettingsEnums.SETTINGS_WIFI_DPP_ENROLLEE; 348 } 349 } 350 351 // Container Activity must implement this interface 352 public interface OnScanWifiDppSuccessListener { onScanWifiDppSuccess(WifiQrCode wifiQrCode)353 void onScanWifiDppSuccess(WifiQrCode wifiQrCode); 354 } 355 private OnScanWifiDppSuccessListener mScanWifiDppSuccessListener; 356 357 /** 358 * Configurator container activity of the fragment should create instance with this constructor. 359 */ WifiDppQrCodeScannerFragment()360 public WifiDppQrCodeScannerFragment() { 361 super(); 362 363 mIsConfiguratorMode = true; 364 } 365 WifiDppQrCodeScannerFragment(WifiPickerTracker wifiPickerTracker, WifiPermissionChecker wifiPermissionChecker)366 public WifiDppQrCodeScannerFragment(WifiPickerTracker wifiPickerTracker, 367 WifiPermissionChecker wifiPermissionChecker) { 368 super(); 369 370 mIsConfiguratorMode = true; 371 mWifiPickerTracker = wifiPickerTracker; 372 mWifiPermissionChecker = wifiPermissionChecker; 373 } 374 375 /** 376 * Enrollee container activity of the fragment should create instance with this constructor and 377 * specify the SSID string of the WI-Fi network to be provisioned. 378 */ WifiDppQrCodeScannerFragment(String ssid)379 WifiDppQrCodeScannerFragment(String ssid) { 380 super(); 381 382 mIsConfiguratorMode = false; 383 mSsid = ssid; 384 } 385 386 @Override onActivityCreated(Bundle savedInstanceState)387 public void onActivityCreated(Bundle savedInstanceState) { 388 super.onActivityCreated(savedInstanceState); 389 390 mWorkerThread = new HandlerThread( 391 TAG + "{" + Integer.toHexString(System.identityHashCode(this)) + "}", 392 Process.THREAD_PRIORITY_BACKGROUND); 393 mWorkerThread.start(); 394 final Clock elapsedRealtimeClock = new SimpleClock(ZoneOffset.UTC) { 395 @Override 396 public long millis() { 397 return SystemClock.elapsedRealtime(); 398 } 399 }; 400 final Context context = getContext(); 401 mWifiPickerTracker = FeatureFactory.getFeatureFactory() 402 .getWifiTrackerLibProvider() 403 .createWifiPickerTracker(getSettingsLifecycle(), context, 404 new Handler(Looper.getMainLooper()), 405 mWorkerThread.getThreadHandler(), 406 elapsedRealtimeClock, 407 MAX_SCAN_AGE_MILLIS, 408 SCAN_INTERVAL_MILLIS, 409 null /* listener */); 410 411 // setTitle for TalkBack 412 if (mIsConfiguratorMode) { 413 getActivity().setTitle(R.string.wifi_dpp_add_device_to_network); 414 } else { 415 getActivity().setTitle(R.string.wifi_dpp_scan_qr_code); 416 } 417 } 418 419 @Override onAttach(Context context)420 public void onAttach(Context context) { 421 super.onAttach(context); 422 423 mScanWifiDppSuccessListener = (OnScanWifiDppSuccessListener) context; 424 } 425 426 @Override onDetach()427 public void onDetach() { 428 mScanWifiDppSuccessListener = null; 429 430 super.onDetach(); 431 } 432 433 @Override onDestroyView()434 public void onDestroyView() { 435 mWorkerThread.quit(); 436 437 super.onDestroyView(); 438 } 439 440 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)441 public final View onCreateView(LayoutInflater inflater, ViewGroup container, 442 Bundle savedInstanceState) { 443 return inflater.inflate(R.layout.wifi_dpp_qrcode_scanner_fragment, container, 444 /* attachToRoot */ false); 445 } 446 447 @Override onViewCreated(View view, Bundle savedInstanceState)448 public void onViewCreated(View view, Bundle savedInstanceState) { 449 super.onViewCreated(view, savedInstanceState); 450 mSummary = view.findViewById(R.id.sud_layout_subtitle); 451 452 mTextureView = view.findViewById(R.id.preview_view); 453 mTextureView.setSurfaceTextureListener(this); 454 455 mDecorateView = view.findViewById(R.id.decorate_view); 456 457 setProgressBarShown(isWifiDppHandshaking()); 458 459 if (mIsConfiguratorMode) { 460 setHeaderTitle(R.string.wifi_dpp_add_device_to_network); 461 462 WifiNetworkConfig wifiNetworkConfig = ((WifiNetworkConfig.Retriever) getActivity()) 463 .getWifiNetworkConfig(); 464 if (!WifiNetworkConfig.isValidConfig(wifiNetworkConfig)) { 465 throw new IllegalStateException("Invalid Wi-Fi network for configuring"); 466 } 467 mSummary.setText(getString(R.string.wifi_dpp_center_qr_code, 468 wifiNetworkConfig.getSsid())); 469 } else { 470 setHeaderTitle(R.string.wifi_dpp_scan_qr_code); 471 472 updateEnrolleeSummary(); 473 } 474 475 mErrorMessage = view.findViewById(R.id.error_message); 476 } 477 478 @Override onCreateOptionsMenu(Menu menu, MenuInflater inflater)479 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 480 menu.removeItem(Menu.FIRST); 481 482 super.onCreateOptionsMenu(menu, inflater); 483 } 484 485 @Override onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height)486 public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { 487 initCamera(surface); 488 } 489 490 @Override onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height)491 public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { 492 // Do nothing 493 } 494 495 @Override onSurfaceTextureDestroyed(SurfaceTexture surface)496 public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { 497 destroyCamera(); 498 return true; 499 } 500 501 @Override onSurfaceTextureUpdated(SurfaceTexture surface)502 public void onSurfaceTextureUpdated(SurfaceTexture surface) { 503 // Do nothing 504 } 505 506 @Override getViewSize()507 public Size getViewSize() { 508 return new Size(mTextureView.getWidth(), mTextureView.getHeight()); 509 } 510 511 @Override getFramePosition(Size previewSize, int cameraOrientation)512 public Rect getFramePosition(Size previewSize, int cameraOrientation) { 513 return new Rect(0, 0, previewSize.getHeight(), previewSize.getHeight()); 514 } 515 516 @Override setTransform(Matrix transform)517 public void setTransform(Matrix transform) { 518 mTextureView.setTransform(transform); 519 } 520 521 @Override isValid(String qrCode)522 public boolean isValid(String qrCode) { 523 try { 524 mWifiQrCode = new WifiQrCode(qrCode); 525 } catch (IllegalArgumentException e) { 526 showErrorMessage(R.string.wifi_dpp_qr_code_is_not_valid_format); 527 return false; 528 } 529 530 // It's impossible to provision other device with ZXing Wi-Fi Network config format 531 if (mIsConfiguratorMode 532 && mWifiQrCode.getScheme() 533 == UriParserResults.URI_SCHEME_ZXING_WIFI_NETWORK_CONFIG) { 534 showErrorMessage(R.string.wifi_dpp_qr_code_is_not_valid_format); 535 return false; 536 } 537 538 return true; 539 } 540 541 /** 542 * This method is only called when QrCamera.ScannerCallback.isValid returns true; 543 */ 544 @Override handleSuccessfulResult(String qrCode)545 public void handleSuccessfulResult(String qrCode) { 546 switch (mWifiQrCode.getScheme()) { 547 case UriParserResults.URI_SCHEME_DPP: 548 handleWifiDpp(); 549 break; 550 551 case UriParserResults.URI_SCHEME_ZXING_WIFI_NETWORK_CONFIG: 552 handleZxingWifiFormat(); 553 break; 554 555 default: 556 // continue below 557 } 558 } 559 handleWifiDpp()560 private void handleWifiDpp() { 561 Message message = mHandler.obtainMessage(MESSAGE_SCAN_WIFI_DPP_SUCCESS); 562 message.obj = new WifiQrCode(mWifiQrCode.getQrCode()); 563 564 mHandler.sendMessageDelayed(message, SHOW_SUCCESS_SQUARE_INTERVAL); 565 } 566 handleZxingWifiFormat()567 private void handleZxingWifiFormat() { 568 Message message = mHandler.obtainMessage(MESSAGE_SCAN_ZXING_WIFI_FORMAT_SUCCESS); 569 message.obj = new WifiQrCode(mWifiQrCode.getQrCode()).getWifiConfiguration(); 570 571 mHandler.sendMessageDelayed(message, SHOW_SUCCESS_SQUARE_INTERVAL); 572 } 573 574 @Override handleCameraFailure()575 public void handleCameraFailure() { 576 destroyCamera(); 577 } 578 initCamera(SurfaceTexture surface)579 private void initCamera(SurfaceTexture surface) { 580 // Check if the camera has already created. 581 if (mCamera == null) { 582 mCamera = new QrCamera(getContext(), this); 583 584 if (isWifiDppHandshaking()) { 585 if (mDecorateView != null) { 586 mDecorateView.setFocused(true); 587 } 588 } else { 589 mCamera.start(surface); 590 } 591 } 592 } 593 destroyCamera()594 private void destroyCamera() { 595 if (mCamera != null) { 596 mCamera.stop(); 597 mCamera = null; 598 } 599 } 600 showErrorMessage(@tringRes int messageResId)601 private void showErrorMessage(@StringRes int messageResId) { 602 final Message message = mHandler.obtainMessage(MESSAGE_SHOW_ERROR_MESSAGE, 603 getString(messageResId)); 604 message.sendToTarget(); 605 } 606 607 @VisibleForTesting showErrorMessageAndRestartCamera(@tringRes int messageResId)608 void showErrorMessageAndRestartCamera(@StringRes int messageResId) { 609 final Message message = mHandler.obtainMessage(MESSAGE_SHOW_ERROR_MESSAGE, 610 getString(messageResId)); 611 message.arg1 = ARG_RESTART_CAMERA; 612 message.sendToTarget(); 613 } 614 615 @Override onSaveInstanceState(Bundle outState)616 public void onSaveInstanceState(Bundle outState) { 617 outState.putBoolean(KEY_IS_CONFIGURATOR_MODE, mIsConfiguratorMode); 618 outState.putInt(KEY_LATEST_ERROR_CODE, mLatestStatusCode); 619 outState.putParcelable(KEY_WIFI_CONFIGURATION, mEnrolleeWifiConfiguration); 620 621 super.onSaveInstanceState(outState); 622 } 623 624 private class EasyConnectEnrolleeStatusCallback extends EasyConnectStatusCallback { 625 @Override onEnrolleeSuccess(int newNetworkId)626 public void onEnrolleeSuccess(int newNetworkId) { 627 628 // Connect to the new network. 629 final WifiManager wifiManager = getContext().getSystemService(WifiManager.class); 630 final List<WifiConfiguration> wifiConfigs = 631 wifiManager.getPrivilegedConfiguredNetworks(); 632 for (WifiConfiguration wifiConfig : wifiConfigs) { 633 if (wifiConfig.networkId == newNetworkId) { 634 mLatestStatusCode = WifiDppUtils.EASY_CONNECT_EVENT_SUCCESS; 635 mEnrolleeWifiConfiguration = wifiConfig; 636 if (!canConnectWifi(wifiConfig.SSID)) return; 637 wifiManager.connect(wifiConfig, WifiDppQrCodeScannerFragment.this); 638 return; 639 } 640 } 641 642 Log.e(TAG, "Invalid networkId " + newNetworkId); 643 mLatestStatusCode = EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_GENERIC; 644 updateEnrolleeSummary(); 645 showErrorMessageAndRestartCamera(R.string.wifi_dpp_check_connection_try_again); 646 } 647 648 @Override onConfiguratorSuccess(int code)649 public void onConfiguratorSuccess(int code) { 650 // Do nothing 651 } 652 653 @Override onFailure(int code)654 public void onFailure(int code) { 655 Log.d(TAG, "EasyConnectEnrolleeStatusCallback.onFailure " + code); 656 657 int errorMessageResId; 658 switch (code) { 659 case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_INVALID_URI: 660 errorMessageResId = R.string.wifi_dpp_qr_code_is_not_valid_format; 661 break; 662 663 case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_AUTHENTICATION: 664 errorMessageResId = R.string.wifi_dpp_failure_authentication_or_configuration; 665 break; 666 667 case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_NOT_COMPATIBLE: 668 errorMessageResId = R.string.wifi_dpp_failure_not_compatible; 669 break; 670 671 case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_CONFIGURATION: 672 errorMessageResId = R.string.wifi_dpp_failure_authentication_or_configuration; 673 break; 674 675 case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_BUSY: 676 if (code == mLatestStatusCode) { 677 throw(new IllegalStateException("stopEasyConnectSession and try again for" 678 + "EASY_CONNECT_EVENT_FAILURE_BUSY but still failed")); 679 } 680 681 mLatestStatusCode = code; 682 final WifiManager wifiManager = 683 getContext().getSystemService(WifiManager.class); 684 wifiManager.stopEasyConnectSession(); 685 startWifiDppEnrolleeInitiator(mWifiQrCode); 686 return; 687 688 case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_TIMEOUT: 689 errorMessageResId = R.string.wifi_dpp_failure_timeout; 690 break; 691 692 case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_GENERIC: 693 errorMessageResId = R.string.wifi_dpp_failure_generic; 694 break; 695 696 case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_NOT_SUPPORTED: 697 throw(new IllegalStateException("EASY_CONNECT_EVENT_FAILURE_NOT_SUPPORTED" + 698 " should be a configurator only error")); 699 700 case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_INVALID_NETWORK: 701 throw(new IllegalStateException("EASY_CONNECT_EVENT_FAILURE_INVALID_NETWORK" + 702 " should be a configurator only error")); 703 704 default: 705 throw(new IllegalStateException("Unexpected Wi-Fi DPP error")); 706 } 707 708 mLatestStatusCode = code; 709 updateEnrolleeSummary(); 710 showErrorMessageAndRestartCamera(errorMessageResId); 711 } 712 713 @Override onProgress(int code)714 public void onProgress(int code) { 715 // Do nothing 716 } 717 } 718 startWifiDppEnrolleeInitiator(WifiQrCode wifiQrCode)719 private void startWifiDppEnrolleeInitiator(WifiQrCode wifiQrCode) { 720 final WifiDppInitiatorViewModel model = 721 new ViewModelProvider(this).get(WifiDppInitiatorViewModel.class); 722 723 model.startEasyConnectAsEnrolleeInitiator(wifiQrCode.getQrCode()); 724 } 725 726 @Override onSuccess()727 public void onSuccess() { 728 final Intent resultIntent = new Intent(); 729 resultIntent.putExtra(KEY_WIFI_CONFIGURATION, mEnrolleeWifiConfiguration); 730 731 final Activity hostActivity = getActivity(); 732 if (hostActivity == null) return; 733 if (mWifiPermissionChecker == null) { 734 mWifiPermissionChecker = new WifiPermissionChecker(hostActivity); 735 } 736 737 if (!mWifiPermissionChecker.canAccessWifiState()) { 738 Log.w(TAG, "Calling package does not have ACCESS_WIFI_STATE permission for result."); 739 EventLog.writeEvent(0x534e4554, "187176859", 740 mWifiPermissionChecker.getLaunchedPackage(), "no ACCESS_WIFI_STATE permission"); 741 hostActivity.finish(); 742 return; 743 } 744 745 if (!mWifiPermissionChecker.canAccessFineLocation()) { 746 Log.w(TAG, "Calling package does not have ACCESS_FINE_LOCATION permission for result."); 747 EventLog.writeEvent(0x534e4554, "187176859", 748 mWifiPermissionChecker.getLaunchedPackage(), 749 "no ACCESS_FINE_LOCATION permission"); 750 hostActivity.finish(); 751 return; 752 } 753 754 hostActivity.setResult(Activity.RESULT_OK, resultIntent); 755 hostActivity.finish(); 756 } 757 758 @Override onFailure(int reason)759 public void onFailure(int reason) { 760 Log.d(TAG, "Wi-Fi connect onFailure reason - " + reason); 761 showErrorMessageAndRestartCamera(R.string.wifi_dpp_check_connection_try_again); 762 } 763 764 // Check is Easy Connect handshaking or not isWifiDppHandshaking()765 private boolean isWifiDppHandshaking() { 766 final WifiDppInitiatorViewModel model = 767 new ViewModelProvider(this).get(WifiDppInitiatorViewModel.class); 768 769 return model.isWifiDppHandshaking(); 770 } 771 772 /** 773 * To resume camera decoding task after handshake fail or Wi-Fi connection fail. 774 */ restartCamera()775 private void restartCamera() { 776 if (mCamera == null) { 777 Log.d(TAG, "mCamera is not available for restarting camera"); 778 return; 779 } 780 781 if (mCamera.isDecodeTaskAlive()) { 782 mCamera.stop(); 783 } 784 785 final SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture(); 786 if (surfaceTexture == null) { 787 throw new IllegalStateException("SurfaceTexture is not ready for restarting camera"); 788 } 789 790 mCamera.start(surfaceTexture); 791 } 792 updateEnrolleeSummary()793 private void updateEnrolleeSummary() { 794 if (isWifiDppHandshaking()) { 795 mSummary.setText(R.string.wifi_dpp_connecting); 796 } else { 797 String description; 798 if (TextUtils.isEmpty(mSsid)) { 799 description = getString(R.string.wifi_dpp_scan_qr_code_join_unknown_network, mSsid); 800 } else { 801 description = getString(R.string.wifi_dpp_scan_qr_code_join_network, mSsid); 802 } 803 mSummary.setText(description); 804 } 805 } 806 807 @VisibleForTesting isDecodeTaskAlive()808 protected boolean isDecodeTaskAlive() { 809 return mCamera != null && mCamera.isDecodeTaskAlive(); 810 } 811 812 @Override isFooterAvailable()813 protected boolean isFooterAvailable() { 814 return false; 815 } 816 } 817