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