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.provider.Settings.EXTRA_EASY_CONNECT_ATTEMPTED_SSID;
20 import static android.provider.Settings.EXTRA_EASY_CONNECT_BAND_LIST;
21 import static android.provider.Settings.EXTRA_EASY_CONNECT_CHANNEL_LIST;
22 import static android.provider.Settings.EXTRA_EASY_CONNECT_ERROR_CODE;
23 
24 import android.app.Activity;
25 import android.app.settings.SettingsEnums;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.net.wifi.EasyConnectStatusCallback;
29 import android.net.wifi.WifiManager;
30 import android.os.Bundle;
31 import android.text.TextUtils;
32 import android.util.Log;
33 import android.util.SparseArray;
34 import android.view.LayoutInflater;
35 import android.view.View;
36 import android.view.ViewGroup;
37 import android.widget.Button;
38 import android.widget.ImageView;
39 
40 import androidx.lifecycle.ViewModelProviders;
41 
42 import com.android.settings.R;
43 
44 import com.google.android.setupcompat.template.FooterButton;
45 
46 import org.json.JSONArray;
47 import org.json.JSONObject;
48 
49 /**
50  * After getting Wi-Fi network information and(or) QR code, this fragment config a device to connect
51  * to the Wi-Fi network.
52  */
53 public class WifiDppAddDeviceFragment extends WifiDppQrCodeBaseFragment {
54     private static final String TAG = "WifiDppAddDeviceFragment";
55 
56     private ImageView mWifiApPictureView;
57     private Button mChooseDifferentNetwork;
58 
59     private int mLatestStatusCode = WifiDppUtils.EASY_CONNECT_EVENT_FAILURE_NONE;
60 
61     // Key for Bundle usage
62     private static final String KEY_LATEST_STATUS_CODE = "key_latest_status_code";
63 
64     private class EasyConnectConfiguratorStatusCallback extends EasyConnectStatusCallback {
65         @Override
onEnrolleeSuccess(int newNetworkId)66         public void onEnrolleeSuccess(int newNetworkId) {
67             // Do nothing
68         }
69 
70         @Override
onConfiguratorSuccess(int code)71         public void onConfiguratorSuccess(int code) {
72             showSuccessUi(/* isConfigurationChange */ false);
73         }
74 
75         @Override
onFailure(int code, String ssid, SparseArray<int[]> channelListArray, int[] operatingClassArray)76         public void onFailure(int code, String ssid, SparseArray<int[]> channelListArray,
77                 int[] operatingClassArray) {
78             Log.d(TAG, "EasyConnectConfiguratorStatusCallback.onFailure: " + code);
79             if (!TextUtils.isEmpty(ssid)) {
80                 Log.d(TAG, "Tried SSID: " + ssid);
81             }
82             if (channelListArray.size() != 0) {
83                 Log.d(TAG, "Tried channels: " + channelListArray);
84             }
85             if (operatingClassArray != null && operatingClassArray.length > 0) {
86                 StringBuilder sb = new StringBuilder("Supported bands: ");
87                 for (int i = 0; i < operatingClassArray.length; i++) {
88                     sb.append(operatingClassArray[i] + " ");
89                 }
90                 Log.d(TAG, sb.toString());
91             }
92 
93             showErrorUi(code, getResultIntent(code, ssid, channelListArray,
94                     operatingClassArray), /* isConfigurationChange */ false);
95         }
96 
97         @Override
onProgress(int code)98         public void onProgress(int code) {
99             // Do nothing
100         }
101     }
102 
showSuccessUi(boolean isConfigurationChange)103     private void showSuccessUi(boolean isConfigurationChange) {
104         setHeaderIconImageResource(R.drawable.ic_devices_check_circle_green_32dp);
105         setHeaderTitle(R.string.wifi_dpp_wifi_shared_with_device);
106         setProgressBarShown(isEasyConnectHandshaking());
107         mSummary.setVisibility(View.INVISIBLE);
108         mWifiApPictureView.setImageResource(R.drawable.wifi_dpp_success);
109         mChooseDifferentNetwork.setVisibility(View.INVISIBLE);
110         mLeftButton.setText(getContext(), R.string.wifi_dpp_add_another_device);
111         mLeftButton.setOnClickListener(v -> getFragmentManager().popBackStack());
112         mRightButton.setText(getContext(), R.string.done);
113         mRightButton.setOnClickListener(v -> {
114             final Activity activity = getActivity();
115             activity.setResult(Activity.RESULT_OK);
116             activity.finish();
117         });
118         mRightButton.setVisibility(View.VISIBLE);
119 
120         if (!isConfigurationChange) {
121             mLatestStatusCode = WifiDppUtils.EASY_CONNECT_EVENT_SUCCESS;
122         }
123     }
124 
getResultIntent(int code, String ssid, SparseArray<int[]> channelListArray, int[] operatingClassArray)125     private Intent getResultIntent(int code, String ssid, SparseArray<int[]> channelListArray,
126             int[] operatingClassArray) {
127         Intent intent = new Intent();
128         intent.putExtra(EXTRA_EASY_CONNECT_ERROR_CODE, code);
129 
130         if (!TextUtils.isEmpty(ssid)) {
131             intent.putExtra(EXTRA_EASY_CONNECT_ATTEMPTED_SSID, ssid);
132         }
133         if (channelListArray != null && channelListArray.size() != 0) {
134             int key;
135             int index = 0;
136             JSONObject formattedChannelList = new JSONObject();
137 
138             // Build a JSON array of operating classes, with an array of channels for each
139             // operating class.
140             do {
141                 try {
142                     key = channelListArray.keyAt(index);
143                 } catch (java.lang.ArrayIndexOutOfBoundsException e) {
144                     break;
145                 }
146                 JSONArray channelsInClassArray = new JSONArray();
147 
148                 int[] output = channelListArray.get(key);
149                 for (int i = 0; i < output.length; i++) {
150                     channelsInClassArray.put(output[i]);
151                 }
152                 try {
153                     formattedChannelList.put(Integer.toString(key), channelsInClassArray);
154                 } catch (org.json.JSONException e) {
155                     formattedChannelList = new JSONObject();
156                     break;
157                 }
158                 index++;
159             } while (true);
160 
161             intent.putExtra(EXTRA_EASY_CONNECT_CHANNEL_LIST,
162                     formattedChannelList.toString());
163         }
164         if (operatingClassArray != null && operatingClassArray.length != 0) {
165             intent.putExtra(EXTRA_EASY_CONNECT_BAND_LIST, operatingClassArray);
166         }
167 
168         return intent;
169     }
170 
showErrorUi(int code, Intent resultIntent, boolean isConfigurationChange)171     private void showErrorUi(int code, Intent resultIntent, boolean isConfigurationChange) {
172         CharSequence summaryCharSequence;
173         switch (code) {
174             case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_INVALID_URI:
175                 summaryCharSequence = getText(R.string.wifi_dpp_qr_code_is_not_valid_format);
176                 break;
177 
178             case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_AUTHENTICATION:
179                 summaryCharSequence = getText(
180                         R.string.wifi_dpp_failure_authentication_or_configuration);
181                 break;
182 
183             case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_NOT_COMPATIBLE:
184                 summaryCharSequence = getText(R.string.wifi_dpp_failure_not_compatible);
185                 break;
186 
187             case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_CONFIGURATION:
188                 summaryCharSequence = getText(
189                         R.string.wifi_dpp_failure_authentication_or_configuration);
190                 break;
191 
192             case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_BUSY:
193                 if (isConfigurationChange) {
194                     return;
195                 }
196 
197                 if (code == mLatestStatusCode) {
198                     throw(new IllegalStateException("Tried restarting EasyConnectSession but still"
199                             + "receiving EASY_CONNECT_EVENT_FAILURE_BUSY"));
200                 }
201 
202                 mLatestStatusCode = code;
203                 final WifiManager wifiManager =
204                         getContext().getSystemService(WifiManager.class);
205                 wifiManager.stopEasyConnectSession();
206                 startWifiDppConfiguratorInitiator();
207                 return;
208 
209             case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_TIMEOUT:
210                 summaryCharSequence = getText(R.string.wifi_dpp_failure_timeout);
211                 break;
212 
213             case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_GENERIC:
214                 summaryCharSequence = getText(R.string.wifi_dpp_failure_generic);
215                 break;
216 
217             case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_NOT_SUPPORTED:
218                 summaryCharSequence = getString(
219                         R.string.wifi_dpp_failure_not_supported, getSsid());
220                 break;
221 
222             case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_INVALID_NETWORK:
223                 throw(new IllegalStateException("Wi-Fi DPP configurator used a non-PSK/non-SAE"
224                         + "network to handshake"));
225 
226             case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK:
227                 summaryCharSequence = getText(R.string.wifi_dpp_failure_cannot_find_network);
228                 break;
229 
230             case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION:
231                 summaryCharSequence = getText(R.string.wifi_dpp_failure_enrollee_authentication);
232                 break;
233 
234             case EasyConnectStatusCallback
235                     .EASY_CONNECT_EVENT_FAILURE_ENROLLEE_REJECTED_CONFIGURATION:
236                 summaryCharSequence =
237                         getText(R.string.wifi_dpp_failure_enrollee_rejected_configuration);
238                 break;
239 
240             default:
241                 throw(new IllegalStateException("Unexpected Wi-Fi DPP error"));
242         }
243 
244         setHeaderTitle(R.string.wifi_dpp_could_not_add_device);
245         mSummary.setText(summaryCharSequence);
246         mWifiApPictureView.setImageResource(R.drawable.wifi_dpp_error);
247         mChooseDifferentNetwork.setVisibility(View.INVISIBLE);
248         FooterButton finishingButton = mLeftButton;
249         if (hasRetryButton(code)) {
250             mRightButton.setText(getContext(), R.string.retry);
251         } else {
252             mRightButton.setText(getContext(), R.string.done);
253             finishingButton = mRightButton;
254             mLeftButton.setVisibility(View.INVISIBLE);
255         }
256         finishingButton.setOnClickListener(v -> {
257             getActivity().setResult(Activity.RESULT_CANCELED, resultIntent);
258             getActivity().finish();
259         });
260 
261         if (isEasyConnectHandshaking()) {
262             mSummary.setText(R.string.wifi_dpp_sharing_wifi_with_this_device);
263         }
264 
265         setProgressBarShown(isEasyConnectHandshaking());
266         mRightButton.setVisibility(isEasyConnectHandshaking() ? View.INVISIBLE : View.VISIBLE);
267 
268         if (!isConfigurationChange) {
269             mLatestStatusCode = code;
270         }
271     }
272 
hasRetryButton(int code)273     private boolean hasRetryButton(int code) {
274         switch (code) {
275             case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_INVALID_URI:
276             case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_NOT_COMPATIBLE:
277                 return false;
278 
279             default:
280                 break;
281         }
282 
283         return true;
284     }
285 
286     @Override
getMetricsCategory()287     public int getMetricsCategory() {
288         return SettingsEnums.SETTINGS_WIFI_DPP_CONFIGURATOR;
289     }
290 
291     @Override
onCreate(Bundle savedInstanceState)292     public void onCreate(Bundle savedInstanceState) {
293         super.onCreate(savedInstanceState);
294 
295         if (savedInstanceState != null) {
296             mLatestStatusCode = savedInstanceState.getInt(KEY_LATEST_STATUS_CODE);
297         }
298 
299         final WifiDppInitiatorViewModel model =
300                 ViewModelProviders.of(this).get(WifiDppInitiatorViewModel.class);
301 
302         model.getStatusCode().observe(this, statusCode -> {
303             // After configuration change, observe callback will be triggered,
304             // do nothing for this case if a handshake does not end
305             if (model.isWifiDppHandshaking()) {
306                 return;
307             }
308 
309             int code = statusCode.intValue();
310             if (code == WifiDppUtils.EASY_CONNECT_EVENT_SUCCESS) {
311                 new EasyConnectConfiguratorStatusCallback().onConfiguratorSuccess(code);
312             } else {
313                 new EasyConnectConfiguratorStatusCallback().onFailure(code, model.getTriedSsid(),
314                         model.getTriedChannels(), model.getBandArray());
315             }
316         });
317     }
318 
319     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)320     public final View onCreateView(LayoutInflater inflater, ViewGroup container,
321             Bundle savedInstanceState) {
322         return inflater.inflate(R.layout.wifi_dpp_add_device_fragment, container,
323                 /* attachToRoot */ false);
324     }
325 
326     @Override
onViewCreated(View view, Bundle savedInstanceState)327     public void onViewCreated(View view, Bundle savedInstanceState) {
328         super.onViewCreated(view, savedInstanceState);
329 
330         setHeaderIconImageResource(R.drawable.ic_devices_other_32dp);
331 
332         final WifiQrCode wifiQrCode = ((WifiDppConfiguratorActivity) getActivity())
333                 .getWifiDppQrCode();
334         final String information = wifiQrCode.getInformation();
335         if (TextUtils.isEmpty(information)) {
336             setHeaderTitle(R.string.wifi_dpp_device_found);
337         } else {
338             setHeaderTitle(information);
339         }
340 
341         updateSummary();
342         mWifiApPictureView = view.findViewById(R.id.wifi_ap_picture_view);
343 
344         mChooseDifferentNetwork = view.findViewById(R.id.choose_different_network);
345         mChooseDifferentNetwork.setOnClickListener(v ->
346             mClickChooseDifferentNetworkListener.onClickChooseDifferentNetwork()
347         );
348 
349         mLeftButton.setText(getContext(), R.string.cancel);
350         mLeftButton.setOnClickListener(v -> getActivity().finish());
351 
352         mRightButton.setText(getContext(), R.string.wifi_dpp_share_wifi);
353         mRightButton.setOnClickListener(v -> {
354             setProgressBarShown(true);
355             mRightButton.setVisibility(View.INVISIBLE);
356             startWifiDppConfiguratorInitiator();
357             updateSummary();
358         });
359 
360         if (savedInstanceState != null) {
361             if (mLatestStatusCode == WifiDppUtils.EASY_CONNECT_EVENT_SUCCESS) {
362                 showSuccessUi(/* isConfigurationChange */ true);
363             } else if (mLatestStatusCode == WifiDppUtils.EASY_CONNECT_EVENT_FAILURE_NONE) {
364                 setProgressBarShown(isEasyConnectHandshaking());
365                 mRightButton.setVisibility(isEasyConnectHandshaking() ?
366                         View.INVISIBLE : View.VISIBLE);
367             } else {
368                 showErrorUi(mLatestStatusCode, /* reslutIntent */ null, /* isConfigurationChange */
369                         true);
370             }
371         }
372     }
373 
374     @Override
onSaveInstanceState(Bundle outState)375     public void onSaveInstanceState(Bundle outState) {
376         outState.putInt(KEY_LATEST_STATUS_CODE, mLatestStatusCode);
377 
378         super.onSaveInstanceState(outState);
379     }
380 
getSsid()381     private String getSsid() {
382         final WifiNetworkConfig wifiNetworkConfig = ((WifiDppConfiguratorActivity) getActivity())
383                 .getWifiNetworkConfig();
384         if (!WifiNetworkConfig.isValidConfig(wifiNetworkConfig)) {
385             throw new IllegalStateException("Invalid Wi-Fi network for configuring");
386         }
387         return wifiNetworkConfig.getSsid();
388     }
389 
startWifiDppConfiguratorInitiator()390     private void startWifiDppConfiguratorInitiator() {
391         final WifiQrCode wifiQrCode = ((WifiDppConfiguratorActivity) getActivity())
392                 .getWifiDppQrCode();
393         final String qrCode = wifiQrCode.getQrCode();
394         final int networkId =
395                 ((WifiDppConfiguratorActivity) getActivity()).getWifiNetworkConfig().getNetworkId();
396         final WifiDppInitiatorViewModel model =
397                 ViewModelProviders.of(this).get(WifiDppInitiatorViewModel.class);
398 
399         model.startEasyConnectAsConfiguratorInitiator(qrCode, networkId);
400     }
401 
402     // Container Activity must implement this interface
403     public interface OnClickChooseDifferentNetworkListener {
onClickChooseDifferentNetwork()404         void onClickChooseDifferentNetwork();
405     }
406     private OnClickChooseDifferentNetworkListener mClickChooseDifferentNetworkListener;
407 
408     @Override
onAttach(Context context)409     public void onAttach(Context context) {
410         super.onAttach(context);
411 
412         mClickChooseDifferentNetworkListener = (OnClickChooseDifferentNetworkListener) context;
413     }
414 
415     @Override
onDetach()416     public void onDetach() {
417         mClickChooseDifferentNetworkListener = null;
418 
419         super.onDetach();
420     }
421 
422     // Check is Easy Connect handshaking or not
isEasyConnectHandshaking()423     private boolean isEasyConnectHandshaking() {
424         final WifiDppInitiatorViewModel model =
425                 ViewModelProviders.of(this).get(WifiDppInitiatorViewModel.class);
426 
427         return model.isWifiDppHandshaking();
428     }
429 
updateSummary()430     private void updateSummary() {
431         if (isEasyConnectHandshaking()) {
432             mSummary.setText(R.string.wifi_dpp_sharing_wifi_with_this_device);
433         } else {
434             mSummary.setText(getString(R.string.wifi_dpp_add_device_to_wifi, getSsid()));
435         }
436     }
437 
438     @Override
isFooterAvailable()439     protected boolean isFooterAvailable() {
440         return true;
441     }
442 }
443