Implement WifiNetworkListFragment

1.Scan valid access points for DPP configurator by using WifiTracker.
2.Allow user to add a new network for DPP configurator.

Bug: 118794978
Test: RunSettingsRoboTests
Change-Id: I36504d28dec7d2c9091aa6c35ebe2495045681f7
This commit is contained in:
Johnson Lu
2019-01-10 14:08:06 +08:00
parent c4e447ce71
commit 0adba0aa1b
7 changed files with 543 additions and 2 deletions

View File

@@ -34,7 +34,7 @@
<include layout="@layout/wifi_dpp_fragment_header"/> <include layout="@layout/wifi_dpp_fragment_header"/>
<ListView android:id="@+id/saved_wifi_network_list" <LinearLayout android:id="@+id/wifi_network_list_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/header"/> app:layout_constraintTop_toBottomOf="@+id/header"/>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
-->
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory
android:key="access_points"
android:layout="@layout/preference_category_no_label"/>
</PreferenceScreen>

View File

@@ -25,6 +25,9 @@ import android.view.ViewGroup;
import android.widget.Button; import android.widget.Button;
import android.widget.ListView; import android.widget.ListView;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import com.android.internal.logging.nano.MetricsProto; import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.R; import com.android.settings.R;
@@ -33,6 +36,8 @@ import com.android.settings.R;
* {@code WifiDppConfiguratorActivity} to start with this fragment to choose a saved Wi-Fi network. * {@code WifiDppConfiguratorActivity} to start with this fragment to choose a saved Wi-Fi network.
*/ */
public class WifiDppChooseSavedWifiNetworkFragment extends WifiDppQrCodeBaseFragment { public class WifiDppChooseSavedWifiNetworkFragment extends WifiDppQrCodeBaseFragment {
private static final String TAG_FRAGMENT_WIFI_NETWORK_LIST = "wifi_network_list_fragment";
private ListView mSavedWifiNetworkList; private ListView mSavedWifiNetworkList;
private Button mButtonLeft; private Button mButtonLeft;
private Button mButtonRight; private Button mButtonRight;
@@ -50,6 +55,15 @@ public class WifiDppChooseSavedWifiNetworkFragment extends WifiDppQrCodeBaseFrag
if (actionBar != null) { if (actionBar != null) {
actionBar.hide(); actionBar.hide();
} }
/** Embeded WifiNetworkListFragment as child fragment within
* WifiDppChooseSavedWifiNetworkFragment. */
final FragmentManager fragmentManager = getChildFragmentManager();
final WifiNetworkListFragment fragment = new WifiNetworkListFragment();
final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.wifi_network_list_container, fragment,
TAG_FRAGMENT_WIFI_NETWORK_LIST);
fragmentTransaction.commit();
} }
@Override @Override

View File

@@ -53,7 +53,9 @@ public class WifiDppConfiguratorActivity extends InstrumentedActivity implements
WifiDppQrCodeGeneratorFragment.OnQrCodeGeneratorFragmentAddButtonClickedListener, WifiDppQrCodeGeneratorFragment.OnQrCodeGeneratorFragmentAddButtonClickedListener,
WifiDppQrCodeScannerFragment.OnScanWifiDppSuccessListener, WifiDppQrCodeScannerFragment.OnScanWifiDppSuccessListener,
WifiDppQrCodeScannerFragment.OnScanZxingWifiFormatSuccessListener, WifiDppQrCodeScannerFragment.OnScanZxingWifiFormatSuccessListener,
WifiDppAddDeviceFragment.OnClickChooseDifferentNetworkListener { WifiDppAddDeviceFragment.OnClickChooseDifferentNetworkListener,
WifiNetworkListFragment.OnChooseNetworkListener {
private static final String TAG = "WifiDppConfiguratorActivity"; private static final String TAG = "WifiDppConfiguratorActivity";
public static final String ACTION_CONFIGURATOR_QR_CODE_SCANNER = public static final String ACTION_CONFIGURATOR_QR_CODE_SCANNER =
@@ -329,4 +331,11 @@ public class WifiDppConfiguratorActivity extends InstrumentedActivity implements
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
} }
@Override
public void onChooseNetwork(WifiNetworkConfig wifiNetworkConfig) {
mWifiNetworkConfig = new WifiNetworkConfig(wifiNetworkConfig);
showAddDeviceFragment(/* addToBackStack */ true);
}
} }

View File

