| /* |
| * 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<int[]> 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<int[]> 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; |
| } |
| } |