Create settings page for choosing default Network Scorer.

Bug: 35203304
Bug: 35852687
Test: make RunSettingsRoboTests -j40
Change-Id: Iaef671e0a9e8beb560e1d2c9864f44ef46993a6b
This commit is contained in:
Stephen Chen
2017-03-01 17:10:08 -08:00
parent 8e667cf2d4
commit 9347c95c39
7 changed files with 522 additions and 0 deletions

View File

@@ -3013,6 +3013,10 @@
<string name="sms_change_default_dialog_text" translatable="true">Use <xliff:g id="new_app">%1$s</xliff:g> instead of <xliff:g id="current_app">%2$s</xliff:g> as your SMS app?</string> <string name="sms_change_default_dialog_text" translatable="true">Use <xliff:g id="new_app">%1$s</xliff:g> instead of <xliff:g id="current_app">%2$s</xliff:g> as your SMS app?</string>
<string name="sms_change_default_no_previous_dialog_text" translatable="true">Use <xliff:g id="new_app">%s</xliff:g> as your SMS app?</string> <string name="sms_change_default_no_previous_dialog_text" translatable="true">Use <xliff:g id="new_app">%s</xliff:g> as your SMS app?</string>
<!-- Network Scorer Picker title [CHAR LIMIT=40]-->
<string name="network_scorer_picker_title">Network Scorer</string>
<string name="network_scorer_picker_none_preference">None</string>
<!-- Wifi Assistant change wi-fi assistant title. [CHAR LIMIT=40] --> <!-- Wifi Assistant change wi-fi assistant title. [CHAR LIMIT=40] -->
<string name="network_scorer_change_active_dialog_title">Change Wi\u2011Fi assistant?</string> <string name="network_scorer_change_active_dialog_title">Change Wi\u2011Fi assistant?</string>
<!-- Wifi Assistant request message. This message asks the user if it is okay for an app to become the Wifi Assistant instead of the current Wifi Assistant app. [CHAR LIMIT=100] --> <!-- Wifi Assistant request message. This message asks the user if it is okay for an app to become the Wifi Assistant instead of the current Wifi Assistant app. [CHAR LIMIT=100] -->

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2017 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"
android:title="@string/network_scorer_picker_title">
</PreferenceScreen>

View File

@@ -58,6 +58,11 @@
android:fragment="com.android.settings.WifiCallingSettings" android:fragment="com.android.settings.WifiCallingSettings"
settings:keywords="@string/keywords_wifi_calling"/> settings:keywords="@string/keywords_wifi_calling"/>
<Preference
android:key="network_scorer_picker"
android:title="@string/network_scorer_picker_title"
android:fragment="com.android.settings.network.NetworkScorerPicker"/>
<Preference <Preference
android:key="wifi_direct" android:key="wifi_direct"
android:title="@string/wifi_menu_p2p"> android:title="@string/wifi_menu_p2p">

View File

@@ -0,0 +1,73 @@
/*
* Copyright (C) 2017 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.network;
import android.annotation.Nullable;
import android.net.NetworkScoreManager;
import android.net.NetworkScorerAppData;
import java.util.List;
/**
* Wrapper around {@link NetworkScoreManager} to facilitate unit testing.
*
* TODO: delete this class once robolectric supports Android O
*/
public class NetworkScoreManagerWrapper {
private final NetworkScoreManager mNetworkScoreManager;
public NetworkScoreManagerWrapper(NetworkScoreManager networkScoreManager) {
mNetworkScoreManager = networkScoreManager;
}
/**
* Returns the list of available scorer apps. The list will be empty if there are
* no valid scorers.
*/
public List<NetworkScorerAppData> getAllValidScorers() {
return mNetworkScoreManager.getAllValidScorers();
}
/**
* Obtain the package name of the current active network scorer.
*
* <p>At any time, only one scorer application will receive {@link #ACTION_SCORE_NETWORKS}
* broadcasts and be allowed to call {@link #updateScores}. Applications may use this method to
* determine the current scorer and offer the user the ability to select a different scorer via
* the {@link #ACTION_CHANGE_ACTIVE} intent.
* @return the full package name of the current active scorer, or null if there is no active
* scorer.
*/
@Nullable
public String getActiveScorerPackage() {
return mNetworkScoreManager.getActiveScorerPackage();
}
/**
* Set the active scorer to a new package and clear existing scores.
*
* <p>Should never be called directly without obtaining user consent. This can be done by using
* the {@link #ACTION_CHANGE_ACTIVE} broadcast, or using a custom configuration activity.
*
* @return true if the operation succeeded, or false if the new package is not a valid scorer.
* @throws SecurityException if the caller is not a system process or does not hold the
* {@link android.Manifest.permission#REQUEST_NETWORK_SCORES} permission
*/
public boolean setActiveScorer(String packageName) throws SecurityException {
return mNetworkScoreManager.setActiveScorer(packageName);
}
}

