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 android.app.ActionBar; 20 import android.app.Activity; 21 import android.app.settings.SettingsEnums; 22 import android.content.Context; 23 import android.content.pm.ActivityInfo; 24 import android.net.wifi.EasyConnectStatusCallback; 25 import android.net.wifi.WifiManager; 26 import android.os.Bundle; 27 import android.text.TextUtils; 28 import android.util.Log; 29 import android.view.LayoutInflater; 30 import android.view.View; 31 import android.view.ViewGroup; 32 import android.view.accessibility.AccessibilityEvent; 33 import android.widget.Button; 34 import android.widget.ImageView; 35 import android.widget.ProgressBar; 36 37 import androidx.lifecycle.ViewModelProviders; 38 39 import com.android.settings.R; 40 41 import java.util.concurrent.Executor; 42 43 /** 44 * After getting Wi-Fi network information and(or) QR code, this fragment config a device to connect 45 * to the Wi-Fi network. 46 */ 47 public class WifiDppAddDeviceFragment extends WifiDppQrCodeBaseFragment { 48 private static final String TAG = "WifiDppAddDeviceFragment"; 49 50 private ProgressBar mProgressBar; 51 private ImageView mWifiApPictureView; 52 private Button mChooseDifferentNetwork; 53 private Button mButtonLeft; 54 private Button mButtonRight; 55 56 private int mLatestStatusCode = WifiDppUtils.EASY_CONNECT_EVENT_FAILURE_NONE; 57 58 // Key for Bundle usage 59 private static final String KEY_LATEST_ERROR_CODE = "key_latest_error_code"; 60 61 private class EasyConnectConfiguratorStatusCallback extends EasyConnectStatusCallback { 62 @Override onEnrolleeSuccess(int newNetworkId)63 public void onEnrolleeSuccess(int newNetworkId) { 64 // Do nothing 65 } 66 67 @Override onConfiguratorSuccess(int code)68 public void onConfiguratorSuccess(int code) { 69 showSuccessUi(/* isConfigurationChange */ false); 70 } 71 72 @Override onFailure(int code)73 public void onFailure(int code) { 74 Log.d(TAG, "EasyConnectConfiguratorStatusCallback.onFailure " + code); 75 76 showErrorUi(code, /* isConfigurationChange */ false); 77 } 78 79 @Override onProgress(int code)80 public void onProgress(int code) { 81 // Do nothing 82 } 83 } 84 showSuccessUi(boolean isConfigurationChange)85 private void showSuccessUi(boolean isConfigurationChange) { 86 setHeaderIconImageResource(R.drawable.ic_devices_check_circle_green); 87 mTitle.setText(R.string.wifi_dpp_wifi_shared_with_device); 88 mProgressBar.setVisibility(isGoingInitiator() ? View.VISIBLE : View.INVISIBLE); 89 mSummary.setVisibility(View.INVISIBLE); 90 mWifiApPictureView.setImageResource(R.drawable.wifi_dpp_success); 91 mChooseDifferentNetwork.setVisibility(View.INVISIBLE); 92 mButtonLeft.setText(R.string.wifi_dpp_add_another_device); 93 mButtonLeft.setOnClickListener(v -> getFragmentManager().popBackStack()); 94 mButtonRight.setText(R.string.done); 95 mButtonRight.setOnClickListener(v -> { 96 final Activity activity = getActivity(); 97 activity.setResult(Activity.RESULT_OK); 98 activity.finish(); 99 }); 100 101 if (!isConfigurationChange) { 102 mLatestStatusCode = WifiDppUtils.EASY_CONNECT_EVENT_SUCCESS; 103 changeFocusAndAnnounceChange(mButtonRight, mTitle); 104 } 105 } 106 showErrorUi(int code, boolean isConfigurationChange)107 private void showErrorUi(int code, boolean isConfigurationChange) { 108 switch (code) { 109 case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_INVALID_URI: 110 mSummary.setText(R.string.wifi_dpp_could_not_detect_valid_qr_code); 111 break; 112 113 case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_AUTHENTICATION: 114 mSummary.setText(R.string.wifi_dpp_failure_authentication_or_configuration); 115 break; 116 117 case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_NOT_COMPATIBLE: 118 mSummary.setText(R.string.wifi_dpp_failure_not_compatible); 119 break; 120 121 case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_CONFIGURATION: 122 mSummary.setText(R.string.wifi_dpp_failure_authentication_or_configuration); 123 break; 124 125 case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_BUSY: 126 if (isConfigurationChange) { 127 return; 128 } 129 130 if (code == mLatestStatusCode) { 131 throw(new IllegalStateException("Tried restarting EasyConnectSession but still" 132 + "receiving EASY_CONNECT_EVENT_FAILURE_BUSY")); 133 } 134 135 mLatestStatusCode = code; 136 final WifiManager wifiManager = 137 getContext().getSystemService(WifiManager.class); 138 wifiManager.stopEasyConnectSession(); 139 startWifiDppConfiguratorInitiator(); 140 return; 141 142 case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_TIMEOUT: 143 mSummary.setText(R.string.wifi_dpp_failure_timeout); 144 break; 145 146 case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_GENERIC: 147 mSummary.setText(R.string.wifi_dpp_failure_generic); 148 break; 149 150 case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_NOT_SUPPORTED: 151 mSummary.setText(getString(R.string.wifi_dpp_failure_not_supported, getSsid())); 152 break; 153 154 case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_INVALID_NETWORK: 155 throw(new IllegalStateException("Wi-Fi DPP configurator used a non-PSK/non-SAE" 156 + "network to handshake")); 157 158 default: 159 throw(new IllegalStateException("Unexpected Wi-Fi DPP error")); 160 } 161 162 mTitle.setText(R.string.wifi_dpp_could_not_add_device); 163 mWifiApPictureView.setImageResource(R.drawable.wifi_dpp_error); 164 mChooseDifferentNetwork.setVisibility(View.INVISIBLE); 165 if (hasRetryButton(code)) { 166 mButtonRight.setText(R.string.retry); 167 } else { 168 mButtonRight.setText(R.string.done); 169 mButtonRight.setOnClickListener(v -> getActivity().finish()); 170 mButtonLeft.setVisibility(View.INVISIBLE); 171 } 172 173 if (isGoingInitiator()) { 174 mSummary.setText(R.string.wifi_dpp_sharing_wifi_with_this_device); 175 } 176 177 mProgressBar.setVisibility(isGoingInitiator() ? View.VISIBLE : View.INVISIBLE); 178 mButtonRight.setVisibility(isGoingInitiator() ? View.INVISIBLE : View.VISIBLE); 179 180 if (!isConfigurationChange) { 181 mLatestStatusCode = code; 182 changeFocusAndAnnounceChange(mButtonRight, mSummary); 183 } 184 } 185 hasRetryButton(int code)186 private boolean hasRetryButton(int code) { 187 switch (code) { 188 case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_INVALID_URI: 189 case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_NOT_COMPATIBLE: 190 return false; 191 192 default: 193 break; 194 } 195 196 return true; 197 } 198 199 @Override getMetricsCategory()200 public int getMetricsCategory() { 201 return SettingsEnums.SETTINGS_WIFI_DPP_CONFIGURATOR; 202 } 203 204 @Override onCreate(Bundle savedInstanceState)205 public void onCreate(Bundle savedInstanceState) { 206 super.onCreate(savedInstanceState); 207 208 if (savedInstanceState != null) { 209 mLatestStatusCode = savedInstanceState.getInt(KEY_LATEST_ERROR_CODE); 210 } 211 212 final WifiDppInitiatorViewModel model = 213 ViewModelProviders.of(this).get(WifiDppInitiatorViewModel.class); 214 215 model.getStatusCode().observe(this, statusCode -> { 216 // After configuration change, observe callback will be triggered, 217 // do nothing for this case if a handshake does not end 218 if (model.isGoingInitiator()) { 219 return; 220 } 221 222 int code = statusCode.intValue(); 223 if (code == WifiDppUtils.EASY_CONNECT_EVENT_SUCCESS) { 224 new EasyConnectConfiguratorStatusCallback().onConfiguratorSuccess(code); 225 } else { 226 new EasyConnectConfiguratorStatusCallback().onFailure(code); 227 } 228 }); 229 } 230 231 @Override onActivityCreated(Bundle savedInstanceState)232 public void onActivityCreated(Bundle savedInstanceState) { 233 super.onActivityCreated(savedInstanceState); 234 235 final ActionBar actionBar = getActivity().getActionBar(); 236 if (actionBar != null) { 237 actionBar.hide(); 238 } 239 } 240 241 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)242 public final View onCreateView(LayoutInflater inflater, ViewGroup container, 243 Bundle savedInstanceState) { 244 return inflater.inflate(R.layout.wifi_dpp_add_device_fragment, container, 245 /* attachToRoot */ false); 246 } 247 248 @Override onViewCreated(View view, Bundle savedInstanceState)249 public void onViewCreated(View view, Bundle savedInstanceState) { 250 super.onViewCreated(view, savedInstanceState); 251 252 setHeaderIconImageResource(R.drawable.ic_devices_other_opaque_black); 253 254 mProgressBar = view.findViewById(R.id.indeterminate_bar); 255 256 final WifiQrCode wifiQrCode = ((WifiDppConfiguratorActivity) getActivity()) 257 .getWifiDppQrCode(); 258 final String information = wifiQrCode.getInformation(); 259 if (TextUtils.isEmpty(information)) { 260 mTitle.setText(R.string.wifi_dpp_device_found); 261 } else { 262 mTitle.setText(information); 263 } 264 265 updateSummary(); 266 mWifiApPictureView = view.findViewById(R.id.wifi_ap_picture_view); 267 268 mChooseDifferentNetwork = view.findViewById(R.id.choose_different_network); 269 mChooseDifferentNetwork.setOnClickListener(v -> 270 mClickChooseDifferentNetworkListener.onClickChooseDifferentNetwork() 271 ); 272 273 mButtonLeft = view.findViewById(R.id.button_left); 274 mButtonLeft.setText(R.string.cancel); 275 mButtonLeft.setOnClickListener(v -> getActivity().finish()); 276 277 mButtonRight = view.findViewById(R.id.button_right); 278 mButtonRight.setText(R.string.wifi_dpp_share_wifi); 279 mButtonRight.setOnClickListener(v -> { 280 mProgressBar.setVisibility(View.VISIBLE); 281 mButtonRight.setVisibility(View.INVISIBLE); 282 startWifiDppConfiguratorInitiator(); 283 updateSummary(); 284 mTitleSummaryContainer.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); 285 }); 286 287 if (savedInstanceState != null) { 288 if (mLatestStatusCode == WifiDppUtils.EASY_CONNECT_EVENT_SUCCESS) { 289 showSuccessUi(/* isConfigurationChange */ true); 290 } else if (mLatestStatusCode == WifiDppUtils.EASY_CONNECT_EVENT_FAILURE_NONE) { 291 mProgressBar.setVisibility(isGoingInitiator() ? View.VISIBLE : View.INVISIBLE); 292 mButtonRight.setVisibility(isGoingInitiator() ? View.INVISIBLE : View.VISIBLE); 293 } else { 294 showErrorUi(mLatestStatusCode, /* isConfigurationChange */ true); 295 } 296 } else { 297 changeFocusAndAnnounceChange(mButtonRight, mTitleSummaryContainer); 298 } 299 } 300 301 @Override onSaveInstanceState(Bundle outState)302 public void onSaveInstanceState(Bundle outState) { 303 outState.putInt(KEY_LATEST_ERROR_CODE, mLatestStatusCode); 304 305 super.onSaveInstanceState(outState); 306 } 307 getSsid()308 private String getSsid() { 309 final WifiNetworkConfig wifiNetworkConfig = ((WifiDppConfiguratorActivity) getActivity()) 310 .getWifiNetworkConfig(); 311 if (!WifiNetworkConfig.isValidConfig(wifiNetworkConfig)) { 312 throw new IllegalStateException("Invalid Wi-Fi network for configuring"); 313 } 314 return wifiNetworkConfig.getSsid(); 315 } 316 startWifiDppConfiguratorInitiator()317 private void startWifiDppConfiguratorInitiator() { 318 final WifiQrCode wifiQrCode = ((WifiDppConfiguratorActivity) getActivity()) 319 .getWifiDppQrCode(); 320 final String qrCode = wifiQrCode.getQrCode(); 321 final int networkId = 322 ((WifiDppConfiguratorActivity) getActivity()).getWifiNetworkConfig().getNetworkId(); 323 final WifiDppInitiatorViewModel model = 324 ViewModelProviders.of(this).get(WifiDppInitiatorViewModel.class); 325 326 model.startEasyConnectAsConfiguratorInitiator(qrCode, networkId); 327 } 328 329 // Container Activity must implement this interface 330 public interface OnClickChooseDifferentNetworkListener { onClickChooseDifferentNetwork()331 public void onClickChooseDifferentNetwork(); 332 } 333 OnClickChooseDifferentNetworkListener mClickChooseDifferentNetworkListener; 334 335 @Override onAttach(Context context)336 public void onAttach(Context context) { 337 super.onAttach(context); 338 339 mClickChooseDifferentNetworkListener = (OnClickChooseDifferentNetworkListener) context; 340 } 341 342 @Override onDetach()343 public void onDetach() { 344 mClickChooseDifferentNetworkListener = null; 345 346 super.onDetach(); 347 } 348 349 // Check is Easy Connect handshaking or not isGoingInitiator()350 private boolean isGoingInitiator() { 351 final WifiDppInitiatorViewModel model = 352 ViewModelProviders.of(this).get(WifiDppInitiatorViewModel.class); 353 354 return model.isGoingInitiator(); 355 } 356 updateSummary()357 private void updateSummary() { 358 if (isGoingInitiator()) { 359 mSummary.setText(R.string.wifi_dpp_sharing_wifi_with_this_device); 360 } else { 361 mSummary.setText(getString(R.string.wifi_dpp_add_device_to_wifi, getSsid())); 362 } 363 } 364 365 /** 366 * This fragment will change UI display and text messages for events. To improve Talkback user 367 * experienience, using this method to focus on a right component and announce a changed text 368 * after an UI changing event. 369 * 370 * @param focusView The UI component which will be focused 371 * @param announceView The UI component's text will be talked 372 */ changeFocusAndAnnounceChange(View focusView, View announceView)373 private void changeFocusAndAnnounceChange(View focusView, View announceView) { 374 focusView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); 375 announceView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); 376 } 377 } 378