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.ViewModelProvider; 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 new ViewModelProvider(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 new ViewModelProvider(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 407 private OnClickChooseDifferentNetworkListener mClickChooseDifferentNetworkListener; 408 409 @Override onAttach(Context context)410 public void onAttach(Context context) { 411 super.onAttach(context); 412 413 mClickChooseDifferentNetworkListener = (OnClickChooseDifferentNetworkListener) context; 414 } 415 416 @Override onDetach()417 public void onDetach() { 418 mClickChooseDifferentNetworkListener = null; 419 420 super.onDetach(); 421 } 422 423 // Check is Easy Connect handshaking or not isEasyConnectHandshaking()424 private boolean isEasyConnectHandshaking() { 425 final WifiDppInitiatorViewModel model = 426 new ViewModelProvider(this).get(WifiDppInitiatorViewModel.class); 427 428 return model.isWifiDppHandshaking(); 429 } 430 updateSummary()431 private void updateSummary() { 432 if (isEasyConnectHandshaking()) { 433 mSummary.setText(R.string.wifi_dpp_sharing_wifi_with_this_device); 434 } else { 435 mSummary.setText(getString(R.string.wifi_dpp_add_device_to_wifi, getSsid())); 436 } 437 } 438 439 @Override isFooterAvailable()440 protected boolean isFooterAvailable() { 441 return true; 442 } 443 } 444