View File

@@ -0,0 +1,135 @@
/*
* Copyright (C) 2017 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.network;
import android.content.Context;
import android.net.NetworkScoreManager;
import android.net.NetworkScorerAppData;
import android.os.Bundle;
import android.support.annotation.VisibleForTesting;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceScreen;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.android.settings.R;
import com.android.settings.core.InstrumentedPreferenceFragment;
import com.android.settings.core.instrumentation.Instrumentable;
import com.android.settings.widget.RadioButtonPreference;
import java.util.List;
/**
* Fragment for choosing default network scorer.
*/
public class NetworkScorerPicker extends InstrumentedPreferenceFragment implements
RadioButtonPreference.OnClickListener {
private NetworkScoreManagerWrapper mNetworkScoreManager;
@Override
public int getMetricsCategory() {
//TODO(35854268): Add logging.
return Instrumentable.METRICS_CATEGORY_UNKNOWN;
}
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
super.onCreatePreferences(savedInstanceState, rootKey);
addPreferencesFromResource(R.xml.network_scorer_picker_prefs);
updateCandidates();
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
mNetworkScoreManager = createNetworkScorerManagerWrapper(context);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View view = super.onCreateView(inflater, container, savedInstanceState);
// this is needed so the back button goes back to previous fragment
setHasOptionsMenu(true);
return view;
}
@VisibleForTesting
public void updateCandidates() {
final PreferenceScreen screen = getPreferenceScreen();
screen.removeAll();
final List<NetworkScorerAppData> scorers = mNetworkScoreManager.getAllValidScorers();
if (scorers.isEmpty()) {
final RadioButtonPreference nonePref = new RadioButtonPreference(getPrefContext());
nonePref.setTitle(R.string.network_scorer_picker_none_preference);
nonePref.setChecked(true);
screen.addPreference(nonePref);
return;
}
final String defaultAppKey = getActiveScorerPackage();
final int numScorers = scorers.size();
for (int i = 0; i < numScorers; i++) {
final RadioButtonPreference pref = new RadioButtonPreference(getPrefContext());
final NetworkScorerAppData appData = scorers.get(i);
final String appKey = appData.getRecommendationServicePackageName();
pref.setTitle(appData.getRecommendationServiceLabel());
pref.setKey(appKey);
pref.setChecked(TextUtils.equals(defaultAppKey, appKey));
pref.setOnClickListener(this);
screen.addPreference(pref);
}
}
private String getActiveScorerPackage() {
return mNetworkScoreManager.getActiveScorerPackage();
}
private boolean setActiveScorer(String key) {
if (!TextUtils.equals(key, getActiveScorerPackage())) {
return mNetworkScoreManager.setActiveScorer(key);
}
return false;
}
@Override
public void onRadioButtonClicked(RadioButtonPreference selected) {
final String selectedKey = selected.getKey();
final boolean success = setActiveScorer(selectedKey);
if (success) {
updateCheckedState(selectedKey);
}
}
private void updateCheckedState(String selectedKey) {
final PreferenceScreen screen = getPreferenceScreen();
final int count = screen.getPreferenceCount();
for (int i = 0; i < count; i++) {
final Preference pref = screen.getPreference(i);
if (pref instanceof RadioButtonPreference) {
final RadioButtonPreference radioPref = (RadioButtonPreference) pref;
radioPref.setChecked(TextUtils.equals(pref.getKey(), selectedKey));
}
}
}
@VisibleForTesting
NetworkScoreManagerWrapper createNetworkScorerManagerWrapper(Context context) {
return new NetworkScoreManagerWrapper(context.getSystemService(NetworkScoreManager.class));
}
}

