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