@@ -0,0 +1,317 @@
/*
* 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.dpp;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.R;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.wifi.AddNetworkFragment;
import com.android.settingslib.wifi.AccessPoint;
import com.android.settingslib.wifi.AccessPointPreference;
import com.android.settingslib.wifi.WifiTracker;
import com.android.settingslib.wifi.WifiTrackerFactory;
import java.util.List;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
public class WifiNetworkListFragment extends SettingsPreferenceFragment implements
WifiTracker.WifiListener, AccessPoint.AccessPointListener {
private static final String TAG = "WifiNetworkListFragment";
private static final String WIFI_CONFIG_KEY = "wifi_config_key";
private static final String PREF_KEY_EMPTY_WIFI_LIST = "wifi_empty_list";
private static final String PREF_KEY_ACCESS_POINTS = "access_points";
static final int ADD_NETWORK_REQUEST = 1;
private PreferenceCategory mAccessPointsPreferenceCategory;
private AccessPointPreference.UserBadgeCache mUserBadgeCache;
private Preference mAddPreference;
private WifiManager mWifiManager;
private WifiTracker mWifiTracker;
private WifiManager.ActionListener mSaveListener;
// Container Activity must implement this interface
public interface OnChooseNetworkListener {
public void onChooseNetwork(WifiNetworkConfig wifiNetworkConfig);
}
OnChooseNetworkListener mOnChooseNetworkListener;
@Override
public int getMetricsCategory() {
return MetricsProto.MetricsEvent.SETTINGS_WIFI_DPP_CONFIGURATOR;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (!(context instanceof OnChooseNetworkListener)) {
throw new IllegalArgumentException("Invalid context type");
}
mOnChooseNetworkListener = (OnChooseNetworkListener) context;
}
@Override
public void onDetach() {
mOnChooseNetworkListener = null;
super.onDetach();
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mWifiTracker = WifiTrackerFactory.create(getActivity(), this,
getSettingsLifecycle(), /* includeSaved */true, /* includeSaved */ true);
mWifiManager = mWifiTracker.getManager();
mSaveListener = new WifiManager.ActionListener() {
@Override
public void onSuccess() {
// Do nothing.
}
@Override
public void onFailure(int reason) {
Activity activity = getActivity();
if (activity != null) {
Toast.makeText(activity,
R.string.wifi_failed_save_message,
Toast.LENGTH_SHORT).show();
}
}
};
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == ADD_NETWORK_REQUEST) {
if (resultCode == Activity.RESULT_OK) {
handleAddNetworkSubmitEvent(data);
}
mWifiTracker.resumeScanning();
}
}
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.wifi_dpp_network_list);
mAccessPointsPreferenceCategory = (PreferenceCategory) findPreference(
PREF_KEY_ACCESS_POINTS);
mAddPreference = new Preference(getPrefContext());
mAddPreference.setIcon(R.drawable.ic_menu_add);
mAddPreference.setTitle(R.string.wifi_add_network);
mUserBadgeCache = new AccessPointPreference.UserBadgeCache(getPackageManager());
}
/** Called when the state of Wifi has changed. */
@Override
public void onWifiStateChanged(int state) {
final int wifiState = mWifiManager.getWifiState();
switch (wifiState) {
case WifiManager.WIFI_STATE_ENABLED:
updateAccessPointPreferences();
break;
case WifiManager.WIFI_STATE_ENABLING:
case WifiManager.WIFI_STATE_DISABLING:
removeAccessPointPreferences();
break;
}
}
/** Called when the connection state of wifi has changed. */
@Override
public void onConnectedChanged() {
// Do nothing.
}
/**
* Called to indicate the list of AccessPoints has been updated and
* getAccessPoints should be called to get the latest information.
*/
@Override
public void onAccessPointsChanged() {
updateAccessPointPreferences();
}
@Override
public void onAccessPointChanged(final AccessPoint accessPoint) {
Log.d(TAG, "onAccessPointChanged (singular) callback initiated");
View view = getView();
if (view != null) {
view.post(() -> {
final Object tag = accessPoint.getTag();
if (tag != null) {
((AccessPointPreference) tag).refresh();
}
});
}
}
@Override
public void onLevelChanged(AccessPoint accessPoint) {
((AccessPointPreference) accessPoint.getTag()).onLevelChanged();
}
@Override
public boolean onPreferenceTreeClick(Preference preference) {
if (preference instanceof AccessPointPreference) {
final AccessPoint selectedAccessPoint =
((AccessPointPreference) preference).getAccessPoint();
if (selectedAccessPoint == null) {
return false;
}
// Launch WifiDppAddDeviceFragment to start DPP in Configurator-Initiator role.
final WifiConfiguration wifiConfig = selectedAccessPoint.getConfig();
if (wifiConfig == null) {
throw new IllegalArgumentException("Invalid access point");
}
final WifiNetworkConfig networkConfig = WifiNetworkConfig.getValidConfigOrNull(
selectedAccessPoint.getSecurityString(/* concise */ true),
wifiConfig.getPrintableSsid(), wifiConfig.preSharedKey, /* hiddenSsid */ false,
wifiConfig.networkId);
if (mOnChooseNetworkListener != null) {
mOnChooseNetworkListener.onChooseNetwork(networkConfig);
}
} else if (preference == mAddPreference) {
launchAddNetworkFragment();
} else {
return super.onPreferenceTreeClick(preference);
}
return true;
}
private void handleAddNetworkSubmitEvent(Intent data) {
final WifiConfiguration wifiConfiguration = data.getParcelableExtra(WIFI_CONFIG_KEY);
if (wifiConfiguration != null) {
mWifiManager.save(wifiConfiguration, mSaveListener);
}
}
private boolean isValidForDppConfiguration(AccessPoint accessPoint) {
final int security = accessPoint.getSecurity();
// DPP 1.0 only support SAE and PSK.
if (!(security == AccessPoint.SECURITY_PSK || security == AccessPoint.SECURITY_SAE)) {
return false;
}
// Can only use saved network for DPP configuration. For ephemeral connections networkId
// is invalid.
if (!accessPoint.isSaved()) {
return false;
}
// Ignore access points that are out of range.
if (!accessPoint.isReachable()) {
return false;
}
return true;
}
private void launchAddNetworkFragment() {
new SubSettingLauncher(getContext())
.setTitleRes(R.string.wifi_add_network)
.setDestination(AddNetworkFragment.class.getName())
.setSourceMetricsCategory(getMetricsCategory())
.setResultListener(this, ADD_NETWORK_REQUEST)
.launch();
}
private void removeAccessPointPreferences() {
mAccessPointsPreferenceCategory.removeAll();
mAccessPointsPreferenceCategory.setVisible(false);
}
private void updateAccessPointPreferences() {
// in case state has changed
if (!mWifiManager.isWifiEnabled()) {
return;
}
// AccessPoints are sorted by the WifiTracker
final List<AccessPoint> accessPoints = mWifiTracker.getAccessPoints();
boolean hasAvailableAccessPoints = false;
mAccessPointsPreferenceCategory.setVisible(true);
cacheRemoveAllPrefs(mAccessPointsPreferenceCategory);
int index = 0;
for (; index < accessPoints.size(); index++) {
AccessPoint accessPoint = accessPoints.get(index);
// Check if this access point is valid for DPP.
if (isValidForDppConfiguration(accessPoint)) {
final String key = accessPoint.getKey();
hasAvailableAccessPoints = true;
final AccessPointPreference pref = (AccessPointPreference) getCachedPreference(key);
if (pref != null) {
pref.setOrder(index);
continue;
}
final AccessPointPreference preference = createAccessPointPreference(accessPoint);
preference.setKey(key);
preference.setOrder(index);
mAccessPointsPreferenceCategory.addPreference(preference);
accessPoint.setListener(this);
preference.refresh();
}
}
removeCachedPrefs(mAccessPointsPreferenceCategory);
mAddPreference.setOrder(index);
mAccessPointsPreferenceCategory.addPreference(mAddPreference);
if (!hasAvailableAccessPoints) {
final Preference pref = new Preference(getPrefContext());
pref.setSelectable(false);
pref.setSummary(R.string.wifi_empty_list_wifi_on);
pref.setOrder(index++);
pref.setKey(PREF_KEY_EMPTY_WIFI_LIST);
mAccessPointsPreferenceCategory.addPreference(pref);
}
}
private AccessPointPreference createAccessPointPreference(AccessPoint accessPoint) {
return new AccessPointPreference(accessPoint, getPrefContext(), mUserBadgeCache,
R.drawable.ic_wifi_signal_0, /* forSavedNetworks */ false);
}
}

