getAllValidScorers() {
+ return mNetworkScoreManager.getAllValidScorers();
+ }
+
+ /**
+ * Obtain the package name of the current active network scorer.
+ *
+ * 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.
+ *
+ *
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);
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/settings/network/NetworkScorerPicker.java b/src/com/android/settings/network/NetworkScorerPicker.java
new file mode 100644
index 00000000000..da9d84f6a66
--- /dev/null
+++ b/src/com/android/settings/network/NetworkScorerPicker.java
@@ -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 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));
+ }
+}
diff --git a/tests/robotests/src/android/net/NetworkScorerAppData.java b/tests/robotests/src/android/net/NetworkScorerAppData.java
new file mode 100644
index 00000000000..1eaa8a7a7f4
--- /dev/null
+++ b/tests/robotests/src/android/net/NetworkScorerAppData.java
@@ -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 CREATOR =
+ new Creator() {
+ @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);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/network/NetworkScorerPickerTest.java b/tests/robotests/src/com/android/settings/network/NetworkScorerPickerTest.java
new file mode 100644
index 00000000000..fef6f851bc9
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/network/NetworkScorerPickerTest.java
@@ -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 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 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;
+ }
+ }
+}