View File

@@ -0,0 +1,112 @@
package android.net;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.Objects;
/**
* Holds metadata about a discovered network scorer/recommendation application.
*
* TODO: delete this class once robolectric supports Android O
*/
public final class NetworkScorerAppData implements Parcelable {
/** UID of the scorer app. */
public final int packageUid;
private final ComponentName mRecommendationService;
/** User visible label in Settings for the recommendation service. */
private final String mRecommendationServiceLabel;
/**
* The {@link ComponentName} of the Activity to start before enabling the "connect to open
* wifi networks automatically" feature.
*/
private final ComponentName mEnableUseOpenWifiActivity;
public NetworkScorerAppData(int packageUid, ComponentName recommendationServiceComp,
String recommendationServiceLabel, ComponentName enableUseOpenWifiActivity) {
this.packageUid = packageUid;
this.mRecommendationService = recommendationServiceComp;
this.mRecommendationServiceLabel = recommendationServiceLabel;
this.mEnableUseOpenWifiActivity = enableUseOpenWifiActivity;
}
protected NetworkScorerAppData(Parcel in) {
packageUid = in.readInt();
mRecommendationService = ComponentName.readFromParcel(in);
mRecommendationServiceLabel = in.readString();
mEnableUseOpenWifiActivity = ComponentName.readFromParcel(in);
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(packageUid);
ComponentName.writeToParcel(mRecommendationService, dest);
dest.writeString(mRecommendationServiceLabel);
ComponentName.writeToParcel(mEnableUseOpenWifiActivity, dest);
}
@Override
public int describeContents() {
return 0;
}
public static final Creator<NetworkScorerAppData> CREATOR =
new Creator<NetworkScorerAppData>() {
@Override
public NetworkScorerAppData createFromParcel(Parcel in) {
return new NetworkScorerAppData(in);
}
@Override
public NetworkScorerAppData[] newArray(int size) {
return new NetworkScorerAppData[size];
}
};
public String getRecommendationServicePackageName() {
return mRecommendationService.getPackageName();
}
public ComponentName getRecommendationServiceComponent() {
return mRecommendationService;
}
@Nullable
public ComponentName getEnableUseOpenWifiActivity() {
return mEnableUseOpenWifiActivity;
}
@Nullable
public String getRecommendationServiceLabel() {
return mRecommendationServiceLabel;
}
@Override
public String toString() {
return "NetworkScorerAppData{" +
"packageUid=" + packageUid +
", mRecommendationService=" + mRecommendationService +
", mRecommendationServiceLabel=" + mRecommendationServiceLabel +
", mEnableUseOpenWifiActivity=" + mEnableUseOpenWifiActivity +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
NetworkScorerAppData that = (NetworkScorerAppData) o;
return packageUid == that.packageUid &&
Objects.equals(mRecommendationService, that.mRecommendationService) &&
Objects.equals(mRecommendationServiceLabel, that.mRecommendationServiceLabel) &&
Objects.equals(mEnableUseOpenWifiActivity, that.mEnableUseOpenWifiActivity);
}
@Override
public int hashCode() {
return Objects.hash(packageUid, mRecommendationService, mRecommendationServiceLabel,
mEnableUseOpenWifiActivity);
}
}

View File

