From 74cb72433a5b13601f437f981c769f2766914545 Mon Sep 17 00:00:00 2001 From: Antony Sargent Date: Fri, 14 Dec 2018 12:45:04 -0800 Subject: [PATCH] Add wifi connection info to the multi-network header The Network & internet page will have a dynamic header at the top when users have more than one mobile subscription, showing information about connectivity. This CL adds a preference to this header when there is a wifi connection, showing the same information as on the wifi-page (connection strength, speed rating if available, etc.). Bug: 116349402 Test: make RunSettingsRoboTests Change-Id: Ia80d6e236a4996b501372ac4cd8e46ba6c5f8841 --- .../network/MultiNetworkHeaderController.java | 18 ++- .../wifi/WifiConnectionPreferenceController.java | 177 +++++++++++++++++++++ .../network/MultiNetworkHeaderControllerTest.java | 25 ++- .../WifiConnectionPreferenceControllerTest.java | 161 +++++++++++++++++++ 4 files changed, 374 insertions(+), 7 deletions(-) create mode 100644 src/com/android/settings/wifi/WifiConnectionPreferenceController.java create mode 100644 tests/robotests/src/com/android/settings/network/WifiConnectionPreferenceControllerTest.java diff --git a/src/com/android/settings/network/MultiNetworkHeaderController.java b/src/com/android/settings/network/MultiNetworkHeaderController.java index 881aaa2ba9..8860c477e9 100644 --- a/src/com/android/settings/network/MultiNetworkHeaderController.java +++ b/src/com/android/settings/network/MultiNetworkHeaderController.java @@ -18,7 +18,9 @@ package com.android.settings.network; import android.content.Context; +import com.android.internal.logging.nano.MetricsProto; import com.android.settings.core.BasePreferenceController; +import com.android.settings.wifi.WifiConnectionPreferenceController; import com.android.settingslib.core.lifecycle.Lifecycle; import androidx.annotation.VisibleForTesting; @@ -29,9 +31,11 @@ import androidx.preference.PreferenceScreen; // are two or more active mobile subscriptions. It shows an overview of available network // connections with an entry for wifi (if connected) and an entry for each subscription. public class MultiNetworkHeaderController extends BasePreferenceController implements + WifiConnectionPreferenceController.UpdateListener, SubscriptionsPreferenceController.UpdateListener { public static final String TAG = "MultiNetworkHdrCtrl"; + private WifiConnectionPreferenceController mWifiController; private SubscriptionsPreferenceController mSubscriptionsController; private PreferenceCategory mPreferenceCategory; @@ -40,13 +44,22 @@ public class MultiNetworkHeaderController extends BasePreferenceController imple } public void init(Lifecycle lifecycle) { + mWifiController = createWifiController(lifecycle); mSubscriptionsController = createSubscriptionsController(lifecycle); - // TODO(asargent) - add in a controller for showing wifi status here + } + + @VisibleForTesting + WifiConnectionPreferenceController createWifiController(Lifecycle lifecycle) { + final int prefOrder = 0; + return new WifiConnectionPreferenceController(mContext, lifecycle, this, mPreferenceKey, + prefOrder, MetricsProto.MetricsEvent.SETTINGS_NETWORK_CATEGORY); } @VisibleForTesting SubscriptionsPreferenceController createSubscriptionsController(Lifecycle lifecycle) { - return new SubscriptionsPreferenceController(mContext, lifecycle, this, mPreferenceKey, 10); + final int prefStartOrder = 10; + return new SubscriptionsPreferenceController(mContext, lifecycle, this, mPreferenceKey, + prefStartOrder); } @Override @@ -54,6 +67,7 @@ public class MultiNetworkHeaderController extends BasePreferenceController imple super.displayPreference(screen); mPreferenceCategory = (PreferenceCategory) screen.findPreference(mPreferenceKey); mPreferenceCategory.setVisible(isAvailable()); + mWifiController.displayPreference(screen); mSubscriptionsController.displayPreference(screen); } diff --git a/src/com/android/settings/wifi/WifiConnectionPreferenceController.java b/src/com/android/settings/wifi/WifiConnectionPreferenceController.java new file mode 100644 index 0000000000..b73bce981c --- /dev/null +++ b/src/com/android/settings/wifi/WifiConnectionPreferenceController.java @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2018 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; + +import android.content.Context; +import android.os.Bundle; + +import com.android.settings.R; +import com.android.settings.core.SubSettingLauncher; +import com.android.settings.wifi.details.WifiNetworkDetailsFragment; +import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settingslib.core.lifecycle.Lifecycle; +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 androidx.preference.PreferenceGroup; +import androidx.preference.PreferenceScreen; + +/** + * This places a preference into a PreferenceGroup owned by some parent + * controller class when there is a wifi connection present. + */ +public class WifiConnectionPreferenceController extends AbstractPreferenceController implements + WifiTracker.WifiListener { + + private static final String TAG = "WifiConnPrefCtrl"; + + private static final String KEY = "active_wifi_connection"; + + private UpdateListener mUpdateListener; + private Context mPrefContext; + private String mPreferenceGroupKey; + private PreferenceGroup mPreferenceGroup; + private WifiTracker mWifiTracker; + private AccessPointPreference mPreference; + private AccessPointPreference.UserBadgeCache mBadgeCache; + private int order; + private int mMetricsCategory; + + /** + * Used to notify a parent controller that this controller has changed in availability, or has + * updated the content in the preference that it manages. + */ + public interface UpdateListener { + void onChildrenUpdated(); + } + + /** + * @param context the context for the UI where we're placing the preference + * @param lifecycle for listening to lifecycle events for the UI + * @param updateListener for notifying a parent controller of changes + * @param preferenceGroupKey the key to use to lookup the PreferenceGroup where this controller + * will add its preference + * @param order the order that the preference added by this controller should use - + * useful when this preference needs to be ordered in a specific way + * relative to others in the PreferenceGroup + * @param metricsCategory - the category to use as the source when handling the click on the + * pref to go to the wifi connection detail page + */ + public WifiConnectionPreferenceController(Context context, Lifecycle lifecycle, + UpdateListener updateListener, String preferenceGroupKey, int order, + int metricsCategory) { + super(context); + mUpdateListener = updateListener; + mPreferenceGroupKey = preferenceGroupKey; + mWifiTracker = WifiTrackerFactory.create(context, this, lifecycle, true /* includeSaved */, + true /* includeScans */); + this.order = order; + mMetricsCategory = metricsCategory; + mBadgeCache = new AccessPointPreference.UserBadgeCache(context.getPackageManager()); + } + + @Override + public boolean isAvailable() { + return mWifiTracker.isConnected() && getCurrentAccessPoint() != null; + } + + @Override + public String getPreferenceKey() { + return KEY; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPreferenceGroup = (PreferenceGroup) screen.findPreference(mPreferenceGroupKey); + mPrefContext = screen.getContext(); + update(); + } + + private AccessPoint getCurrentAccessPoint() { + for (AccessPoint accessPoint : mWifiTracker.getAccessPoints()) { + if (accessPoint.isActive()) { + return accessPoint; + } + } + return null; + } + + private void updatePreference(AccessPoint accessPoint) { + if (mPreference != null) { + mPreferenceGroup.removePreference(mPreference); + mPreference = null; + } + if (accessPoint == null) { + return; + } + if (mPrefContext != null) { + mPreference = new AccessPointPreference(accessPoint, mPrefContext, mBadgeCache, + R.drawable.ic_wifi_signal_0, false /* forSavedNetworks */); + mPreference.setKey(KEY); + mPreference.refresh(); + mPreference.setOrder(order); + + mPreference.setOnPreferenceClickListener(pref -> { + Bundle args = new Bundle(); + mPreference.getAccessPoint().saveWifiState(args); + new SubSettingLauncher(mPrefContext) + .setTitleRes(R.string.pref_title_network_details) + .setDestination(WifiNetworkDetailsFragment.class.getName()) + .setArguments(args) + .setSourceMetricsCategory(mMetricsCategory) + .launch(); + return true; + }); + mPreferenceGroup.addPreference(mPreference); + } + } + + private void update() { + AccessPoint connectedAccessPoint = null; + if (mWifiTracker.isConnected()) { + connectedAccessPoint = getCurrentAccessPoint(); + } + if (connectedAccessPoint == null) { + updatePreference(null); + } else { + if (mPreference == null || !mPreference.getAccessPoint().equals(connectedAccessPoint)) { + updatePreference(connectedAccessPoint); + } else if (mPreference != null) { + mPreference.refresh(); + } + } + mUpdateListener.onChildrenUpdated(); + } + + @Override + public void onWifiStateChanged(int state) { + update(); + } + + @Override + public void onConnectedChanged() { + update(); + } + + @Override + public void onAccessPointsChanged() { + update(); + } +} diff --git a/tests/robotests/src/com/android/settings/network/MultiNetworkHeaderControllerTest.java b/tests/robotests/src/com/android/settings/network/MultiNetworkHeaderControllerTest.java index fbd78672b3..b4ebcc4be1 100644 --- a/tests/robotests/src/com/android/settings/network/MultiNetworkHeaderControllerTest.java +++ b/tests/robotests/src/com/android/settings/network/MultiNetworkHeaderControllerTest.java @@ -29,6 +29,7 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.telephony.SubscriptionManager; +import com.android.settings.wifi.WifiConnectionPreferenceController; import com.android.settingslib.core.lifecycle.Lifecycle; import org.junit.Before; @@ -55,6 +56,8 @@ public class MultiNetworkHeaderControllerTest { @Mock private PreferenceCategory mPreferenceCategory; @Mock + private WifiConnectionPreferenceController mWifiController; + @Mock private SubscriptionsPreferenceController mSubscriptionsController; @Mock private SubscriptionManager mSubscriptionManager; @@ -74,6 +77,7 @@ public class MultiNetworkHeaderControllerTest { when(mPreferenceScreen.findPreference(eq(KEY_HEADER))).thenReturn(mPreferenceCategory); mHeaderController = spy(new MultiNetworkHeaderController(mContext, KEY_HEADER)); + doReturn(mWifiController).when(mHeaderController).createWifiController(mLifecycle); doReturn(mSubscriptionsController).when(mHeaderController).createSubscriptionsController( mLifecycle); } @@ -85,8 +89,9 @@ public class MultiNetworkHeaderControllerTest { // When calling displayPreference, the header itself should only be visible if the // subscriptions controller says it is available. This is a helper for test cases of this logic. - private void displayPreferenceTest(boolean subscriptionsAvailable, + private void displayPreferenceTest(boolean wifiAvailable, boolean subscriptionsAvailable, boolean setVisibleExpectedValue) { + when(mWifiController.isAvailable()).thenReturn(wifiAvailable); when(mSubscriptionsController.isAvailable()).thenReturn(subscriptionsAvailable); mHeaderController.init(mLifecycle); @@ -96,13 +101,23 @@ public class MultiNetworkHeaderControllerTest { } @Test - public void displayPreference_subscriptionsNotAvailable_categoryIsNotVisible() { - displayPreferenceTest(false, false); + public void displayPreference_bothNotAvailable_categoryIsNotVisible() { + displayPreferenceTest(false, false, false); + } + + @Test + public void displayPreference_wifiAvailableButNotSubscriptions_categoryIsNotVisible() { + displayPreferenceTest(true, false, false); + } + + @Test + public void displayPreference_subscriptionsAvailableButNotWifi_categoryIsVisible() { + displayPreferenceTest(false, true, true); } @Test - public void displayPreference_subscriptionsAvailable_categoryIsVisible() { - displayPreferenceTest(true, true); + public void displayPreference_bothAvailable_categoryIsVisible() { + displayPreferenceTest(true, true, true); } @Test diff --git a/tests/robotests/src/com/android/settings/network/WifiConnectionPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/network/WifiConnectionPreferenceControllerTest.java new file mode 100644 index 0000000000..703731858c --- /dev/null +++ b/tests/robotests/src/com/android/settings/network/WifiConnectionPreferenceControllerTest.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2018 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.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; + +import com.android.settings.wifi.WifiConnectionPreferenceController; +import com.android.settingslib.core.lifecycle.Lifecycle; +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 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.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +import java.util.ArrayList; +import java.util.Arrays; + +import androidx.lifecycle.LifecycleOwner; +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceScreen; + +@RunWith(RobolectricTestRunner.class) +public class WifiConnectionPreferenceControllerTest { + private static final String KEY = "wifi_connection"; + + @Mock + WifiTracker mWifiTracker; + @Mock + PreferenceScreen mScreen; + @Mock + PreferenceCategory mPreferenceCategory; + + private Context mContext; + private LifecycleOwner mLifecycleOwner; + private Lifecycle mLifecycle; + private WifiConnectionPreferenceController mController; + private int mOnChildUpdatedCount; + private WifiConnectionPreferenceController.UpdateListener mUpdateListener; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(RuntimeEnvironment.application); + WifiTrackerFactory.setTestingWifiTracker(mWifiTracker); + mLifecycleOwner = () -> mLifecycle; + mLifecycle = new Lifecycle(mLifecycleOwner); + when(mScreen.findPreference(eq(KEY))).thenReturn(mPreferenceCategory); + when(mScreen.getContext()).thenReturn(mContext); + mUpdateListener = () -> mOnChildUpdatedCount++; + + mController = new WifiConnectionPreferenceController(mContext, mLifecycle, mUpdateListener, + KEY, 0, 0); + } + + @Test + public void isAvailable_noWiFiConnection_availableIsFalse() { + when(mWifiTracker.isConnected()).thenReturn(false); + assertThat(mController.isAvailable()).isFalse(); + } + + @Test + public void displayPreference_noWiFiConnection_noPreferenceAdded() { + when(mWifiTracker.isConnected()).thenReturn(false); + when(mWifiTracker.getAccessPoints()).thenReturn(new ArrayList<>()); + mController.displayPreference(mScreen); + verify(mPreferenceCategory, never()).addPreference(any()); + } + + @Test + public void displayPreference_hasWiFiConnection_preferenceAdded() { + when(mWifiTracker.isConnected()).thenReturn(true); + final AccessPoint accessPoint = mock(AccessPoint.class); + when(accessPoint.isActive()).thenReturn(true); + when(mWifiTracker.getAccessPoints()).thenReturn(Arrays.asList(accessPoint)); + mController.displayPreference(mScreen); + verify(mPreferenceCategory).addPreference(any(AccessPointPreference.class)); + } + + @Test + public void onConnectedChanged_wifiBecameDisconnected_preferenceRemoved() { + when(mWifiTracker.isConnected()).thenReturn(true); + final AccessPoint accessPoint = mock(AccessPoint.class); + + when(accessPoint.isActive()).thenReturn(true); + when(mWifiTracker.getAccessPoints()).thenReturn(Arrays.asList(accessPoint)); + mController.displayPreference(mScreen); + final ArgumentCaptor captor = ArgumentCaptor.forClass( + AccessPointPreference.class); + verify(mPreferenceCategory).addPreference(captor.capture()); + final AccessPointPreference pref = captor.getValue(); + + when(mWifiTracker.isConnected()).thenReturn(false); + when(mWifiTracker.getAccessPoints()).thenReturn(new ArrayList<>()); + final int onUpdatedCountBefore = mOnChildUpdatedCount; + mController.onConnectedChanged(); + verify(mPreferenceCategory).removePreference(pref); + assertThat(mOnChildUpdatedCount).isEqualTo(onUpdatedCountBefore + 1); + } + + + @Test + public void onAccessPointsChanged_wifiBecameConnectedToDifferentAP_preferenceReplaced() { + when(mWifiTracker.isConnected()).thenReturn(true); + final AccessPoint accessPoint1 = mock(AccessPoint.class); + + when(accessPoint1.isActive()).thenReturn(true); + when(mWifiTracker.getAccessPoints()).thenReturn(Arrays.asList(accessPoint1)); + mController.displayPreference(mScreen); + final ArgumentCaptor captor = ArgumentCaptor.forClass( + AccessPointPreference.class); + + + final AccessPoint accessPoint2 = mock(AccessPoint.class); + when(accessPoint1.isActive()).thenReturn(false); + when(accessPoint2.isActive()).thenReturn(true); + when(mWifiTracker.getAccessPoints()).thenReturn(Arrays.asList(accessPoint1, accessPoint2)); + final int onUpdatedCountBefore = mOnChildUpdatedCount; + mController.onAccessPointsChanged(); + + verify(mPreferenceCategory, times(2)).addPreference(captor.capture()); + final AccessPointPreference pref1 = captor.getAllValues().get(0); + final AccessPointPreference pref2 = captor.getAllValues().get(1); + assertThat(pref1.getAccessPoint()).isEqualTo(accessPoint1); + assertThat(pref2.getAccessPoint()).isEqualTo(accessPoint2); + verify(mPreferenceCategory).removePreference(eq(pref1)); + assertThat(mOnChildUpdatedCount).isEqualTo(onUpdatedCountBefore + 1); + } +} -- 2.11.0