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