<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>
+ <!-- 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] -->
<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] -->
--- /dev/null
+<?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>
\ No newline at end of file
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
android:key="wifi_direct"
android:title="@string/wifi_menu_p2p">
<intent android:targetPackage="com.android.settings"
--- /dev/null
+/*
+ * 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);
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * 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));
+ }
+}
--- /dev/null
+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);
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+ }
+}