| /* |
| * Copyright (C) 2019 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.addappnetworks; |
| |
| import static android.app.Activity.RESULT_CANCELED; |
| import static android.app.Activity.RESULT_OK; |
| |
| import android.app.settings.SettingsEnums; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.pm.PackageManager; |
| import android.graphics.drawable.Drawable; |
| import android.net.wifi.WifiConfiguration; |
| import android.net.wifi.WifiConfiguration.KeyMgmt; |
| import android.net.wifi.WifiManager; |
| import android.net.wifi.WifiNetworkSuggestion; |
| import android.net.wifi.hotspot2.PasspointConfiguration; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.os.HandlerThread; |
| import android.os.Looper; |
| import android.os.Message; |
| import android.os.Process; |
| import android.os.SimpleClock; |
| import android.os.SystemClock; |
| import android.provider.Settings; |
| import android.text.TextUtils; |
| import android.util.Log; |
| import android.view.LayoutInflater; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.widget.ArrayAdapter; |
| import android.widget.Button; |
| import android.widget.ImageView; |
| import android.widget.ListView; |
| import android.widget.TextView; |
| |
| import androidx.annotation.NonNull; |
| import androidx.annotation.Nullable; |
| import androidx.fragment.app.FragmentActivity; |
| import androidx.preference.internal.PreferenceImageView; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.settings.R; |
| import com.android.settings.Utils; |
| import com.android.settings.core.InstrumentedFragment; |
| import com.android.settings.overlay.FeatureFactory; |
| import com.android.wifitrackerlib.WifiEntry; |
| import com.android.wifitrackerlib.WifiPickerTracker; |
| |
| import java.time.Clock; |
| import java.time.ZoneOffset; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Optional; |
| |
| /** |
| * The Fragment list those networks, which is proposed by other app, to user, and handle user's |
| * choose on either saving those networks or rejecting the request. |
| */ |
| public class AddAppNetworksFragment extends InstrumentedFragment implements |
| WifiPickerTracker.WifiPickerTrackerCallback { |
| public static final String TAG = "AddAppNetworksFragment"; |
| |
| // Possible result values in each item of the returned result list, which is used |
| // to inform the caller APP the processed result of each specified network. |
| @VisibleForTesting |
| static final int RESULT_NETWORK_SUCCESS = 0; |
| private static final int RESULT_NETWORK_ADD_ERROR = 1; |
| @VisibleForTesting |
| static final int RESULT_NETWORK_ALREADY_EXISTS = 2; |
| |
| // Handler messages for controlling different state and delay showing the status message. |
| @VisibleForTesting static final int MESSAGE_START_SAVING_NETWORK = 1; |
| @VisibleForTesting static final int MESSAGE_SHOW_SAVED_AND_CONNECT_NETWORK = 2; |
| @VisibleForTesting static final int MESSAGE_SHOW_SAVE_FAILED = 3; |
| private static final int MESSAGE_FINISH = 4; |
| |
| // Signal level for the initial signal icon. |
| @VisibleForTesting |
| static final int INITIAL_RSSI_SIGNAL_LEVEL = 0; |
| // Max networks count within one request. |
| private static final int MAX_SPECIFIC_NETWORKS_COUNT = 5; |
| |
| // Duration for showing different status message. |
| private static final long SHOW_SAVING_INTERVAL_MILLIS = 500L; |
| private static final long SHOW_SAVED_INTERVAL_MILLIS = 1000L; |
| |
| // Max age of tracked WifiEntries. |
| private static final long MAX_SCAN_AGE_MILLIS = 15_000; |
| // Interval between initiating WifiPickerTracker scans. |
| private static final long SCAN_INTERVAL_MILLIS = 10_000; |
| |
| @VisibleForTesting |
| FragmentActivity mActivity; |
| @VisibleForTesting |
| View mLayoutView; |
| @VisibleForTesting |
| Button mCancelButton; |
| @VisibleForTesting |
| Button mSaveButton; |
| @VisibleForTesting |
| String mCallingPackageName; |
| @VisibleForTesting |
| List<WifiNetworkSuggestion> mAllSpecifiedNetworksList; |
| @VisibleForTesting |
| List<UiConfigurationItem> mUiToRequestedList; |
| @VisibleForTesting |
| List<Integer> mResultCodeArrayList; |
| @VisibleForTesting |
| WifiPickerTracker mWifiPickerTracker; |
| // Worker thread used for WifiPickerTracker work |
| @VisibleForTesting |
| HandlerThread mWorkerThread; |
| |
| private boolean mIsSingleNetwork; |
| private boolean mAnyNetworkSavedSuccess; |
| private TextView mSummaryView; |
| private TextView mSingleNetworkProcessingStatusView; |
| private int mSavingIndex; |
| private UiConfigurationItemAdapter mUiConfigurationItemAdapter; |
| private WifiManager.ActionListener mSaveListener; |
| private WifiManager mWifiManager; |
| |
| @VisibleForTesting |
| final Handler mHandler = new Handler() { |
| @Override |
| public void handleMessage(Message msg) { |
| showSaveStatusByState(msg.what); |
| |
| switch (msg.what) { |
| case MESSAGE_START_SAVING_NETWORK: |
| mSaveButton.setEnabled(false); |
| // Save the proposed networks, start from first one. |
| mSavingIndex = 0; |
| saveNetwork(mSavingIndex); |
| break; |
| |
| case MESSAGE_SHOW_SAVED_AND_CONNECT_NETWORK: |
| // For the single network case, we need to call connection after saved. |
| if (mIsSingleNetwork) { |
| connectNetwork(0); |
| } |
| sendEmptyMessageDelayed(MESSAGE_FINISH, |
| SHOW_SAVED_INTERVAL_MILLIS); |
| break; |
| |
| case MESSAGE_SHOW_SAVE_FAILED: |
| mSaveButton.setEnabled(true); |
| break; |
| |
| case MESSAGE_FINISH: |
| finishWithResult(RESULT_OK, mResultCodeArrayList); |
| break; |
| |
| default: |
| // Do nothing. |
| break; |
| } |
| } |
| }; |
| |
| @Nullable |
| @Override |
| public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, |
| @Nullable Bundle savedInstanceState) { |
| mActivity = getActivity(); |
| mWifiManager = mActivity.getSystemService(WifiManager.class); |
| mWorkerThread = new HandlerThread( |
| TAG + "{" + Integer.toHexString(System.identityHashCode(this)) + "}", |
| Process.THREAD_PRIORITY_BACKGROUND); |
| mWorkerThread.start(); |
| final Clock elapsedRealtimeClock = new SimpleClock(ZoneOffset.UTC) { |
| @Override |
| public long millis() { |
| return SystemClock.elapsedRealtime(); |
| } |
| }; |
| mWifiPickerTracker = FeatureFactory.getFactory(mActivity.getApplicationContext()) |
| .getWifiTrackerLibProvider() |
| .createWifiPickerTracker(getSettingsLifecycle(), mActivity, |
| new Handler(Looper.getMainLooper()), |
| mWorkerThread.getThreadHandler(), |
| elapsedRealtimeClock, |
| MAX_SCAN_AGE_MILLIS, |
| SCAN_INTERVAL_MILLIS, |
| this); |
| return inflater.inflate(R.layout.wifi_add_app_networks, container, false); |
| } |
| |
| @Override |
| public void onDestroy() { |
| mWorkerThread.quit(); |
| |
| super.onDestroy(); |
| } |
| |
| @Override |
| public void onViewCreated(View view, Bundle savedInstanceState) { |
| super.onViewCreated(view, savedInstanceState); |
| |
| // Initial UI variables. |
| mLayoutView = view; |
| mCancelButton = view.findViewById(R.id.cancel); |
| mSaveButton = view.findViewById(R.id.save); |
| mSummaryView = view.findViewById(R.id.app_summary); |
| mSingleNetworkProcessingStatusView = view.findViewById(R.id.single_status); |
| // Assigns button listeners and network save listener. |
| mCancelButton.setOnClickListener(getCancelClickListener()); |
| mSaveButton.setOnClickListener(getSaveClickListener()); |
| prepareSaveResultListener(); |
| |
| // Prepare the non-UI variables. |
| final Bundle bundle = getArguments(); |
| createContent(bundle); |
| } |
| |
| /** |
| * Updates the UI contents to be aligned with the parameters in Bundle. This API may be called |
| * by the Activity directly when get a new intent. |
| */ |
| @VisibleForTesting |
| void createContent(Bundle bundle) { |
| // For new intent case, if device is saving those networks specified in old intent, just |
| // ignore this new intent for preventing status error. |
| if (mSaveButton != null && !mSaveButton.isEnabled()) { |
| Log.d(TAG, "Network saving, ignore new intent"); |
| return; |
| } |
| |
| mAllSpecifiedNetworksList = |
| bundle.getParcelableArrayList(Settings.EXTRA_WIFI_NETWORK_LIST); |
| |
| // If there is no network in the request intent or the requested networks exceed the |
| // maximum limit, then just finish activity. |
| if (mAllSpecifiedNetworksList == null || mAllSpecifiedNetworksList.isEmpty() |
| || mAllSpecifiedNetworksList.size() > MAX_SPECIFIC_NETWORKS_COUNT) { |
| finishWithResult(RESULT_CANCELED, null /* resultArrayList */); |
| return; |
| } |
| |
| // Initial the result arry. |
| initializeResultCodeArray(); |
| // Filter the saved networks, and prepare a not saved networks list for UI to present. |
| filterSavedNetworks(mWifiManager.getPrivilegedConfiguredNetworks()); |
| |
| // If all the specific networks are all exist, we just need to finish with result. |
| if (mUiToRequestedList.size() == 0) { |
| finishWithResult(RESULT_OK, mResultCodeArrayList); |
| return; |
| } |
| |
| if (mAllSpecifiedNetworksList.size() == 1) { |
| mIsSingleNetwork = true; |
| // Set the multiple networks related layout to be gone, and the single network layout |
| // items to be visible. |
| mLayoutView.findViewById(R.id.multiple_networks).setVisibility(View.GONE); |
| mLayoutView.findViewById(R.id.single_network).setVisibility(View.VISIBLE); |
| |
| // Show signal icon for single network case. |
| updateSingleNetworkSignalIcon(INITIAL_RSSI_SIGNAL_LEVEL); |
| // Show the SSID of the proposed network. |
| ((TextView) mLayoutView.findViewById(R.id.single_ssid)).setText( |
| mUiToRequestedList.get(0).mDisplayedSsid); |
| // Set the status view as gone when UI is initialized. |
| mSingleNetworkProcessingStatusView.setVisibility(View.GONE); |
| } else { |
| // Multiple networks request case. |
| mIsSingleNetwork = false; |
| // Set the single network related layout to be gone, and the multiple networks layout |
| // items to be visible. |
| mLayoutView.findViewById(R.id.single_network).setVisibility(View.GONE); |
| mLayoutView.findViewById(R.id.multiple_networks).setVisibility(View.VISIBLE); |
| |
| if (mUiConfigurationItemAdapter == null) { |
| // Prepare a UI adapter and set to UI listview. |
| final ListView uiNetworkListView = mLayoutView.findViewById(R.id.config_list); |
| mUiConfigurationItemAdapter = new UiConfigurationItemAdapter(mActivity, |
| com.android.settingslib.R.layout.preference_access_point, |
| mUiToRequestedList); |
| uiNetworkListView.setAdapter(mUiConfigurationItemAdapter); |
| } else { |
| mUiConfigurationItemAdapter.notifyDataSetChanged(); |
| } |
| } |
| |
| // Assigns caller app icon, title, and summary. |
| mCallingPackageName = |
| bundle.getString(AddAppNetworksActivity.KEY_CALLING_PACKAGE_NAME); |
| assignAppIcon(mActivity, mCallingPackageName); |
| assignTitleAndSummary(mActivity, mCallingPackageName); |
| } |
| |
| private void initializeResultCodeArray() { |
| final int networksSize = mAllSpecifiedNetworksList.size(); |
| mResultCodeArrayList = new ArrayList<>(); |
| |
| for (int i = 0; i < networksSize; i++) { |
| mResultCodeArrayList.add(RESULT_NETWORK_SUCCESS); |
| } |
| } |
| |
| private String getWepKey(WifiConfiguration config) { |
| return (config.wepTxKeyIndex >= 0 && config.wepTxKeyIndex < config.wepKeys.length) |
| ? config.wepKeys[config.wepTxKeyIndex] : null; |
| } |
| |
| private boolean isSavedPasspointConfiguration( |
| PasspointConfiguration specifiecPassPointConfiguration) { |
| return mWifiManager.getPasspointConfigurations().stream() |
| .filter(config -> config.equals(specifiecPassPointConfiguration)) |
| .findFirst() |
| .isPresent(); |
| } |
| |
| private boolean isSavedWifiConfiguration(WifiConfiguration specifiedConfig, |
| List<WifiConfiguration> savedWifiConfigurations) { |
| final String ssidWithQuotation = addQuotationIfNeeded(specifiedConfig.SSID); |
| final int authType = specifiedConfig.getAuthType(); |
| // TODO: reformat to use lambda |
| for (WifiConfiguration privilegedWifiConfiguration : savedWifiConfigurations) { |
| // If SSID or security type is different, should be new network or need to be |
| // updated network, continue to check others. |
| if (!ssidWithQuotation.equals(privilegedWifiConfiguration.SSID) |
| || authType != privilegedWifiConfiguration.getAuthType()) { |
| continue; |
| } |
| |
| // If specified network and saved network have same security types, we'll check |
| // more information according to their security type to judge if they are same. |
| switch (authType) { |
| case KeyMgmt.NONE: |
| final String wep = getWepKey(specifiedConfig); |
| final String savedWep = getWepKey(privilegedWifiConfiguration); |
| return TextUtils.equals(wep, savedWep); |
| case KeyMgmt.OWE: |
| return true; |
| case KeyMgmt.WPA_PSK: |
| case KeyMgmt.WPA2_PSK: |
| case KeyMgmt.SAE: |
| if (specifiedConfig.preSharedKey.equals( |
| privilegedWifiConfiguration.preSharedKey)) { |
| return true; |
| } |
| break; |
| // TODO: Check how to judge enterprise type. |
| default: |
| break; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * For the APP specified networks, filter saved ones and mark those saved as existed. And |
| * prepare a new UiConfigurationItem list, which contains those new or need to be updated |
| * networks, for creating UI to user. |
| */ |
| @VisibleForTesting |
| void filterSavedNetworks(List<WifiConfiguration> savedWifiConfigurations) { |
| if (mUiToRequestedList == null) { |
| mUiToRequestedList = new ArrayList<>(); |
| } else { |
| mUiToRequestedList.clear(); |
| } |
| |
| int networkPositionInBundle = 0; |
| for (WifiNetworkSuggestion suggestion : mAllSpecifiedNetworksList) { |
| String displayedName = null; |
| boolean foundInSavedList = false; |
| |
| /* |
| * If specified is passpoint network, need to check with the existing passpoint |
| * networks. |
| */ |
| final PasspointConfiguration passpointConfig = suggestion.getPasspointConfig(); |
| if (passpointConfig != null) { |
| foundInSavedList = isSavedPasspointConfiguration(passpointConfig); |
| displayedName = passpointConfig.getHomeSp().getFriendlyName(); |
| } else { |
| final WifiConfiguration specifiedConfig = suggestion.getWifiConfiguration(); |
| displayedName = removeDoubleQuotes(specifiedConfig.SSID); |
| foundInSavedList = isSavedWifiConfiguration(specifiedConfig, |
| savedWifiConfigurations); |
| } |
| |
| if (foundInSavedList) { |
| // If this requested network already in the saved networks, mark this item in the |
| // result code list as existed. |
| mResultCodeArrayList.set(networkPositionInBundle, RESULT_NETWORK_ALREADY_EXISTS); |
| } else { |
| // Prepare to add to UI list to show to user |
| UiConfigurationItem uiConfigurationItem = new UiConfigurationItem(displayedName, |
| suggestion, networkPositionInBundle, INITIAL_RSSI_SIGNAL_LEVEL); |
| mUiToRequestedList.add(uiConfigurationItem); |
| } |
| networkPositionInBundle++; |
| } |
| } |
| |
| private void updateSingleNetworkSignalIcon(int level) { |
| // TODO: Check level of the network to show signal icon. |
| final Drawable wifiIcon = mActivity.getDrawable( |
| Utils.getWifiIconResource(level)).mutate(); |
| final Drawable wifiIconDark = wifiIcon.getConstantState().newDrawable().mutate(); |
| wifiIconDark.setTintList( |
| Utils.getColorAttr(mActivity, android.R.attr.colorControlNormal)); |
| ((ImageView) mLayoutView.findViewById(R.id.signal_strength)).setImageDrawable(wifiIconDark); |
| } |
| |
| private String addQuotationIfNeeded(String input) { |
| if (TextUtils.isEmpty(input)) { |
| return ""; |
| } |
| |
| if (input.length() >= 2 && input.startsWith("\"") && input.endsWith("\"")) { |
| return input; |
| } |
| |
| StringBuilder sb = new StringBuilder(); |
| sb.append("\"").append(input).append("\""); |
| |
| return sb.toString(); |
| } |
| |
| static String removeDoubleQuotes(String string) { |
| if (TextUtils.isEmpty(string)) { |
| return ""; |
| } |
| int length = string.length(); |
| if ((length > 1) && (string.charAt(0) == '"') |
| && (string.charAt(length - 1) == '"')) { |
| return string.substring(1, length - 1); |
| } |
| return string; |
| } |
| |
| private void assignAppIcon(Context context, String callingPackageName) { |
| final Drawable drawable = loadPackageIconDrawable(context, callingPackageName); |
| ((ImageView) mLayoutView.findViewById(R.id.app_icon)).setImageDrawable(drawable); |
| } |
| |
| private Drawable loadPackageIconDrawable(Context context, String callingPackageName) { |
| Drawable icon = null; |
| try { |
| icon = context.getPackageManager().getApplicationIcon(callingPackageName); |
| } catch (PackageManager.NameNotFoundException e) { |
| Log.d(TAG, "Cannot get application icon", e); |
| } |
| |
| return icon; |
| } |
| |
| private void assignTitleAndSummary(Context context, String callingPackageName) { |
| // Assigns caller app name to title |
| ((TextView) mLayoutView.findViewById(R.id.app_title)).setText(getTitle()); |
| |
| // Set summary |
| mSummaryView.setText(getAddNetworkRequesterSummary( |
| Utils.getApplicationLabel(context, callingPackageName))); |
| } |
| |
| private CharSequence getAddNetworkRequesterSummary(CharSequence appName) { |
| return getString(mIsSingleNetwork ? R.string.wifi_add_app_single_network_summary |
| : R.string.wifi_add_app_networks_summary, appName); |
| } |
| |
| private CharSequence getTitle() { |
| return getString(mIsSingleNetwork ? R.string.wifi_add_app_single_network_title |
| : R.string.wifi_add_app_networks_title); |
| } |
| |
| View.OnClickListener getCancelClickListener() { |
| return (v) -> { |
| Log.d(TAG, "User rejected to add network"); |
| finishWithResult(RESULT_CANCELED, null /* resultArrayList */); |
| }; |
| } |
| |
| View.OnClickListener getSaveClickListener() { |
| return (v) -> { |
| Log.d(TAG, "User agree to add networks"); |
| // Start to process saving networks. |
| final Message message = mHandler.obtainMessage(MESSAGE_START_SAVING_NETWORK); |
| message.sendToTarget(); |
| }; |
| } |
| |
| /** |
| * This class used to show network items to user, each item contains one specific (@Code |
| * WifiConfiguration} and one index to mapping this UI item to the item in the APP request |
| * network list. |
| */ |
| @VisibleForTesting |
| static class UiConfigurationItem { |
| public final String mDisplayedSsid; |
| public final WifiNetworkSuggestion mWifiNetworkSuggestion; |
| public final int mIndex; |
| public int mLevel; |
| |
| UiConfigurationItem(String displayedSsid, WifiNetworkSuggestion wifiNetworkSuggestion, |
| int index, int level) { |
| mDisplayedSsid = displayedSsid; |
| mWifiNetworkSuggestion = wifiNetworkSuggestion; |
| mIndex = index; |
| mLevel = level; |
| } |
| } |
| |
| private class UiConfigurationItemAdapter extends ArrayAdapter<UiConfigurationItem> { |
| private final int mResourceId; |
| private final LayoutInflater mInflater; |
| |
| UiConfigurationItemAdapter(Context context, int resourceId, |
| List<UiConfigurationItem> objects) { |
| super(context, resourceId, objects); |
| mResourceId = resourceId; |
| mInflater = LayoutInflater.from(context); |
| } |
| |
| @Override |
| public View getView(int position, View view, ViewGroup parent) { |
| if (view == null) { |
| view = mInflater.inflate(mResourceId, parent, false /* attachToRoot */); |
| } |
| |
| final View divider = view.findViewById( |
| com.android.settingslib.R.id.two_target_divider); |
| if (divider != null) { |
| divider.setVisibility(View.GONE); |
| } |
| |
| final UiConfigurationItem uiConfigurationItem = getItem(position); |
| final TextView titleView = view.findViewById(android.R.id.title); |
| if (titleView != null) { |
| // Shows whole SSID for better UX. |
| titleView.setSingleLine(false); |
| titleView.setText(uiConfigurationItem.mDisplayedSsid); |
| } |
| |
| final PreferenceImageView imageView = view.findViewById(android.R.id.icon); |
| if (imageView != null) { |
| final Drawable drawable = getContext().getDrawable( |
| com.android.settingslib.Utils.getWifiIconResource( |
| uiConfigurationItem.mLevel)); |
| drawable.setTintList( |
| com.android.settingslib.Utils.getColorAttr(getContext(), |
| android.R.attr.colorControlNormal)); |
| imageView.setImageDrawable(drawable); |
| } |
| |
| final TextView summaryView = view.findViewById(android.R.id.summary); |
| if (summaryView != null) { |
| summaryView.setVisibility(View.GONE); |
| } |
| |
| return view; |
| } |
| } |
| |
| private void prepareSaveResultListener() { |
| mSaveListener = new WifiManager.ActionListener() { |
| @Override |
| public void onSuccess() { |
| mAnyNetworkSavedSuccess = true; |
| |
| if (saveNextNetwork()) { |
| return; |
| } |
| |
| // Show saved or failed according to all results |
| showSavedOrFail(); |
| } |
| |
| @Override |
| public void onFailure(int reason) { |
| // Set result code of this network to be failed in the return list. |
| mResultCodeArrayList.set(mUiToRequestedList.get(mSavingIndex).mIndex, |
| RESULT_NETWORK_ADD_ERROR); |
| |
| if (saveNextNetwork()) { |
| return; |
| } |
| |
| // Show saved or failed according to all results |
| showSavedOrFail(); |
| } |
| }; |
| } |
| |
| /** |
| * For multiple networks case, we need to check if there is other network need to save. |
| */ |
| private boolean saveNextNetwork() { |
| // Save the next network if have. |
| if (!mIsSingleNetwork && mSavingIndex < (mUiToRequestedList.size() - 1)) { |
| mSavingIndex++; |
| saveNetwork(mSavingIndex); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /** |
| * If any one of the specified networks is success, then we show saved and return all results |
| * list back to caller APP, otherwise we show failed to indicate all networks saved failed. |
| */ |
| private void showSavedOrFail() { |
| Message nextStateMessage; |
| if (mAnyNetworkSavedSuccess) { |
| // Enter next state after all networks are saved. |
| nextStateMessage = mHandler.obtainMessage( |
| MESSAGE_SHOW_SAVED_AND_CONNECT_NETWORK); |
| } else { |
| nextStateMessage = mHandler.obtainMessage(MESSAGE_SHOW_SAVE_FAILED); |
| } |
| // Delay to change to next state for showing saving mesage for a period. |
| mHandler.sendMessageDelayed(nextStateMessage, SHOW_SAVING_INTERVAL_MILLIS); |
| } |
| |
| /** |
| * Call framework API to save single network. |
| */ |
| @VisibleForTesting |
| void saveNetwork(int index) { |
| final PasspointConfiguration passpointConfig = |
| mUiToRequestedList.get(index).mWifiNetworkSuggestion.getPasspointConfig(); |
| if (passpointConfig != null) { |
| // Save passpoint, if no IllegalArgumentException, then treat it as success. |
| try { |
| mWifiManager.addOrUpdatePasspointConfiguration(passpointConfig); |
| mAnyNetworkSavedSuccess = true; |
| } catch (IllegalArgumentException e) { |
| mResultCodeArrayList.set(mUiToRequestedList.get(index).mIndex, |
| RESULT_NETWORK_ADD_ERROR); |
| } |
| |
| if (saveNextNetwork()) { |
| return; |
| } |
| // Show saved or failed according to all results. |
| showSavedOrFail(); |
| } else { |
| final WifiConfiguration wifiConfiguration = |
| mUiToRequestedList.get(index).mWifiNetworkSuggestion.getWifiConfiguration(); |
| wifiConfiguration.SSID = addQuotationIfNeeded(wifiConfiguration.SSID); |
| mWifiManager.save(wifiConfiguration, mSaveListener); |
| } |
| } |
| |
| private void connectNetwork(int index) { |
| final WifiConfiguration wifiConfiguration = |
| mUiToRequestedList.get(index).mWifiNetworkSuggestion.getWifiConfiguration(); |
| mWifiManager.connect(wifiConfiguration, null /* ActionListener */); |
| } |
| |
| private void finishWithResult(int resultCode, List<Integer> resultArrayList) { |
| if (mActivity == null) { |
| return; |
| } |
| |
| if (resultArrayList != null) { |
| Intent intent = new Intent(); |
| intent.putIntegerArrayListExtra(Settings.EXTRA_WIFI_NETWORK_RESULT_LIST, |
| (ArrayList<Integer>) resultArrayList); |
| mActivity.setResult(resultCode, intent); |
| } |
| mActivity.finish(); |
| } |
| |
| @Override |
| public int getMetricsCategory() { |
| return SettingsEnums.PANEL_ADD_WIFI_NETWORKS; |
| } |
| |
| @VisibleForTesting |
| void showSaveStatusByState(int status) { |
| switch (status) { |
| case MESSAGE_START_SAVING_NETWORK: |
| if (mIsSingleNetwork) { |
| // Set the initial text color for status message. |
| mSingleNetworkProcessingStatusView.setTextColor( |
| com.android.settingslib.Utils.getColorAttr(mActivity, |
| android.R.attr.textColorSecondary)); |
| mSingleNetworkProcessingStatusView.setText( |
| getString(R.string.wifi_add_app_single_network_saving_summary)); |
| mSingleNetworkProcessingStatusView.setVisibility(View.VISIBLE); |
| } else { |
| mSummaryView.setTextColor( |
| com.android.settingslib.Utils.getColorAttr(mActivity, |
| android.R.attr.textColorSecondary)); |
| mSummaryView.setText( |
| getString(R.string.wifi_add_app_networks_saving_summary, |
| mUiToRequestedList.size())); |
| } |
| break; |
| |
| case MESSAGE_SHOW_SAVED_AND_CONNECT_NETWORK: |
| if (mIsSingleNetwork) { |
| mSingleNetworkProcessingStatusView.setText( |
| getString(R.string.wifi_add_app_single_network_saved_summary)); |
| } else { |
| mSummaryView.setText( |
| getString(R.string.wifi_add_app_networks_saved_summary)); |
| } |
| break; |
| |
| case MESSAGE_SHOW_SAVE_FAILED: |
| if (mIsSingleNetwork) { |
| // Error message need to use colorError attribute to show. |
| mSingleNetworkProcessingStatusView.setTextColor( |
| com.android.settingslib.Utils.getColorAttr(mActivity, |
| android.R.attr.colorError)); |
| mSingleNetworkProcessingStatusView.setText( |
| getString(R.string.wifi_add_app_network_save_failed_summary)); |
| } else { |
| // Error message need to use colorError attribute to show. |
| mSummaryView.setTextColor( |
| com.android.settingslib.Utils.getColorAttr(mActivity, |
| android.R.attr.colorError)); |
| mSummaryView.setText( |
| getString(R.string.wifi_add_app_network_save_failed_summary)); |
| } |
| break; |
| |
| default: |
| // Do nothing. |
| break; |
| } |
| } |
| |
| |
| |
| @VisibleForTesting |
| void updateScanResultsToUi() { |
| if (mUiToRequestedList == null) { |
| // Nothing need to be updated. |
| return; |
| } |
| |
| List<WifiEntry> reachableWifiEntries = null; |
| if (mWifiPickerTracker.getWifiState() == WifiManager.WIFI_STATE_ENABLED) { |
| reachableWifiEntries = mWifiPickerTracker.getWifiEntries(); |
| final WifiEntry connectedWifiEntry = mWifiPickerTracker.getConnectedWifiEntry(); |
| if (connectedWifiEntry != null) { |
| reachableWifiEntries.add(connectedWifiEntry); |
| } |
| } |
| |
| // Update the signal level of the UI networks. |
| for (UiConfigurationItem uiConfigurationItem : mUiToRequestedList) { |
| uiConfigurationItem.mLevel = 0; |
| if (reachableWifiEntries != null) { |
| final Optional<WifiEntry> matchedWifiEntry = reachableWifiEntries.stream() |
| .filter(wifiEntry -> TextUtils.equals( |
| uiConfigurationItem.mWifiNetworkSuggestion.getSsid(), |
| wifiEntry.getSsid())) |
| .findFirst(); |
| uiConfigurationItem.mLevel = |
| matchedWifiEntry.isPresent() ? matchedWifiEntry.get().getLevel() : 0; |
| } |
| } |
| |
| if (mIsSingleNetwork) { |
| updateSingleNetworkSignalIcon(mUiToRequestedList.get(0).mLevel); |
| } else { |
| if (mUiConfigurationItemAdapter != null) { |
| mUiConfigurationItemAdapter.notifyDataSetChanged(); |
| } |
| } |
| } |
| |
| @Override |
| public void onResume() { |
| super.onResume(); |
| onWifiEntriesChanged(); |
| } |
| |
| /** Called when the state of Wifi has changed. */ |
| @Override |
| public void onWifiStateChanged() { |
| onWifiEntriesChanged(); |
| } |
| |
| /** |
| * Update the results when data changes |
| */ |
| @Override |
| public void onWifiEntriesChanged() { |
| updateScanResultsToUi(); |
| } |
| |
| @Override |
| public void onNumSavedSubscriptionsChanged() { |
| // Do nothing. |
| } |
| |
| @Override |
| public void onNumSavedNetworksChanged() { |
| // Do nothing. |
| } |
| } |