@@ -0,0 +1,172 @@
/*
* Copyright (C) 2017 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.network;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.ComponentName;
import android.content.Context;
import android.net.NetworkScorerAppData;
import android.support.v7.preference.PreferenceManager;
import android.support.v7.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
import com.android.settings.widget.RadioButtonPreference;
import com.google.android.collect.Lists;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import java.util.ArrayList;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public class NetworkScorerPickerTest {
private static final String TEST_SCORER_PACKAGE_1 = "Test Package 1";
private static final String TEST_SCORER_CLASS_1 = "Test Class 1";
private static final String TEST_SCORER_LABEL_1 = "Test Label 1";
private static final String TEST_SCORER_PACKAGE_2 = "Test Package 2";
private Context mContext;
@Mock
private NetworkScoreManagerWrapper mNetworkScoreManager;
@Mock
private PreferenceScreen mPreferenceScreen;
private TestFragment mFragment;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
mFragment = new TestFragment(mContext, mPreferenceScreen, mNetworkScoreManager);
mFragment.onAttach(mContext);
}
@Test
public void testOnRadioButtonClicked_success() {
RadioButtonPreference pref = new RadioButtonPreference(mContext);
pref.setKey(TEST_SCORER_PACKAGE_1);
when(mPreferenceScreen.getPreference(anyInt())).thenReturn(pref);
when(mPreferenceScreen.getPreferenceCount()).thenReturn(1);
when(mNetworkScoreManager.setActiveScorer(TEST_SCORER_PACKAGE_1)).thenReturn(true);
when(mNetworkScoreManager.getActiveScorerPackage()).thenReturn(TEST_SCORER_PACKAGE_2);
mFragment.onRadioButtonClicked(pref);
verify(mNetworkScoreManager).setActiveScorer(TEST_SCORER_PACKAGE_1);
assertThat(pref.isChecked()).isTrue();
}
@Test
public void testOnRadioButtonClicked_currentScorer_doNothing() {
RadioButtonPreference pref = new RadioButtonPreference(mContext);
pref.setKey(TEST_SCORER_PACKAGE_1);
pref.setChecked(true);
when(mPreferenceScreen.getPreference(anyInt())).thenReturn(pref);
when(mPreferenceScreen.getPreferenceCount()).thenReturn(1);
when(mNetworkScoreManager.setActiveScorer(TEST_SCORER_PACKAGE_1)).thenReturn(true);
when(mNetworkScoreManager.getActiveScorerPackage()).thenReturn(TEST_SCORER_PACKAGE_1);
mFragment.onRadioButtonClicked(pref);
verify(mNetworkScoreManager, never()).setActiveScorer(any());
assertThat(pref.isChecked()).isTrue();
}
@Test
public void testUpdateCandidates_noValidScorers_nonePreference() {
when(mNetworkScoreManager.getAllValidScorers()).thenReturn(new ArrayList<>());
ArgumentCaptor<RadioButtonPreference> arg =
ArgumentCaptor.forClass(RadioButtonPreference.class);
mFragment.updateCandidates();
verify(mPreferenceScreen).addPreference(arg.capture());
assertThat(arg.getValue().getTitle()).isEqualTo(
mContext.getString(R.string.network_scorer_picker_none_preference));
}
@Test
public void testUpdateCandidates_validScorer() {
ComponentName scorer = new ComponentName(TEST_SCORER_PACKAGE_1, TEST_SCORER_CLASS_1);
NetworkScorerAppData scorerAppData = new NetworkScorerAppData(
0, scorer, TEST_SCORER_LABEL_1, null /* enableUseOpenWifiActivity */);
when(mNetworkScoreManager.getAllValidScorers()).thenReturn(
Lists.newArrayList(scorerAppData));
when(mNetworkScoreManager.getActiveScorerPackage()).thenReturn(TEST_SCORER_PACKAGE_1);
ArgumentCaptor<RadioButtonPreference> arg =
ArgumentCaptor.forClass(RadioButtonPreference.class);
mFragment.updateCandidates();
verify(mPreferenceScreen).addPreference(arg.capture());
RadioButtonPreference pref = arg.getValue();
assertThat(pref.getTitle()).isEqualTo(TEST_SCORER_LABEL_1);
assertThat(pref.isChecked()).isTrue();
}
public static class TestFragment extends NetworkScorerPicker {
private final Context mContext;
private final PreferenceScreen mScreen;
private final PreferenceManager mPrefManager;
private final NetworkScoreManagerWrapper mNetworkScoreManagerWrapper;
public TestFragment(Context context, PreferenceScreen preferenceScreen,
NetworkScoreManagerWrapper networkScoreManagerWrapper) {
mContext = context;
mScreen = preferenceScreen;
mNetworkScoreManagerWrapper = networkScoreManagerWrapper;
mPrefManager = mock(PreferenceManager.class);
when(mPrefManager.getContext()).thenReturn(context);
}
@Override
public Context getContext() {
return mContext;
}
@Override
public PreferenceManager getPreferenceManager() {
return mPrefManager;
}
@Override
public PreferenceScreen getPreferenceScreen() {
return mScreen;
}
@Override
NetworkScoreManagerWrapper createNetworkScorerManagerWrapper(Context context) {
return mNetworkScoreManagerWrapper;
}
}
}