/* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.wifi.dpp; import static android.provider.Settings.EXTRA_EASY_CONNECT_ATTEMPTED_SSID; import static android.provider.Settings.EXTRA_EASY_CONNECT_BAND_LIST; import static android.provider.Settings.EXTRA_EASY_CONNECT_CHANNEL_LIST; import static android.provider.Settings.EXTRA_EASY_CONNECT_ERROR_CODE; import android.app.Activity; import android.app.settings.SettingsEnums; import android.content.Context; import android.content.Intent; import android.net.wifi.EasyConnectStatusCallback; import android.net.wifi.WifiManager; import android.os.Bundle; import android.text.TextUtils; import android.util.Log; import android.util.SparseArray; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.ImageView; import androidx.lifecycle.ViewModelProviders; import com.android.settings.R; import com.google.android.setupcompat.template.FooterButton; import org.json.JSONArray; import org.json.JSONObject; /** * After getting Wi-Fi network information and(or) QR code, this fragment config a device to connect * to the Wi-Fi network. */ public class WifiDppAddDeviceFragment extends WifiDppQrCodeBaseFragment { private static final String TAG = "WifiDppAddDeviceFragment"; private ImageView mWifiApPictureView; private Button mChooseDifferentNetwork; private int mLatestStatusCode = WifiDppUtils.EASY_CONNECT_EVENT_FAILURE_NONE; // Key for Bundle usage private static final String KEY_LATEST_STATUS_CODE = "key_latest_status_code"; private class EasyConnectConfiguratorStatusCallback extends EasyConnectStatusCallback { @Override public void onEnrolleeSuccess(int newNetworkId) { // Do nothing } @Override public void onConfiguratorSuccess(int code) { showSuccessUi(/* isConfigurationChange */ false); } @Override public void onFailure(int code, String ssid, SparseArray channelListArray, int[] operatingClassArray) { Log.d(TAG, "EasyConnectConfiguratorStatusCallback.onFailure: " + code); if (!TextUtils.isEmpty(ssid)) { Log.d(TAG, "Tried SSID: " + ssid); } if (channelListArray.size() != 0) { Log.d(TAG, "Tried channels: " + channelListArray); } if (operatingClassArray != null && operatingClassArray.length > 0) { StringBuilder sb = new StringBuilder("Supported bands: "); for (int i = 0; i < operatingClassArray.length; i++) { sb.append(operatingClassArray[i] + " "); } Log.d(TAG, sb.toString()); } showErrorUi(code, getResultIntent(code, ssid, channelListArray, operatingClassArray), /* isConfigurationChange */ false); } @Override public void onProgress(int code) { // Do nothing } } private void showSuccessUi(boolean isConfigurationChange) { setHeaderIconImageResource(R.drawable.ic_devices_check_circle_green_32dp); setHeaderTitle(R.string.wifi_dpp_wifi_shared_with_device); setProgressBarShown(isEasyConnectHandshaking()); mSummary.setVisibility(View.INVISIBLE); mWifiApPictureView.setImageResource(R.drawable.wifi_dpp_success); mChooseDifferentNetwork.setVisibility(View.INVISIBLE); mLeftButton.setText(getContext(), R.string.wifi_dpp_add_another_device); mLeftButton.setOnClickListener(v -> getFragmentManager().popBackStack()); mRightButton.setText(getContext(), R.string.done); mRightButton.setOnClickListener(v -> { final Activity activity = getActivity(); activity.setResult(Activity.RESULT_OK); activity.finish(); }); mRightButton.setVisibility(View.VISIBLE); if (!isConfigurationChange) { mLatestStatusCode = WifiDppUtils.EASY_CONNECT_EVENT_SUCCESS; } } private Intent getResultIntent(int code, String ssid, SparseArray channelListArray, int[] operatingClassArray) { Intent intent = new Intent(); intent.putExtra(EXTRA_EASY_CONNECT_ERROR_CODE, code); if (!TextUtils.isEmpty(ssid)) { intent.putExtra(EXTRA_EASY_CONNECT_ATTEMPTED_SSID, ssid); } if (channelListArray != null && channelListArray.size() != 0) { int key; int index = 0; JSONObject formattedChannelList = new JSONObject(); // Build a JSON array of operating classes, with an array of channels for each // operating class. do { try { key = channelListArray.keyAt(index); } catch (java.lang.ArrayIndexOutOfBoundsException e) { break; } JSONArray channelsInClassArray = new JSONArray(); int[] output = channelListArray.get(key); for (int i = 0; i < output.length; i++) { channelsInClassArray.put(output[i]); } try { formattedChannelList.put(Integer.toString(key), channelsInClassArray); } catch (org.json.JSONException e) { formattedChannelList = new JSONObject(); break; } index++; } while (true); intent.putExtra(EXTRA_EASY_CONNECT_CHANNEL_LIST, formattedChannelList.toString()); } if (operatingClassArray != null && operatingClassArray.length != 0) { intent.putExtra(EXTRA_EASY_CONNECT_BAND_LIST, operatingClassArray); } return intent; } private void showErrorUi(int code, Intent resultIntent, boolean isConfigurationChange) { CharSequence summaryCharSequence; switch (code) { case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_INVALID_URI: summaryCharSequence = getText(R.string.wifi_dpp_qr_code_is_not_valid_format); break; case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_AUTHENTICATION: summaryCharSequence = getText( R.string.wifi_dpp_failure_authentication_or_configuration); break; case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_NOT_COMPATIBLE: summaryCharSequence = getText(R.string.wifi_dpp_failure_not_compatible); break; case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_CONFIGURATION: summaryCharSequence = getText( R.string.wifi_dpp_failure_authentication_or_configuration); break; case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_BUSY: if (isConfigurationChange) { return; } if (code == mLatestStatusCode) { throw(new IllegalStateException("Tried restarting EasyConnectSession but still" + "receiving EASY_CONNECT_EVENT_FAILURE_BUSY")); } mLatestStatusCode = code; final WifiManager wifiManager = getContext().getSystemService(WifiManager.class); wifiManager.stopEasyConnectSession(); startWifiDppConfiguratorInitiator(); return; case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_TIMEOUT: summaryCharSequence = getText(R.string.wifi_dpp_failure_timeout); break; case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_GENERIC: summaryCharSequence = getText(R.string.wifi_dpp_failure_generic); break; case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_NOT_SUPPORTED: summaryCharSequence = getString( R.string.wifi_dpp_failure_not_supported, getSsid()); break; case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_INVALID_NETWORK: throw(new IllegalStateException("Wi-Fi DPP configurator used a non-PSK/non-SAE" + "network to handshake")); case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK: summaryCharSequence = getText(R.string.wifi_dpp_failure_cannot_find_network); break; case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION: summaryCharSequence = getText(R.string.wifi_dpp_failure_enrollee_authentication); break; case EasyConnectStatusCallback .EASY_CONNECT_EVENT_FAILURE_ENROLLEE_REJECTED_CONFIGURATION: summaryCharSequence = getText(R.string.wifi_dpp_failure_enrollee_rejected_configuration); break; default: throw(new IllegalStateException("Unexpected Wi-Fi DPP error")); } setHeaderTitle(R.string.wifi_dpp_could_not_add_device); mSummary.setText(summaryCharSequence); mWifiApPictureView.setImageResource(R.drawable.wifi_dpp_error); mChooseDifferentNetwork.setVisibility(View.INVISIBLE); FooterButton finishingButton = mLeftButton; if (hasRetryButton(code)) { mRightButton.setText(getContext(), R.string.retry); } else { mRightButton.setText(getContext(), R.string.done); finishingButton = mRightButton; mLeftButton.setVisibility(View.INVISIBLE); } finishingButton.setOnClickListener(v -> { getActivity().setResult(Activity.RESULT_CANCELED, resultIntent); getActivity().finish(); }); if (isEasyConnectHandshaking()) { mSummary.setText(R.string.wifi_dpp_sharing_wifi_with_this_device); } setProgressBarShown(isEasyConnectHandshaking()); mRightButton.setVisibility(isEasyConnectHandshaking() ? View.INVISIBLE : View.VISIBLE); if (!isConfigurationChange) { mLatestStatusCode = code; } } private boolean hasRetryButton(int code) { switch (code) { case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_INVALID_URI: case EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_NOT_COMPATIBLE: return false; default: break; } return true; } @Override public int getMetricsCategory() { return SettingsEnums.SETTINGS_WIFI_DPP_CONFIGURATOR; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (savedInstanceState != null) { mLatestStatusCode = savedInstanceState.getInt(KEY_LATEST_STATUS_CODE); } final WifiDppInitiatorViewModel model = ViewModelProviders.of(this).get(WifiDppInitiatorViewModel.class); model.getStatusCode().observe(this, statusCode -> { // After configuration change, observe callback will be triggered, // do nothing for this case if a handshake does not end if (model.isWifiDppHandshaking()) { return; } int code = statusCode.intValue(); if (code == WifiDppUtils.EASY_CONNECT_EVENT_SUCCESS) { new EasyConnectConfiguratorStatusCallback().onConfiguratorSuccess(code); } else { new EasyConnectConfiguratorStatusCallback().onFailure(code, model.getTriedSsid(), model.getTriedChannels(), model.getBandArray()); } }); } @Override public final View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.wifi_dpp_add_device_fragment, container, /* attachToRoot */ false); } @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); setHeaderIconImageResource(R.drawable.ic_devices_other_32dp); final WifiQrCode wifiQrCode = ((WifiDppConfiguratorActivity) getActivity()) .getWifiDppQrCode(); final String information = wifiQrCode.getInformation(); if (TextUtils.isEmpty(information)) { setHeaderTitle(R.string.wifi_dpp_device_found); } else { setHeaderTitle(information); } updateSummary(); mWifiApPictureView = view.findViewById(R.id.wifi_ap_picture_view); mChooseDifferentNetwork = view.findViewById(R.id.choose_different_network); mChooseDifferentNetwork.setOnClickListener(v -> mClickChooseDifferentNetworkListener.onClickChooseDifferentNetwork() ); mLeftButton.setText(getContext(), R.string.cancel); mLeftButton.setOnClickListener(v -> getActivity().finish()); mRightButton.setText(getContext(), R.string.wifi_dpp_share_wifi); mRightButton.setOnClickListener(v -> { setProgressBarShown(true); mRightButton.setVisibility(View.INVISIBLE); startWifiDppConfiguratorInitiator(); updateSummary(); }); if (savedInstanceState != null) { if (mLatestStatusCode == WifiDppUtils.EASY_CONNECT_EVENT_SUCCESS) { showSuccessUi(/* isConfigurationChange */ true); } else if (mLatestStatusCode == WifiDppUtils.EASY_CONNECT_EVENT_FAILURE_NONE) { setProgressBarShown(isEasyConnectHandshaking()); mRightButton.setVisibility(isEasyConnectHandshaking() ? View.INVISIBLE : View.VISIBLE); } else { showErrorUi(mLatestStatusCode, /* reslutIntent */ null, /* isConfigurationChange */ true); } } } @Override public void onSaveInstanceState(Bundle outState) { outState.putInt(KEY_LATEST_STATUS_CODE, mLatestStatusCode); super.onSaveInstanceState(outState); } private String getSsid() { final WifiNetworkConfig wifiNetworkConfig = ((WifiDppConfiguratorActivity) getActivity()) .getWifiNetworkConfig(); if (!WifiNetworkConfig.isValidConfig(wifiNetworkConfig)) { throw new IllegalStateException("Invalid Wi-Fi network for configuring"); } return wifiNetworkConfig.getSsid(); } private void startWifiDppConfiguratorInitiator() { final WifiQrCode wifiQrCode = ((WifiDppConfiguratorActivity) getActivity()) .getWifiDppQrCode(); final String qrCode = wifiQrCode.getQrCode(); final int networkId = ((WifiDppConfiguratorActivity) getActivity()).getWifiNetworkConfig().getNetworkId(); final WifiDppInitiatorViewModel model = ViewModelProviders.of(this).get(WifiDppInitiatorViewModel.class); model.startEasyConnectAsConfiguratorInitiator(qrCode, networkId); } // Container Activity must implement this interface public interface OnClickChooseDifferentNetworkListener { void onClickChooseDifferentNetwork(); } private OnClickChooseDifferentNetworkListener mClickChooseDifferentNetworkListener; @Override public void onAttach(Context context) { super.onAttach(context); mClickChooseDifferentNetworkListener = (OnClickChooseDifferentNetworkListener) context; } @Override public void onDetach() { mClickChooseDifferentNetworkListener = null; super.onDetach(); } // Check is Easy Connect handshaking or not private boolean isEasyConnectHandshaking() { final WifiDppInitiatorViewModel model = ViewModelProviders.of(this).get(WifiDppInitiatorViewModel.class); return model.isWifiDppHandshaking(); } private void updateSummary() { if (isEasyConnectHandshaking()) { mSummary.setText(R.string.wifi_dpp_sharing_wifi_with_this_device); } else { mSummary.setText(getString(R.string.wifi_dpp_add_device_to_wifi, getSsid())); } } @Override protected boolean isFooterAvailable() { return true; } }