OSDN Git Service

Create settings page for choosing default Network Scorer.
authorStephen Chen <stewchen@google.com>
Thu, 2 Mar 2017 01:10:08 +0000 (17:10 -0800)
committerStephen Chen <stewchen@google.com>
Mon, 6 Mar 2017 22:08:50 +0000 (14:08 -0800)
Bug: 35203304
Bug: 35852687
Test: make RunSettingsRoboTests -j40
Change-Id: Iaef671e0a9e8beb560e1d2c9864f44ef46993a6b

res/values/strings.xml
res/xml/network_scorer_picker_prefs.xml [new file with mode: 0644]
res/xml/wifi_configure_settings.xml
src/com/android/settings/network/NetworkScoreManagerWrapper.java [new file with mode: 0644]
src/com/android/settings/network/NetworkScorerPicker.java [new file with mode: 0644]
tests/robotests/src/android/net/NetworkScorerAppData.java [new file with mode: 0644]
tests/robotests/src/com/android/settings/network/NetworkScorerPickerTest.java [new file with mode: 0644]

index 1e1304e..10bd724 100644 (file)
     <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] -->
diff --git a/res/xml/network_scorer_picker_prefs.xml b/res/xml/network_scorer_picker_prefs.xml
new file mode 100644 (file)
index 0000000..fab69e7
--- /dev/null
@@ -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>
\ No newline at end of file
index bf1dc75..8e51009 100644 (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"
diff --git a/src/com/android/settings/network/NetworkScoreManagerWrapper.java b/src/com/android/settings/network/NetworkScoreManagerWrapper.java
new file mode 100644 (file)
index 0000000..0d35378
--- /dev/null
@@ -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);
+    }
+}
\ 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 (file)
index 0000000..da9d84f
--- /dev/null
@@ -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));
+    }
+}
diff --git a/tests/robotests/src/android/net/NetworkScorerAppData.java b/tests/robotests/src/android/net/NetworkScorerAppData.java
new file mode 100644 (file)
index 0000000..1eaa8a7
--- /dev/null
@@ -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);
+    }
+}
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 (file)
index 0000000..fef6f85
--- /dev/null
@@ -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;
+        }
+    }
+}