View File

@@ -80,3 +80,4 @@ com.android.settings.wifi.details.WifiNetworkDetailsFragment
com.android.settings.wifi.p2p.WifiP2pSettings com.android.settings.wifi.p2p.WifiP2pSettings
com.android.settings.wifi.savedaccesspoints.SavedAccessPointsWifiSettings com.android.settings.wifi.savedaccesspoints.SavedAccessPointsWifiSettings
com.android.settings.wifi.WifiInfo com.android.settings.wifi.WifiInfo
com.android.settings.wifi.dpp.WifiNetworkListFragment

View File

@@ -0,0 +1,176 @@
/*
* 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.dpp;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.verify;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiSsid;
import android.provider.Settings;
import androidx.fragment.app.Fragment;
import androidx.test.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
import com.android.settingslib.wifi.AccessPoint;
import com.android.settingslib.wifi.WifiTracker;
import com.android.settingslib.wifi.WifiTracker.WifiListener;
import com.android.settingslib.wifi.WifiTrackerFactory;
import com.google.common.collect.Lists;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.List;
@RunWith(AndroidJUnit4.class)
public class WifiNetworkListFragmentTest {
private static final String TEST_SSID = "\"Test Ssid\"";
private static final String TEST_UNQUOTED_SSID = "Test Ssid";
private static final String TEST_BSSID = "0a:08:5c:67:89:00";
private static final int TEST_RSSI = 123;
private static final int TEST_NETWORK_ID = 1;
private static final String TEST_DPP_URL = "DPP:C:81/1;I:DPP_TESTER;K:"
+ "MDkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDIgADUysZxnwRFGQR7Fepipjl84TG/dQR07es91iOB3PkPOk=;;";
// Keys used to lookup resources by name (see the resourceId/resourceString helper methods).
private static final String ID = "id";
private static final String STRING = "string";
private static final String WIFI_DISPLAY_STATUS_CONNECTED = "wifi_display_status_connected";
@Mock
private WifiTracker mWifiTracker;
@Mock
private WifiManager mWifiManager;
private WifiNetworkListFragment mWifiNetworkListFragment;
private Context mContext;
@Rule
public ActivityTestRule<WifiDppConfiguratorActivity> mActivityRule = new ActivityTestRule<>(
WifiDppConfiguratorActivity.class, true);
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = InstrumentationRegistry.getTargetContext();
WifiTrackerFactory.setTestingWifiTracker(mWifiTracker);
when(mWifiTracker.getManager()).thenReturn(mWifiManager);
}
private void callOnWifiStateChanged(int state) {
mActivityRule.getActivity().getMainThreadHandler()
.post(() -> mWifiNetworkListFragment.onWifiStateChanged(state));
}
/** Launch the activity via an Intent with a String extra. */
private void launchActivity(String extraName, String extraValue) {
final Intent intent = new Intent(
WifiDppConfiguratorActivity.ACTION_PROCESS_WIFI_DPP_QR_CODE);
if (extraName != null && extraValue != null) {
intent.putExtra(extraName, extraValue);
}
mActivityRule.launchActivity(intent);
verify(mWifiTracker).getManager();
List<Fragment> fragments =
mActivityRule.getActivity().getSupportFragmentManager().getFragments();
assertThat(fragments.size()).isEqualTo(1);
List<Fragment> childFragments = fragments.get(0).getChildFragmentManager().getFragments();
assertThat(childFragments.size()).isEqualTo(1);
mWifiNetworkListFragment = (WifiNetworkListFragment) childFragments.get(0);
assertThat(mWifiNetworkListFragment).isNotNull();
}
private int resourceId(String type, String name) {
return mContext.getResources().getIdentifier(name, type, mContext.getPackageName());
}
/** Similar to {@link #resourceId}, but for accessing R.string.<name> values. */
private String resourceString(String name) {
return mContext.getResources().getString(resourceId(STRING, name));
}
private void setWifiState(int wifiState) {
when(mWifiManager.getWifiState()).thenReturn(wifiState);
when(mWifiManager.isWifiEnabled()).thenReturn(wifiState == WifiManager.WIFI_STATE_ENABLED);
}
private void setupConnectedAccessPoint() {
final WifiConfiguration config = new WifiConfiguration();
config.SSID = TEST_SSID;
config.BSSID = TEST_BSSID;
config.networkId = TEST_NETWORK_ID;
config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
final WifiInfo wifiInfo = new WifiInfo();
wifiInfo.setSSID(WifiSsid.createFromAsciiEncoded(TEST_UNQUOTED_SSID));
wifiInfo.setBSSID(TEST_BSSID);
wifiInfo.setRssi(TEST_RSSI);
wifiInfo.setNetworkId(TEST_NETWORK_ID);
final NetworkInfo networkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, null, null);
networkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, null, null);
final AccessPoint accessPoint = new AccessPoint(mContext, config);
accessPoint.update(config, wifiInfo, networkInfo);
assertThat(accessPoint.getSsidStr()).isEqualTo(TEST_UNQUOTED_SSID);
assertThat(accessPoint.getBssid()).isEqualTo(TEST_BSSID);
assertThat(accessPoint.getNetworkInfo()).isNotNull();
assertThat(accessPoint.isActive()).isTrue();
assertThat(accessPoint.getSettingsSummary()).isEqualTo(
resourceString(WIFI_DISPLAY_STATUS_CONNECTED));
when(mWifiTracker.getAccessPoints()).thenReturn(
Lists.asList(accessPoint, new AccessPoint[]{}));
}
@Test
public void onConnected_shouldSeeConnectedMessage() throws Exception {
setWifiState(WifiManager.WIFI_STATE_ENABLED);
setupConnectedAccessPoint();
when(mWifiTracker.isConnected()).thenReturn(true);
launchActivity(WifiDppUtils.EXTRA_QR_CODE, TEST_DPP_URL);
callOnWifiStateChanged(WifiManager.WIFI_STATE_ENABLED);
onView(withText(resourceString(WIFI_DISPLAY_STATUS_CONNECTED))).check(
matches(isDisplayed()));
}
}