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