diff options
author | Joe LaPenna <jlapenna@google.com> | 2017-02-15 17:31:38 -0800 |
---|---|---|
committer | Joe LaPenna <jlapenna@google.com> | 2017-02-22 13:14:23 -0800 |
commit | 408ffe650e232c941168713e1203aafa4cdd06ed (patch) | |
tree | 43416c6cc078748c95305e835bd803b7ddad9711 | |
parent | 197976bcaf5ac84447dbfdfb7ed6e5cab2654b14 (diff) | |
download | NetworkRecommendation-408ffe650e232c941168713e1203aafa4cdd06ed.tar.gz |
Update notify code.
Test: mma NetworkRecommendation RunNetworkRecommendationRoboTests
Bug: 34944625
Change-Id: I102afdcd78cb0dc013b7c7965fd8d78d346e9d01
13 files changed, 1056 insertions, 561 deletions
diff --git a/robotests/src/android/net/RecommendationResult.java b/robotests/src/android/net/RecommendationResult.java new file mode 100644 index 0000000..dafb1fc --- /dev/null +++ b/robotests/src/android/net/RecommendationResult.java @@ -0,0 +1,48 @@ +/* + * 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 android.net; + +import android.annotation.Nullable; +import android.net.wifi.WifiConfiguration; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * A placeholder class to prevent ClassNotFound exceptions caused by lack of visibility. + */ +public class RecommendationResult implements Parcelable { + + protected RecommendationResult(Parcel in) { + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + } + + public boolean hasRecommendation() { + return false; + } + + @Nullable + public WifiConfiguration getWifiConfiguration() { + return null; + } +} diff --git a/robotests/src/com/android/networkrecommendation/BroadcastIntentTestHelper.java b/robotests/src/com/android/networkrecommendation/BroadcastIntentTestHelper.java new file mode 100644 index 0000000..fd674b8 --- /dev/null +++ b/robotests/src/com/android/networkrecommendation/BroadcastIntentTestHelper.java @@ -0,0 +1,63 @@ +/* + * 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.networkrecommendation; + +import android.content.Context; +import android.content.Intent; +import android.net.NetworkInfo; +import android.net.wifi.WifiManager; +import android.os.PowerManager; + +/** Convenience methods for sending Intent broadcasts. */ +public class BroadcastIntentTestHelper { + + private final Context mContext; + + public BroadcastIntentTestHelper(Context context) { + mContext = context; + } + + public void sendPowerSaveModeChanged() { + Intent intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED); + mContext.sendBroadcast(intent); + } + + public void sendWifiStateChanged() { + Intent intent = new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION); + mContext.sendBroadcast(intent); + } + + public void sendNetworkStateChanged(NetworkInfo networkInfo) { + Intent intent = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION); + intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, networkInfo); + mContext.sendBroadcast(intent); + } + + public void sendScanResultsAvailable() { + Intent intent = new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); + mContext.sendBroadcast(intent); + } + + public void sendWifiApStateChanged() { + Intent intent = new Intent(WifiManager.WIFI_AP_STATE_CHANGED_ACTION); + mContext.sendBroadcast(intent); + } + + public void sendConfiguredNetworksChanged() { + Intent intent = new Intent(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION); + mContext.sendBroadcast(intent); + } +} diff --git a/robotests/src/com/android/networkrecommendation/TestData.java b/robotests/src/com/android/networkrecommendation/TestData.java new file mode 100644 index 0000000..aad9d5b --- /dev/null +++ b/robotests/src/com/android/networkrecommendation/TestData.java @@ -0,0 +1,41 @@ +/* + * 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.networkrecommendation; + +import com.android.networkrecommendation.util.SsidUtil; + +/** + * Stock objects which can be re-used in multiple tests. + * + * <p>Objects here should be kept simple and generic; test-specific variants should be created + * inside tests as opposed to here. + */ +public class TestData { + + // SSID and BSSID values + public static final String UNQUOTED_SSID_1 = "ssid1"; + public static final String UNQUOTED_SSID_2 = "ssid2"; + public static final String UNQUOTED_SSID_3 = "ssid3"; + public static final String SSID_1 = SsidUtil.quoteSsid(UNQUOTED_SSID_1); + public static final String SSID_2 = SsidUtil.quoteSsid(UNQUOTED_SSID_2); + public static final String SSID_3 = SsidUtil.quoteSsid(UNQUOTED_SSID_3); + public static final String BSSID_1 = "01:01:01:01:01:01"; + public static final String BSSID_2 = "02:02:02:02:02:02"; + public static final String BSSID_3 = "03:03:03:03:03:03"; + + // Can't instantiate. + private TestData() {} +} diff --git a/robotests/src/com/android/networkrecommendation/notify/WifiNotificationControllerTest.java b/robotests/src/com/android/networkrecommendation/notify/WifiNotificationControllerTest.java new file mode 100644 index 0000000..30098ce --- /dev/null +++ b/robotests/src/com/android/networkrecommendation/notify/WifiNotificationControllerTest.java @@ -0,0 +1,371 @@ +/* + * 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.networkrecommendation.notify; + +import static com.android.networkrecommendation.PlatformTestObjectFactory.createOpenNetworkScanResult; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.Notification; +import android.app.NotificationManager; +import android.content.ContentResolver; +import android.content.Intent; +import android.graphics.Bitmap; +import android.net.NetworkInfo; +import android.net.NetworkInfo.DetailedState; +import android.net.NetworkInfo.State; +import android.net.RecommendationRequest; +import android.net.RecommendationResult; +import android.net.wifi.ScanResult; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiManager; +import android.os.Handler; +import android.provider.Settings; +import com.android.networkrecommendation.BroadcastIntentTestHelper; +import com.android.networkrecommendation.SynchronousNetworkRecommendationProvider; +import com.android.networkrecommendation.TestData; +import com.android.networkrecommendation.util.RoboCompatUtil; +import com.google.common.collect.Lists; +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowApplication; +import org.robolectric.shadows.ShadowLooper; +import org.robolectric.shadows.ShadowSettings; + +/** + * Instrumentation tests for {@link com.android.networkrecommendation.WifiNotificationController}. + */ +@RunWith(RobolectricTestRunner.class) +@Config(manifest = "packages/services/NetworkRecommendation/AndroidManifest.xml", sdk = 23) +public class WifiNotificationControllerTest { + + @Mock private WifiManager mWifiManager; + @Mock private NotificationManager mNotificationManager; + @Mock private WifiNotificationHelper mWifiNotificationHelper; + @Mock private SynchronousNetworkRecommendationProvider mNetworkRecommendationProvider; + @Mock private NetworkInfo mNetworkInfo; + @Mock private RecommendationResult mRecommendationResult; + @Mock private RoboCompatUtil mRoboCompatUtil; + @Captor private ArgumentCaptor<List<ScanResult>> mScanResultCaptor; + private ContentResolver mContentResolver; + private Handler mHandler; + private WifiNotificationController mWifiNotificationController; + private BroadcastIntentTestHelper mBroadcastIntentTestHelper; + + /** Initialize objects before each test run. */ + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + // Needed for the NotificationEnabledSettingObserver. + mContentResolver = RuntimeEnvironment.application.getContentResolver(); + ShadowSettings.ShadowGlobal.putInt( + mContentResolver, Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 1); + mHandler = new Handler(ShadowLooper.getMainLooper()); + + RoboCompatUtil.setInstanceForTesting(mRoboCompatUtil); + + mWifiNotificationController = + new WifiNotificationController( + RuntimeEnvironment.application, + mContentResolver, + mHandler, + mNetworkRecommendationProvider, + mWifiManager, + mNotificationManager, + mWifiNotificationHelper); + mWifiNotificationController.start(); + + when(mNetworkInfo.getState()).thenReturn(State.UNKNOWN); + mBroadcastIntentTestHelper = new BroadcastIntentTestHelper(RuntimeEnvironment.application); + } + + private void setOpenAccessPoints() { + List<ScanResult> scanResults = + Lists.newArrayList( + createOpenNetworkScanResult(TestData.UNQUOTED_SSID_1, TestData.BSSID_1), + createOpenNetworkScanResult(TestData.UNQUOTED_SSID_2, TestData.BSSID_2), + createOpenNetworkScanResult(TestData.UNQUOTED_SSID_3, TestData.BSSID_3)); + assertFalse(scanResults.isEmpty()); + when(mWifiManager.getScanResults()).thenReturn(scanResults); + } + + private static WifiConfiguration createFakeConfig() { + WifiConfiguration config = new WifiConfiguration(); + config.SSID = TestData.SSID_1; + config.BSSID = TestData.BSSID_1; + config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); + return config; + } + + private void createFakeBitmap() { + when(mWifiNotificationHelper.createNotificationBadgeBitmap(any(), any())) + .thenReturn(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)); + } + + /** + * When the NetworkRecommendationService associated with this WifiNotificationController is + * unbound, this WifiWakeupController should no longer function. + */ + @Test + public void wifiNotificationControllerStopped() { + mWifiNotificationController.stop(); + + assertFalse( + ShadowApplication.getInstance() + .hasReceiverForIntent( + new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION))); + } + + /** Verifies that a notification is displayed (and retracted) given system events. */ + @Test + public void verifyNotificationDisplayedWhenNetworkRecommended() throws Exception { + when(mWifiManager.getWifiState()).thenReturn(WifiManager.WIFI_STATE_ENABLED); + + mBroadcastIntentTestHelper.sendWifiStateChanged(); + when(mNetworkInfo.getDetailedState()).thenReturn(DetailedState.DISCONNECTED); + mBroadcastIntentTestHelper.sendNetworkStateChanged(mNetworkInfo); + setOpenAccessPoints(); + createFakeBitmap(); + + when(mNetworkRecommendationProvider.requestRecommendation(any(RecommendationRequest.class))) + .thenReturn(mRecommendationResult); + when(mRecommendationResult.getWifiConfiguration()).thenReturn(createFakeConfig()); + + // The notification should not be displayed after only two scan results. + mBroadcastIntentTestHelper.sendScanResultsAvailable(); + mBroadcastIntentTestHelper.sendScanResultsAvailable(); + verify(mNotificationManager, never()) + .notify(anyString(), anyInt(), any(Notification.class)); + + verify(mWifiManager, times(2)).getScanResults(); + + // Changing to and from "SCANNING" state should not affect the counter. + when(mNetworkInfo.getDetailedState()).thenReturn(DetailedState.SCANNING); + mBroadcastIntentTestHelper.sendNetworkStateChanged(mNetworkInfo); + when(mNetworkInfo.getDetailedState()).thenReturn(DetailedState.DISCONNECTED); + mBroadcastIntentTestHelper.sendNetworkStateChanged(mNetworkInfo); + + verify(mNotificationManager, never()) + .notify(anyString(), anyInt(), any(Notification.class)); + + // The third scan result notification will trigger the notification. + mBroadcastIntentTestHelper.sendScanResultsAvailable(); + + verify(mWifiNotificationHelper) + .createMainNotification(any(WifiConfiguration.class), any(Bitmap.class)); + verify(mNotificationManager).notify(anyString(), anyInt(), any(Notification.class)); + verify(mNotificationManager, never()).cancel(anyString(), anyInt()); + } + + /** Verifies that a notification is not displayed for bad networks. */ + @Test + public void verifyNotificationNotDisplayedWhenNoNetworkRecommended() throws Exception { + when(mWifiManager.getWifiState()).thenReturn(WifiManager.WIFI_STATE_ENABLED); + + mBroadcastIntentTestHelper.sendWifiStateChanged(); + when(mNetworkInfo.getDetailedState()).thenReturn(DetailedState.DISCONNECTED); + mBroadcastIntentTestHelper.sendNetworkStateChanged(mNetworkInfo); + setOpenAccessPoints(); + createFakeBitmap(); + + // Recommendation result with no WifiConfiguration returned. + when(mNetworkRecommendationProvider.requestRecommendation(any(RecommendationRequest.class))) + .thenReturn(mRecommendationResult); + when(mRecommendationResult.getWifiConfiguration()).thenReturn(null); + + mBroadcastIntentTestHelper.sendScanResultsAvailable(); + mBroadcastIntentTestHelper.sendScanResultsAvailable(); + mBroadcastIntentTestHelper.sendScanResultsAvailable(); + mBroadcastIntentTestHelper.sendScanResultsAvailable(); + verify(mNotificationManager, never()) + .notify(anyString(), anyInt(), any(Notification.class)); + } + + /** + * Verifies the notifications flow (Connect -> connecting -> connected) when user clicks on + * Connect button. + */ + @Test + public void verifyNotificationsFlowOnConnectToNetwork() throws Exception { + when(mWifiManager.getWifiState()).thenReturn(WifiManager.WIFI_STATE_ENABLED); + + mBroadcastIntentTestHelper.sendWifiStateChanged(); + when(mNetworkInfo.getDetailedState()).thenReturn(DetailedState.DISCONNECTED); + mBroadcastIntentTestHelper.sendNetworkStateChanged(mNetworkInfo); + setOpenAccessPoints(); + createFakeBitmap(); + + when(mNetworkRecommendationProvider.requestRecommendation(any(RecommendationRequest.class))) + .thenReturn(mRecommendationResult); + when(mRecommendationResult.getWifiConfiguration()).thenReturn(createFakeConfig()); + + mBroadcastIntentTestHelper.sendScanResultsAvailable(); + mBroadcastIntentTestHelper.sendScanResultsAvailable(); + mBroadcastIntentTestHelper.sendScanResultsAvailable(); + verify(mWifiNotificationHelper) + .createMainNotification(any(WifiConfiguration.class), any(Bitmap.class)); + verify(mNotificationManager).notify(anyString(), anyInt(), any(Notification.class)); + + // Send connect intent, should attempt to connect to Wi-Fi + Intent intent = + new Intent(WifiNotificationController.ACTION_CONNECT_TO_RECOMMENDED_NETWORK); + ShadowApplication.getInstance().sendBroadcast(intent); + verify(mRoboCompatUtil).connectToWifi(any(WifiManager.class), any(WifiConfiguration.class)); + verify(mWifiNotificationHelper) + .createConnectingNotification(any(WifiConfiguration.class), any(Bitmap.class)); + + // Show connecting notification. + verify(mNotificationManager, times(2)) + .notify(anyString(), anyInt(), any(Notification.class)); + + // Verify show connected notification. + when(mNetworkInfo.getDetailedState()).thenReturn(DetailedState.CONNECTED); + mBroadcastIntentTestHelper.sendNetworkStateChanged(mNetworkInfo); + verify(mWifiNotificationHelper) + .createConnectedNotification(any(WifiConfiguration.class), any(Bitmap.class)); + verify(mNotificationManager, times(3)) + .notify(anyString(), anyInt(), any(Notification.class)); + + // Dismissed the connected notification. + ShadowLooper.runMainLooperToNextTask(); + verify(mNotificationManager).cancel(anyString(), anyInt()); + } + + /** Verifies the Failure to Connect notification after attempting to connect. */ + @Test + public void verifyNotificationsFlowOnFailedToConnectToNetwork() throws Exception { + when(mWifiManager.getWifiState()).thenReturn(WifiManager.WIFI_STATE_ENABLED); + + mBroadcastIntentTestHelper.sendWifiStateChanged(); + when(mNetworkInfo.getDetailedState()).thenReturn(DetailedState.DISCONNECTED); + mBroadcastIntentTestHelper.sendNetworkStateChanged(mNetworkInfo); + setOpenAccessPoints(); + createFakeBitmap(); + + when(mNetworkRecommendationProvider.requestRecommendation(any(RecommendationRequest.class))) + .thenReturn(mRecommendationResult); + when(mRecommendationResult.getWifiConfiguration()).thenReturn(createFakeConfig()); + + mBroadcastIntentTestHelper.sendScanResultsAvailable(); + mBroadcastIntentTestHelper.sendScanResultsAvailable(); + mBroadcastIntentTestHelper.sendScanResultsAvailable(); + verify(mWifiNotificationHelper) + .createMainNotification(any(WifiConfiguration.class), any(Bitmap.class)); + verify(mNotificationManager).notify(anyString(), anyInt(), any(Notification.class)); + + // Send connect intent, should attempt to connect to Wi-Fi + Intent intent = + new Intent(WifiNotificationController.ACTION_CONNECT_TO_RECOMMENDED_NETWORK); + ShadowApplication.getInstance().sendBroadcast(intent); + verify(mRoboCompatUtil).connectToWifi(any(WifiManager.class), any(WifiConfiguration.class)); + verify(mWifiNotificationHelper) + .createConnectingNotification(any(WifiConfiguration.class), any(Bitmap.class)); + + // Show connecting notification. + verify(mNotificationManager, times(2)) + .notify(anyString(), anyInt(), any(Notification.class)); + + // Show failed to connect notification. + ShadowLooper.runMainLooperToNextTask(); + verify(mWifiNotificationHelper) + .createFailedToConnectNotification(any(WifiConfiguration.class)); + + // Dismissed the cancel notification. + ShadowLooper.runMainLooperToNextTask(); + verify(mNotificationManager).cancel(anyString(), anyInt()); + } + + /** Verifies the flow where notification is dismissed. */ + @Test + public void verifyNotificationsFlowOnDismissMainNotification() throws Exception { + when(mWifiManager.getWifiState()).thenReturn(WifiManager.WIFI_STATE_ENABLED); + + mBroadcastIntentTestHelper.sendWifiStateChanged(); + when(mNetworkInfo.getDetailedState()).thenReturn(DetailedState.DISCONNECTED); + mBroadcastIntentTestHelper.sendNetworkStateChanged(mNetworkInfo); + setOpenAccessPoints(); + createFakeBitmap(); + + when(mNetworkRecommendationProvider.requestRecommendation(any(RecommendationRequest.class))) + .thenReturn(mRecommendationResult); + when(mRecommendationResult.getWifiConfiguration()).thenReturn(createFakeConfig()); + + mBroadcastIntentTestHelper.sendScanResultsAvailable(); + mBroadcastIntentTestHelper.sendScanResultsAvailable(); + mBroadcastIntentTestHelper.sendScanResultsAvailable(); + + // Show main notification + verify(mWifiNotificationHelper) + .createMainNotification(any(WifiConfiguration.class), any(Bitmap.class)); + verify(mNotificationManager).notify(anyString(), anyInt(), any(Notification.class)); + + // Send dismiss intent + Intent intent = new Intent(WifiNotificationController.ACTION_NOTIFICATION_DELETED); + ShadowApplication.getInstance().sendBroadcast(intent); + } + + /** Verifies saved networks are skipped when getting network recommendations */ + @Test + public void verifyNotificationsFlowSkipSavedNetworks() throws Exception { + when(mWifiManager.getWifiState()).thenReturn(WifiManager.WIFI_STATE_ENABLED); + + mBroadcastIntentTestHelper.sendWifiStateChanged(); + when(mNetworkInfo.getDetailedState()).thenReturn(DetailedState.DISCONNECTED); + mBroadcastIntentTestHelper.sendNetworkStateChanged(mNetworkInfo); + + // First scan result and saved WifiConfiguration should be equal + when(mWifiManager.getScanResults()) + .thenReturn( + Lists.newArrayList( + createOpenNetworkScanResult( + TestData.UNQUOTED_SSID_1, TestData.BSSID_1))); + when(mWifiManager.getConfiguredNetworks()) + .thenReturn(Lists.newArrayList(createFakeConfig())); + mBroadcastIntentTestHelper.sendScanResultsAvailable(); + verify(mRoboCompatUtil).createRecommendationRequest(mScanResultCaptor.capture()); + + assertEquals(new ArrayList<>(), mScanResultCaptor.getValue()); + } + + /** Test dump() does not crash. */ + @Test + public void testDump() { + StringWriter stringWriter = new StringWriter(); + mWifiNotificationController.dump( + new FileDescriptor(), new PrintWriter(stringWriter), new String[0]); + } +} diff --git a/robotests/src/com/android/networkrecommendation/notify/WifiNotificationHelperTest.java b/robotests/src/com/android/networkrecommendation/notify/WifiNotificationHelperTest.java new file mode 100644 index 0000000..6351600 --- /dev/null +++ b/robotests/src/com/android/networkrecommendation/notify/WifiNotificationHelperTest.java @@ -0,0 +1,149 @@ +/* + * 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.networkrecommendation.notify; + +import static com.android.networkrecommendation.PlatformTestObjectFactory.createOpenNetworkScanResult; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.when; + +import android.app.Notification; +import android.content.Context; +import android.graphics.Bitmap; +import android.net.ScoredNetwork; +import android.net.wifi.ScanResult; +import android.net.wifi.WifiConfiguration; +import com.android.networkrecommendation.SynchronousNetworkRecommendationProvider; +import com.android.networkrecommendation.TestData; +import com.android.networkrecommendation.shadows.BitmapGetPixelsShadow; +import com.android.networkrecommendation.util.RoboCompatUtil; +import com.google.common.collect.Lists; +import java.util.ArrayList; +import java.util.List; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowSettings; + +/** Unit tests for {@link WifiNotificationHelper} */ +@RunWith(RobolectricTestRunner.class) +@Config(manifest = "packages/services/NetworkRecommendation/AndroidManifest.xml", sdk = 23, +shadows={BitmapGetPixelsShadow.class}) +public class WifiNotificationHelperTest { + + private Context mContext; + + @Mock + private SynchronousNetworkRecommendationProvider mSynchronousNetworkRecommendationProvider; + + @Mock private RoboCompatUtil mRoboCompatUtil; + + private WifiNotificationHelper mWifiNotificationHelper; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + RoboCompatUtil.setInstanceForTesting(mRoboCompatUtil); + mContext = RuntimeEnvironment.application; + + mWifiNotificationHelper = + new WifiNotificationHelper(mContext, mSynchronousNetworkRecommendationProvider); + } + + private static WifiConfiguration createFakeConfig() { + WifiConfiguration config = new WifiConfiguration(); + config.SSID = TestData.SSID_1; + config.BSSID = TestData.BSSID_1; + config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); + return config; + } + + private static Bitmap createFakeIcon() { + return Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); + } + + private static void assertValidNotification(Notification notification) { + assertNotNull(notification); + assertNotNull(notification.getSmallIcon()); + assertNotNull(notification.getLargeIcon()); + } + + @Test + public void createMainNotification() { + assertValidNotification( + mWifiNotificationHelper.createMainNotification( + createFakeConfig(), createFakeIcon())); + } + + @Test + public void createConnectingNotification() { + assertValidNotification( + mWifiNotificationHelper.createConnectingNotification( + createFakeConfig(), createFakeIcon())); + } + + @Test + public void createConnectedNotification() { + assertValidNotification( + mWifiNotificationHelper.createConnectedNotification( + createFakeConfig(), createFakeIcon())); + } + + @Test + public void createFailedToConnectNotification() { + assertValidNotification( + mWifiNotificationHelper.createFailedToConnectNotification(createFakeConfig())); + } + + @Test + public void createNotificationBadgeBitmap() { + WifiConfiguration wifiConfig = createFakeConfig(); + List<ScanResult> scanResultList = + Lists.newArrayList(createOpenNetworkScanResult(wifiConfig.SSID, wifiConfig.BSSID)); + when(mSynchronousNetworkRecommendationProvider.getCachedScoredNetwork(any())) + .thenReturn(Mockito.mock(ScoredNetwork.class)); + when(mRoboCompatUtil.calculateBadge(any(), anyInt())).thenReturn(ScoredNetwork.BADGING_4K); + when(mRoboCompatUtil.getWifiIcon(anyInt(), anyInt(), any())) + .thenReturn(mContext.getDrawable(android.R.drawable.stat_sys_warning)); + + assertNotNull( + mWifiNotificationHelper.createNotificationBadgeBitmap(wifiConfig, scanResultList)); + + ShadowSettings.ShadowGlobal.putInt( + mContext.getContentResolver(), + WifiNotificationHelper.NETWORK_SCORING_UI_ENABLED, + 1); + assertNotNull( + mWifiNotificationHelper.createNotificationBadgeBitmap(wifiConfig, scanResultList)); + } + + @Test + public void createNotificationBadgeBitmap_noMatchingScanResults() { + WifiConfiguration wifiConfig = createFakeConfig(); + List<ScanResult> scanResultList = new ArrayList<>(); + + assertNull( + mWifiNotificationHelper.createNotificationBadgeBitmap(wifiConfig, scanResultList)); + } +} diff --git a/robotests/src/com/android/networkrecommendation/shadows/BitmapGetPixelsShadow.java b/robotests/src/com/android/networkrecommendation/shadows/BitmapGetPixelsShadow.java new file mode 100644 index 0000000..8912305 --- /dev/null +++ b/robotests/src/com/android/networkrecommendation/shadows/BitmapGetPixelsShadow.java @@ -0,0 +1,14 @@ +package com.android.networkrecommendation.shadows; + +import android.annotation.ColorInt; +import android.graphics.Bitmap; + +import org.robolectric.annotation.Implements; +import org.robolectric.shadows.ShadowBitmap; + +@Implements(Bitmap.class) +public class BitmapGetPixelsShadow extends ShadowBitmap { + public void getPixels(@ColorInt int[] pixels, int offset, int stride, + int x, int y, int width, int height) { + } +} diff --git a/robotests/src/com/android/networkrecommendation/util/ScanResultUtilTest.java b/robotests/src/com/android/networkrecommendation/util/ScanResultUtilTest.java index 6900c9b..a55a598 100644 --- a/robotests/src/com/android/networkrecommendation/util/ScanResultUtilTest.java +++ b/robotests/src/com/android/networkrecommendation/util/ScanResultUtilTest.java @@ -36,7 +36,7 @@ import org.robolectric.annotation.Config; /** Unit tests for {@link ScanResultUtil}. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = 21, manifest = Config.NONE) +@Config(manifest = "packages/services/NetworkRecommendation/AndroidManifest.xml", sdk = 23) public class ScanResultUtilTest { @Before diff --git a/robotests/src/com/android/networkrecommendation/util/SsidUtilTest.java b/robotests/src/com/android/networkrecommendation/util/SsidUtilTest.java index 25672b4..053c38d 100644 --- a/robotests/src/com/android/networkrecommendation/util/SsidUtilTest.java +++ b/robotests/src/com/android/networkrecommendation/util/SsidUtilTest.java @@ -25,7 +25,7 @@ import org.robolectric.annotation.Config; /** Tests for {@link SsidUtil}. */ @RunWith(RobolectricTestRunner.class) -@Config(manifest = Config.NONE) +@Config(manifest = "packages/services/NetworkRecommendation/AndroidManifest.xml", sdk = 23) public class SsidUtilTest { @Test diff --git a/src/com/android/networkrecommendation/notify/WifiNotificationController.java b/src/com/android/networkrecommendation/notify/WifiNotificationController.java index a7afcdd..28fafc2 100644 --- a/src/com/android/networkrecommendation/notify/WifiNotificationController.java +++ b/src/com/android/networkrecommendation/notify/WifiNotificationController.java @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.android.networkrecommendation.notify; import android.app.Notification; @@ -34,110 +33,115 @@ import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiManager; import android.os.Handler; import android.provider.Settings; +import android.support.annotation.IntDef; import android.support.annotation.Nullable; -import android.support.annotation.VisibleForTesting; - import com.android.networkrecommendation.R; import com.android.networkrecommendation.SynchronousNetworkRecommendationProvider; - +import com.android.networkrecommendation.util.RoboCompatUtil; +import com.android.networkrecommendation.util.ScanResultUtil; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; -/** - * Takes care of handling the "open wi-fi network available" notification - * @hide - */ +/** Takes care of handling the "open wi-fi network available" notification */ public class WifiNotificationController { - /** - * The icon to show in the 'available networks' notification. This will also - * be the ID of the Notification given to the NotificationManager. - */ - private static final int ICON_NETWORKS_AVAILABLE = R.drawable.stat_notify_wifi_in_range; - /** - * When a notification is shown, we wait this amount before possibly showing it again. - */ + /** The unique ID of the Notification given to the NotificationManager. */ + private static final int NOTIFICATION_ID = R.string.wifi_available; + + /** When a notification is shown, we wait this amount before possibly showing it again. */ private final long mNotificationRepeatDelayMs; - /** - * Whether the user has set the setting to show the 'available networks' notification. - */ + + /** Whether the user has set the setting to show the 'available networks' notification. */ private boolean mNotificationEnabled; - /** - * Observes the user setting to keep {@link #mNotificationEnabled} in sync. - */ + + /** Observes the user setting to keep {@link #mNotificationEnabled} in sync. */ private final NotificationEnabledSettingObserver mNotificationEnabledSettingObserver; + /** - * The {@link System#currentTimeMillis()} must be at least this value for us - * to show the notification again. + * The {@link System#currentTimeMillis()} must be at least this value for us to show the + * notification again. */ private long mNotificationRepeatTime; + + /** These are all of the possible states for the open networks available notification. */ + @IntDef({ + State.HIDDEN, + State.SHOWING_CONNECT_ACTIONS, + State.SHOWING_CONNECTING, + State.SHOWING_CONNECTED, + State.SHOWING_FAILURE + }) + @Retention(RetentionPolicy.SOURCE) + public @interface State { + int HIDDEN = 0; + int SHOWING_CONNECT_ACTIONS = 1; + int SHOWING_CONNECTING = 2; + int SHOWING_CONNECTED = 3; + int SHOWING_FAILURE = 4; + } + /** - * Whether the notification is being shown. + * The {@link System#currentTimeMillis()} must be at least this value to log that open networks + * are available. */ - private boolean mNotificationShown; + private long mOpenNetworksLoggingRepeatTime; + + /** Current state of the notification. */ + @State private int mNotificationState = State.HIDDEN; + /** - * The number of continuous scans that must occur before consider the - * supplicant in a scanning state. This allows supplicant to associate with - * remembered networks that are in the scan results. + * The number of continuous scans that must occur before consider the supplicant in a scanning + * state. This allows supplicant to associate with remembered networks that are in the scan + * results. */ private static final int NUM_SCANS_BEFORE_ACTUALLY_SCANNING = 3; + /** - * The number of scans since the last network state change. When this - * exceeds {@link #NUM_SCANS_BEFORE_ACTUALLY_SCANNING}, we consider the - * supplicant to actually be scanning. When the network state changes to - * something other than scanning, we reset this to 0. + * The number of scans since the last network state change. When this exceeds {@link + * #NUM_SCANS_BEFORE_ACTUALLY_SCANNING}, we consider the supplicant to actually be scanning. + * When the network state changes to something other than scanning, we reset this to 0. */ private int mNumScansSinceNetworkStateChange; - /** - * Time in milliseconds to display the Connecting notification. - */ + + /** Time in milliseconds to display the Connecting notification. */ private static final int TIME_TO_SHOW_CONNECTING_MILLIS = 10000; - /** - * Time in milliseconds to display the Connected notification. - */ + + /** Time in milliseconds to display the Connected notification. */ private static final int TIME_TO_SHOW_CONNECTED_MILLIS = 5000; - /** - * Time in milliseconds to display the Failed To Connect notification. - */ + + /** Time in milliseconds to display the Failed To Connect notification. */ private static final int TIME_TO_SHOW_FAILED_MILLIS = 5000; + /** - * Try to connect to provided WifiConfiguration since user wants to - * connect to the recommended open access point. + * Try to connect to provided WifiConfiguration since user wants to connect to the recommended + * open access point. */ static final String ACTION_CONNECT_TO_RECOMMENDED_NETWORK = - "com.android.networkrecommendation.CONNECT_TO_RECOMMENDED_NETWORK"; - /** - * Handles behavior when notification is deleted. - */ + "com.android.networkrecommendation.notify.CONNECT_TO_RECOMMENDED_NETWORK"; + + /** Open wifi picker to see all available networks. */ + static final String ACTION_PICK_WIFI_NETWORK = + "com.android.networkrecommendation.notify.ACTION_PICK_WIFI_NETWORK"; + + /** Handles behavior when notification is deleted. */ static final String ACTION_NOTIFICATION_DELETED = - "com.android.networkrecommendation.NOTIFICATION_DELETED"; - /** - * Network recommended by {@link NetworkScoreManager#requestRecommendation}. - */ + "com.android.networkrecommendation.notify.NOTIFICATION_DELETED"; + + /** Network recommended by {@link NetworkScoreManager#requestRecommendation}. */ private WifiConfiguration mRecommendedNetwork; + + /** Badge icon of the recommended network. */ private Bitmap mNotificationBadgeBitmap; - /** - * Whether {@link WifiNotificationController} has been started. - */ + + /** Whether {@link WifiNotificationController} has been started. */ private final AtomicBoolean mStarted; - /** - * Runnable to dismiss notification. - */ - @VisibleForTesting - final Runnable mDismissNotificationRunnable = () -> { - removeNotification(); - }; - /** - * Runnable to show Failed To Connect notification. - */ - @VisibleForTesting - final Runnable mShowFailedToConnectNotificationRunnable = () -> { - showFailedToConnectNotification(); - }; - private static final String TAG = "WifiNotification"; + private static final String NOTIFICATION_TAG = "WifiNotification"; private final Context mContext; private final Handler mHandler; @@ -150,10 +154,13 @@ public class WifiNotificationController { private NetworkInfo.DetailedState mDetailedState; private volatile int mWifiState; - public WifiNotificationController(Context context, ContentResolver contentResolver, + public WifiNotificationController( + Context context, + ContentResolver contentResolver, Handler handler, SynchronousNetworkRecommendationProvider networkRecommendationProvider, - WifiManager wifiManager, NotificationManager notificationManager, + WifiManager wifiManager, + NotificationManager notificationManager, WifiNotificationHelper helper) { mContext = context; mContentResolver = contentResolver; @@ -165,8 +172,12 @@ public class WifiNotificationController { mStarted = new AtomicBoolean(false); // Setting is in seconds - mNotificationRepeatDelayMs = Settings.Global.getInt( - contentResolver, Settings.Global.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, 900) * 1000L; + mNotificationRepeatDelayMs = + TimeUnit.SECONDS.toMillis( + Settings.Global.getInt( + contentResolver, + Settings.Global.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, + 900)); mNotificationEnabledSettingObserver = new NotificationEnabledSettingObserver(mHandler); } @@ -176,7 +187,7 @@ public class WifiNotificationController { return; } - mWifiState = WifiManager.WIFI_STATE_UNKNOWN; + mWifiState = mWifiManager.getWifiState(); mDetailedState = NetworkInfo.DetailedState.IDLE; IntentFilter filter = new IntentFilter(); @@ -200,78 +211,81 @@ public class WifiNotificationController { mNotificationEnabledSettingObserver.register(); } - private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (intent.getAction().equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) { - mWifiState = mWifiManager.getWifiState(); - resetNotification(); - } else if (intent.getAction().equals( - WifiManager.NETWORK_STATE_CHANGED_ACTION)) { - mNetworkInfo = (NetworkInfo) intent.getParcelableExtra( - WifiManager.EXTRA_NETWORK_INFO); - NetworkInfo.DetailedState detailedState = - mNetworkInfo.getDetailedState(); - if (detailedState != NetworkInfo.DetailedState.SCANNING - && detailedState != mDetailedState) { - mDetailedState = detailedState; - switch (mDetailedState) { - case CONNECTED: - updateNotificationOnConnect(); - break; - case DISCONNECTED: - case CAPTIVE_PORTAL_CHECK: - resetNotification(); - break; - - // TODO: figure out if these are failure cases when connecting - case IDLE: - case SCANNING: - case CONNECTING: - case AUTHENTICATING: - case OBTAINING_IPADDR: - case SUSPENDED: - case FAILED: - case BLOCKED: - case VERIFYING_POOR_LINK: - break; + private final BroadcastReceiver mBroadcastReceiver = + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) { + mWifiState = mWifiManager.getWifiState(); + resetNotification(); + } else if (intent.getAction() + .equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { + mNetworkInfo = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); + NetworkInfo.DetailedState detailedState = mNetworkInfo.getDetailedState(); + if (detailedState != NetworkInfo.DetailedState.SCANNING + && detailedState != mDetailedState) { + mDetailedState = detailedState; + switch (mDetailedState) { + case CONNECTED: + updateNotificationOnConnect(); + break; + case DISCONNECTED: + case CAPTIVE_PORTAL_CHECK: + resetNotification(); + break; + + // TODO: figure out if these are failure cases when connecting + case IDLE: + case SCANNING: + case CONNECTING: + case DISCONNECTING: + case AUTHENTICATING: + case OBTAINING_IPADDR: + case SUSPENDED: + case FAILED: + case BLOCKED: + case VERIFYING_POOR_LINK: + break; + } + } + } else if (intent.getAction() + .equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) { + checkAndSetNotification(mNetworkInfo, mWifiManager.getScanResults()); + } else if (intent.getAction().equals(ACTION_CONNECT_TO_RECOMMENDED_NETWORK)) { + connectToRecommendedNetwork(); + } else if (intent.getAction().equals(ACTION_NOTIFICATION_DELETED)) { + handleNotificationDeleted(); + } else if (intent.getAction().equals(ACTION_PICK_WIFI_NETWORK)) { + openWifiPicker(); } } - } else if (intent.getAction().equals( - WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) { - checkAndSetNotification(mNetworkInfo, mWifiManager.getScanResults()); - } else if (intent.getAction().equals(ACTION_CONNECT_TO_RECOMMENDED_NETWORK)) { - connectToRecommendedNetwork(); - } else if (intent.getAction().equals(ACTION_NOTIFICATION_DELETED)) { - handleNotificationDeleted(); - } - } - }; - - private void checkAndSetNotification(NetworkInfo networkInfo, - List<ScanResult> scanResults) { + }; + private void checkAndSetNotification(NetworkInfo networkInfo, List<ScanResult> scanResults) { // TODO: unregister broadcast so we do not have to check here // If we shouldn't place a notification on available networks, then // don't bother doing any of the following - if (!mNotificationEnabled) return; - if (mWifiState != WifiManager.WIFI_STATE_ENABLED) return; - if (scanResults == null || scanResults.isEmpty()) return; + if (!mNotificationEnabled + || mWifiState != WifiManager.WIFI_STATE_ENABLED + || scanResults == null + || scanResults.isEmpty()) { + return; + } NetworkInfo.State state = NetworkInfo.State.DISCONNECTED; if (networkInfo != null) { state = networkInfo.getState(); } - - if (state == NetworkInfo.State.DISCONNECTED - || state == NetworkInfo.State.UNKNOWN) { + if (state == NetworkInfo.State.DISCONNECTED || state == NetworkInfo.State.UNKNOWN) { + maybeLogOpenNetworksAvailable(); RecommendationResult result = getOpenNetworkRecommendation(scanResults); - if (result != null - && result.getWifiConfiguration() != null) { + if (result != null && result.getWifiConfiguration() != null) { mRecommendedNetwork = result.getWifiConfiguration(); - mNotificationBadgeBitmap = mWifiNotificationHelper.createNotificationBadgeBitmap( - mRecommendedNetwork, scanResults); + + mNotificationBadgeBitmap = + mWifiNotificationHelper.createNotificationBadgeBitmap( + mRecommendedNetwork, scanResults); if (++mNumScansSinceNetworkStateChange >= NUM_SCANS_BEFORE_ACTUALLY_SCANNING && mNotificationBadgeBitmap != null) { /* @@ -291,9 +305,17 @@ public class WifiNotificationController { removeNotification(); } + private void maybeLogOpenNetworksAvailable() { + long now = System.currentTimeMillis(); + if (now < mOpenNetworksLoggingRepeatTime) { + return; + } + mOpenNetworksLoggingRepeatTime = now + mNotificationRepeatDelayMs; + } + /** - * Uses {@link NetworkScoreManager} to choose a qualified network out of the list of - * {@link ScanResult}s. + * Uses {@link NetworkScoreManager} to choose a qualified network out of the list of {@link + * ScanResult}s. * * @return returns the best qualified open networks, if any. */ @@ -302,26 +324,41 @@ public class WifiNotificationController { if (scanResults == null || scanResults.isEmpty()) { return null; } + ArrayList<ScanResult> openNetworks = new ArrayList<>(); + List<WifiConfiguration> configuredNetworks = mWifiManager.getConfiguredNetworks(); for (ScanResult scanResult : scanResults) { //A capability of [ESS] represents an open access point //that is available for an STA to connect + //TODO: potentially handle this within NetworkRecommendationProvider instead. if ("[ESS]".equals(scanResult.capabilities)) { + if (isSavedNetwork(scanResult, configuredNetworks)) { + continue; + } openNetworks.add(scanResult); } } - RecommendationRequest request = new RecommendationRequest.Builder() - .setScanResults(openNetworks.toArray(new ScanResult[openNetworks.size()])) - .build(); + RecommendationRequest request = + RoboCompatUtil.getInstance().createRecommendationRequest(openNetworks); return mNetworkRecommendationProvider.requestRecommendation(request); } - /** - * Display's a notification that there are open Wi-Fi networks. - */ - private void displayNotification() { + /** Returns true if scanResult matches the list of saved networks */ + private boolean isSavedNetwork(ScanResult scanResult, List<WifiConfiguration> savedNetworks) { + if (savedNetworks == null) { + return false; + } + for (int i = 0; i < savedNetworks.size(); i++) { + if (ScanResultUtil.doesScanResultMatchWithNetwork(scanResult, savedNetworks.get(i))) { + return true; + } + } + return false; + } + /** Display's a notification that there are open Wi-Fi networks. */ + private void displayNotification() { // Since we use auto cancel on the notification, when the // mNetworksAvailableNotificationShown is true, the notification may // have actually been canceled. However, when it is false we know @@ -329,103 +366,129 @@ public class WifiNotificationController { // place than here) // Not enough time has passed to show the notification again - if (System.currentTimeMillis() < mNotificationRepeatTime) { return; } - Notification notification = mWifiNotificationHelper.createMainNotification( + Notification notification = + mWifiNotificationHelper.createMainNotification( mRecommendedNetwork, mNotificationBadgeBitmap); mNotificationRepeatTime = System.currentTimeMillis() + mNotificationRepeatDelayMs; - postNotification(notification); - mNotificationShown = true; + mNotificationState = State.SHOWING_CONNECT_ACTIONS; } + /** Opens activity to allow the user to select a wifi network. */ + private void openWifiPicker() { + mContext.startActivity( + new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); + } /** - * Attempts to connect to recommended network and updates the notification to - * show Connecting state. - * TODO(33668991): work with UX to polish notification UI and figure out failure states + * Attempts to connect to recommended network and updates the notification to show Connecting + * state. TODO(33668991): work with UX to polish notification UI and figure out failure states */ private void connectToRecommendedNetwork() { if (mRecommendedNetwork == null) { return; } + mRecommendedNetwork.BSSID = null; + // Attempts to connect to recommended network. - mWifiManager.connect(mRecommendedNetwork, null /* actionListener */); + RoboCompatUtil.getInstance().connectToWifi(mWifiManager, mRecommendedNetwork); // Update notification to connecting status. - Notification notification = mWifiNotificationHelper.createConnectingNotification( + Notification notification = + mWifiNotificationHelper.createConnectingNotification( mRecommendedNetwork, mNotificationBadgeBitmap); postNotification(notification); - mHandler.postDelayed(mShowFailedToConnectNotificationRunnable, + mNotificationState = State.SHOWING_CONNECTING; + mHandler.postDelayed( + () -> { + if (mNotificationState == State.SHOWING_CONNECTING) { + showFailedToConnectNotification(); + } + }, TIME_TO_SHOW_CONNECTING_MILLIS); } /** - * When detailed state changes to CONNECTED, show connected notification or - * reset notification. + * When detailed state changes to CONNECTED, show connected notification or reset notification. * TODO: determine failure state where main notification shows but connected. */ private void updateNotificationOnConnect() { - if (!mNotificationShown) { + if (mNotificationState != State.SHOWING_CONNECTING) { return; } - Notification notification = mWifiNotificationHelper.createConnectedNotification( - mRecommendedNetwork, mNotificationBadgeBitmap); + Notification notification = + mWifiNotificationHelper.createConnectedNotification( + mRecommendedNetwork, mNotificationBadgeBitmap); postNotification(notification); - // Remove any previous reset notification callbacks. - mHandler.removeCallbacks(mShowFailedToConnectNotificationRunnable); - mHandler.postDelayed(mDismissNotificationRunnable, TIME_TO_SHOW_CONNECTED_MILLIS); + mNotificationState = State.SHOWING_CONNECTED; + mHandler.postDelayed( + () -> { + if (mNotificationState == State.SHOWING_CONNECTED) { + removeNotification(); + } + }, + TIME_TO_SHOW_CONNECTED_MILLIS); } /** - * Displays the Failed To Connect notification after the Connecting notification - * is shown for {@link #TIME_TO_SHOW_CONNECTING_MILLIS} duration. + * Displays the Failed To Connect notification after the Connecting notification is shown for + * {@link #TIME_TO_SHOW_CONNECTING_MILLIS} duration. */ private void showFailedToConnectNotification() { Notification notification = mWifiNotificationHelper.createFailedToConnectNotification(mRecommendedNetwork); postNotification(notification); - mHandler.postDelayed(mDismissNotificationRunnable, TIME_TO_SHOW_FAILED_MILLIS); + mNotificationState = State.SHOWING_FAILURE; + mHandler.postDelayed( + () -> { + if (mNotificationState == State.SHOWING_FAILURE) { + removeNotification(); + } + }, + TIME_TO_SHOW_FAILED_MILLIS); } - /** - * Handles behavior when notification is dismissed. - */ + /** Handles behavior when notification is dismissed. */ private void handleNotificationDeleted() { - mNotificationShown = false; + mNotificationState = State.HIDDEN; mRecommendedNetwork = null; mNotificationBadgeBitmap = null; } private void postNotification(Notification notification) { - mNotificationManager.notify(null /* tag */, ICON_NETWORKS_AVAILABLE, notification); + mNotificationManager.notify(NOTIFICATION_TAG, NOTIFICATION_ID, notification); } /** - * Clears variables related to tracking whether a notification has been - * shown recently and clears the current notification. + * Clears variables related to tracking whether a notification has been shown recently and + * clears the current notification. */ private void resetNotification() { - mNotificationRepeatTime = 0; - mNumScansSinceNetworkStateChange = 0; - if (mNotificationShown) { + if (mNotificationState != State.HIDDEN) { removeNotification(); } + mRecommendedNetwork = null; + mNotificationRepeatTime = 0; + mNumScansSinceNetworkStateChange = 0; + mOpenNetworksLoggingRepeatTime = 0; } private void removeNotification() { - mNotificationManager.cancel(null /* tag */, ICON_NETWORKS_AVAILABLE); - mNotificationShown = false; + mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID); + mNotificationState = State.HIDDEN; + mRecommendedNetwork = null; + mNotificationBadgeBitmap = null; } - /** Dump debugging information. */ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("mNotificationEnabled " + mNotificationEnabled); pw.println("mNotificationRepeatTime " + mNotificationRepeatTime); - pw.println("mNotificationShown " + mNotificationShown); + pw.println("mNotificationState " + mNotificationState); pw.println("mNumScansSinceNetworkStateChange " + mNumScansSinceNetworkStateChange); } @@ -435,8 +498,11 @@ public class WifiNotificationController { } public void register() { - mContentResolver.registerContentObserver(Settings.Global.getUriFor( - Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON), true, this); + mContentResolver.registerContentObserver( + Settings.Global.getUriFor( + Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON), + true, + this); mNotificationEnabled = getValue(); } @@ -454,8 +520,10 @@ public class WifiNotificationController { private boolean getValue() { return Settings.Global.getInt( - mContentResolver, - Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0) == 1; + mContentResolver, + Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, + 0) + == 1; } } } diff --git a/src/com/android/networkrecommendation/notify/WifiNotificationHelper.java b/src/com/android/networkrecommendation/notify/WifiNotificationHelper.java index 719e6b4..564bea8 100644 --- a/src/com/android/networkrecommendation/notify/WifiNotificationHelper.java +++ b/src/com/android/networkrecommendation/notify/WifiNotificationHelper.java @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.android.networkrecommendation.notify; import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; +import static com.android.networkrecommendation.Constants.TAG; import android.app.Notification; import android.app.Notification.Action; @@ -27,30 +27,35 @@ import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.drawable.Drawable; -import android.graphics.drawable.LayerDrawable; import android.net.NetworkKey; import android.net.ScoredNetwork; -import android.net.WifiKey; import android.net.wifi.ScanResult; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiManager; import android.os.Bundle; +import android.provider.Settings; import android.support.annotation.NonNull; +import android.support.annotation.VisibleForTesting; import android.text.Spannable; import android.text.SpannableString; import android.text.style.ForegroundColorSpan; - import com.android.networkrecommendation.R; import com.android.networkrecommendation.SynchronousNetworkRecommendationProvider; +import com.android.networkrecommendation.util.Blog; import com.android.networkrecommendation.util.ImageUtil; +import com.android.networkrecommendation.util.RoboCompatUtil; +import com.android.networkrecommendation.util.ScanResultUtil; import com.android.networkrecommendation.util.WifiConfigurationUtil; - import java.util.List; +import javax.annotation.Nullable; -/** - * Helper class that creates notifications for {@link WifiNotificationController}. - */ +/** Helper class that creates notifications for {@link WifiNotificationController}. */ public class WifiNotificationHelper { + + /** This is in reference to the hidden Settings.Global.NETWORK_SCORING_UI_ENABLED */ + @VisibleForTesting + static final String NETWORK_SCORING_UI_ENABLED = "network_scoring_ui_enabled"; + private final Context mContext; private final SynchronousNetworkRecommendationProvider mCachedScoredNetworkProvider; @@ -61,28 +66,36 @@ public class WifiNotificationHelper { } /** - * Creates the main open networks notification with two actions. "Options" link to the - * Wi-Fi picker activity, and "Connect" prompts {@link WifiNotificationController} - * to connect to the recommended network. + * Creates the main open networks notification with two actions. "Options" link to the Wi-Fi + * picker activity, and "Connect" prompts {@link WifiNotificationController} to connect to the + * recommended network. */ public Notification createMainNotification(WifiConfiguration config, Bitmap badge) { - PendingIntent optionsIntent = PendingIntent.getActivity( - mContext, 0, new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK), FLAG_UPDATE_CURRENT); - Action optionsAction = new Action.Builder( - null /* icon */, - mContext.getText(R.string.wifi_available_options), - optionsIntent) - .build(); - PendingIntent connectIntent = PendingIntent.getBroadcast( - mContext, - 0, - new Intent(WifiNotificationController.ACTION_CONNECT_TO_RECOMMENDED_NETWORK), - FLAG_UPDATE_CURRENT); - Action connectAction = new Action.Builder( - null /* icon */, - mContext.getText(R.string.wifi_available_connect), - connectIntent) - .build(); + PendingIntent optionsIntent = + PendingIntent.getBroadcast( + mContext, + 0, + new Intent(WifiNotificationController.ACTION_PICK_WIFI_NETWORK), + FLAG_UPDATE_CURRENT); + Action optionsAction = + new Action.Builder( + null /* icon */, + mContext.getText(R.string.wifi_available_options), + optionsIntent) + .build(); + PendingIntent connectIntent = + PendingIntent.getBroadcast( + mContext, + 0, + new Intent( + WifiNotificationController.ACTION_CONNECT_TO_RECOMMENDED_NETWORK), + FLAG_UPDATE_CURRENT); + Action connectAction = + new Action.Builder( + null /* icon */, + mContext.getText(R.string.wifi_available_connect), + connectIntent) + .build(); return createNotificationBuilder(config, badge) .addAction(connectAction) .addAction(optionsAction) @@ -90,15 +103,16 @@ public class WifiNotificationHelper { } /** - * Creates the notification that indicates the controller is attempting to connect - * to the recommended network. + * Creates the notification that indicates the controller is attempting to connect to the + * recommended network. */ public Notification createConnectingNotification(WifiConfiguration config, Bitmap badge) { - Action connecting = new Action.Builder( - null /* icon */, - mContext.getText(R.string.wifi_available_connecting), - null /* pendingIntent */) - .build(); + Action connecting = + new Action.Builder( + null /* icon */, + mContext.getText(R.string.common_connecting), + null /* pendingIntent */) + .build(); return createNotificationBuilder(config, badge) .addAction(connecting) .setProgress(0 /* max */, 0 /* progress */, true /* indeterminate */) @@ -106,23 +120,22 @@ public class WifiNotificationHelper { } /** - * Creates the notification that indicates the controller successfully connected - * to the recommended network. + * Creates the notification that indicates the controller successfully connected to the + * recommended network. */ public Notification createConnectedNotification(WifiConfiguration config, Bitmap badge) { - Action connected = new Action.Builder( - null /* icon */, - mContext.getText(R.string.wifi_available_connected), - null /* pendingIntent */) - .build(); - return createNotificationBuilder(config, badge) - .addAction(connected) - .build(); + Action connected = + new Action.Builder( + null /* icon */, + mContext.getText(R.string.wifi_available_connected), + null /* pendingIntent */) + .build(); + return createNotificationBuilder(config, badge).addAction(connected).build(); } /** - * Creates the notification that indicates the controller failed to connect to - * the recommended network. + * Creates the notification that indicates the controller failed to connect to the recommended + * network. */ public Notification createFailedToConnectNotification(WifiConfiguration config) { Spannable failedText = @@ -130,83 +143,75 @@ public class WifiNotificationHelper { Resources resources = mContext.getResources(); Drawable iconDrawable = mContext.getDrawable(R.drawable.ic_signal_wifi_no_network); iconDrawable.setTint(mContext.getColor(R.color.color_tint)); - Bitmap icon = ImageUtil.buildScaledBitmap( - iconDrawable, - resources.getDimensionPixelSize(android.R.dimen.notification_large_icon_width), - resources.getDimensionPixelSize(android.R.dimen.notification_large_icon_height)); - failedText.setSpan(new ForegroundColorSpan( - Color.RED), 0, failedText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - return createNotificationBuilder(config, icon) - .setContentText(failedText) - .build(); + Bitmap icon = + ImageUtil.buildScaledBitmap( + iconDrawable, + resources.getDimensionPixelSize( + android.R.dimen.notification_large_icon_width), + resources.getDimensionPixelSize( + android.R.dimen.notification_large_icon_height)); + failedText.setSpan( + new ForegroundColorSpan(Color.RED), + 0, + failedText.length(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + return createNotificationBuilder(config, icon).setContentText(failedText).build(); } private Notification.Builder createNotificationBuilder(WifiConfiguration config, Bitmap badge) { CharSequence title = mContext.getText(R.string.wifi_available); - PendingIntent deleteIntent = PendingIntent.getBroadcast( - mContext, - 0, - new Intent(WifiNotificationController.ACTION_NOTIFICATION_DELETED), - FLAG_UPDATE_CURRENT); + PendingIntent deleteIntent = + PendingIntent.getBroadcast( + mContext, + 0, + new Intent(WifiNotificationController.ACTION_NOTIFICATION_DELETED), + FLAG_UPDATE_CURRENT); return new Notification.Builder(mContext) .setDeleteIntent(deleteIntent) - .setSmallIcon(R.drawable.stat_notify_wifi_in_range) + .setSmallIcon(R.drawable.ic_signal_wifi_badged_4_bars) .setLargeIcon(badge) .setAutoCancel(true) .setTicker(title) .setContentTitle(title) + .setColor(mContext.getColor(R.color.color_tint)) .setContentText(WifiConfigurationUtil.getPrintableSsid(config)) .addExtras(getSystemLabelExtras()); } private Bundle getSystemLabelExtras() { Bundle extras = new Bundle(); - extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, + extras.putString( + Notification.EXTRA_SUBSTITUTE_APP_NAME, mContext.getString(R.string.android_system_label)); return extras; } - //TODO(34177812): Share this logic between systemUi and Settings. - static final int[] WIFI_PIE_FOR_BADGING = { - R.drawable.ic_signal_wifi_badged_0_bars, - R.drawable.ic_signal_wifi_badged_1_bar, - R.drawable.ic_signal_wifi_badged_2_bars, - R.drawable.ic_signal_wifi_badged_3_bars, - R.drawable.ic_signal_wifi_badged_4_bars - }; - - private int getWifiBadgeResourceForEnum(int badgeEnum) { - switch (badgeEnum) { - case ScoredNetwork.BADGING_NONE: - return 0; - case ScoredNetwork.BADGING_SD: - return R.drawable.ic_signal_wifi_badged_sd; - case ScoredNetwork.BADGING_HD: - return R.drawable.ic_signal_wifi_badged_hd; - case ScoredNetwork.BADGING_4K: - return R.drawable.ic_signal_wifi_badged_4k; - default: - throw new IllegalArgumentException("No badge resource for enum :" + badgeEnum); - } - } - /** - * Creates a Wi-Fi badge for the notification using matching {@link ScanResult}'s RSSI - * and badging from {@link CachedScoredNetworkProvider}. + * Creates a Wi-Fi badge for the notification using matching {@link ScanResult}'s RSSI and + * badging from {@link CachedScoredNetworkProvider}. */ + @Nullable public Bitmap createNotificationBadgeBitmap( - @NonNull WifiConfiguration config, - @NonNull List<ScanResult> scanResults) { + @NonNull WifiConfiguration config, @NonNull List<ScanResult> scanResults) { ScanResult matchingScanResult = findMatchingScanResult(scanResults, config); if (matchingScanResult == null) { return null; } int rssi = matchingScanResult.level; - WifiKey wifiKey = new WifiKey(config.SSID, config.BSSID); + + NetworkKey networkKey; + try { + networkKey = ScanResultUtil.createNetworkKey(matchingScanResult); + } catch (IllegalArgumentException e) { + Blog.e(TAG, e, "Error creating NetworkKey."); + return null; + } + ScoredNetwork scoredNetwork = - mCachedScoredNetworkProvider.getCachedScoredNetwork(new NetworkKey(wifiKey)); + mCachedScoredNetworkProvider.getCachedScoredNetwork(networkKey); if (scoredNetwork != null) { - return getBadgedWifiBitmap(scoredNetwork.calculateBadge(rssi), rssi); + return getBadgedWifiBitmap( + RoboCompatUtil.getInstance().calculateBadge(scoredNetwork, rssi), rssi); } return null; } @@ -215,23 +220,26 @@ public class WifiNotificationHelper { if (badgeEnum == ScoredNetwork.BADGING_NONE) { return null; } + if (Settings.Global.getInt(mContext.getContentResolver(), NETWORK_SCORING_UI_ENABLED, 0) + == 0) { + badgeEnum = ScoredNetwork.BADGING_NONE; + } int signalLevel = WifiManager.calculateSignalLevel(rssi, 5); - LayerDrawable layerDrawable = new LayerDrawable(new Drawable[]{ - mContext.getDrawable(WIFI_PIE_FOR_BADGING[signalLevel]), - mContext.getDrawable(getWifiBadgeResourceForEnum(badgeEnum))}); - layerDrawable.setTint(mContext.getColor(R.color.color_tint)); + Drawable drawable = + RoboCompatUtil.getInstance() + .getWifiIcon(signalLevel, badgeEnum, mContext.getTheme()); + drawable.setTint(mContext.getColor(R.color.color_tint)); Resources resources = mContext.getResources(); - return ImageUtil.buildScaledBitmap(layerDrawable, + return ImageUtil.buildScaledBitmap( + drawable, resources.getDimensionPixelSize(android.R.dimen.notification_large_icon_width), resources.getDimensionPixelSize(android.R.dimen.notification_large_icon_height)); } - private ScanResult findMatchingScanResult(List<ScanResult> scanResults, - WifiConfiguration wifiConfiguration) { - String ssid = WifiConfigurationUtil.removeDoubleQuotes(wifiConfiguration); - String bssid = wifiConfiguration.BSSID; + private static ScanResult findMatchingScanResult( + List<ScanResult> scanResults, WifiConfiguration wifiConfiguration) { for (ScanResult scanResult : scanResults) { - if (ssid.equals(scanResult.SSID) && bssid.equals(scanResult.BSSID)) { + if (ScanResultUtil.doesScanResultMatchWithNetwork(scanResult, wifiConfiguration)) { return scanResult; } } diff --git a/src/com/android/networkrecommendation/util/RoboCompatUtil.java b/src/com/android/networkrecommendation/util/RoboCompatUtil.java index a6d3130..686c83a 100644 --- a/src/com/android/networkrecommendation/util/RoboCompatUtil.java +++ b/src/com/android/networkrecommendation/util/RoboCompatUtil.java @@ -26,7 +26,6 @@ import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiManager; import android.os.Bundle; import android.support.annotation.VisibleForTesting; - import java.util.List; /** @@ -65,11 +64,15 @@ public class RoboCompatUtil { wifiManager.connect(wifiConfiguration, null /* actionListener */); } - /** Wraps WifiConfiguration.hasNoInternet and WifiConfiguration.isNoInternetAccessExpected. */ + /** Wraps WifiConfiguration.hasNoInternetAccess. */ @SuppressWarnings("unchecked") public boolean hasNoInternetAccess(WifiConfiguration wifiConfiguration) { - return wifiConfiguration.hasNoInternetAccess() - || wifiConfiguration.isNoInternetAccessExpected(); + return wifiConfiguration.hasNoInternetAccess(); + } + + /** Wraps WifiConfiguration.isNoInternetAccessExpected. */ + public boolean isNoInternetAccessExpected(WifiConfiguration wifiConfiguration) { + return wifiConfiguration.isNoInternetAccessExpected(); } /** Wraps WifiConfiguration.useExternalScores. */ diff --git a/src/com/android/networkrecommendation/util/ScanResultUtil.java b/src/com/android/networkrecommendation/util/ScanResultUtil.java index f5117df..4587e91 100644 --- a/src/com/android/networkrecommendation/util/ScanResultUtil.java +++ b/src/com/android/networkrecommendation/util/ScanResultUtil.java @@ -24,7 +24,6 @@ import android.net.wifi.ScanResult; import android.net.wifi.WifiConfiguration; import android.support.annotation.Nullable; import android.text.TextUtils; - import com.android.networkrecommendation.config.G; /** diff --git a/tests/src/com/android/networkrecommendation/notify/WifiNotificationControllerTest.java b/tests/src/com/android/networkrecommendation/notify/WifiNotificationControllerTest.java deleted file mode 100644 index 26dd56b..0000000 --- a/tests/src/com/android/networkrecommendation/notify/WifiNotificationControllerTest.java +++ /dev/null @@ -1,269 +0,0 @@ -/* - * 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.networkrecommendation.notify; - -import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.anyInt; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.app.Notification; -import android.app.NotificationManager; -import android.content.BroadcastReceiver; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.graphics.Bitmap; -import android.net.NetworkInfo; -import android.net.RecommendationRequest; -import android.net.RecommendationResult; -import android.net.wifi.ScanResult; -import android.net.wifi.WifiConfiguration; -import android.net.wifi.WifiManager; -import android.net.wifi.WifiManager.ActionListener; -import android.os.Handler; -import android.os.Looper; -import android.os.UserHandle; -import android.provider.Settings; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; - -import com.android.networkrecommendation.SynchronousNetworkRecommendationProvider; -import com.android.networkrecommendation.TestUtil; - -import org.junit.After; -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 java.util.ArrayList; -import java.util.List; - -/** - * Unit tests for {@link WifiNotificationController}. - */ -@RunWith(AndroidJUnit4.class) -public class WifiNotificationControllerTest { - @Mock private Context mContext; - @Mock private WifiManager mWifiManager; - @Mock private NotificationManager mNotificationManager; - @Mock private WifiNotificationHelper mWifiNotificationHelper; - @Mock private SynchronousNetworkRecommendationProvider mNetworkRecommendationProvider; - private ContentResolver mContentResolver; - private Handler mHandler; - private WifiNotificationController mWifiNotificationController; - private int mNotificationsEnabledOriginalValue; - - /** - * Internal BroadcastReceiver that WifiNotificationController uses to listen for broadcasts - * this is initialized by calling startServiceAndLoadDriver - */ - private BroadcastReceiver mBroadcastReceiver; - - /** Initialize objects before each test run. */ - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - - // Needed for the NotificationEnabledSettingObserver. - mContentResolver = InstrumentationRegistry.getTargetContext().getContentResolver(); - mNotificationsEnabledOriginalValue = - Settings.Global.getInt(mContentResolver, - Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0); - Settings.Global.putInt(mContentResolver, - Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 1); - mHandler = new Handler(Looper.getMainLooper()); - - mWifiNotificationController = new WifiNotificationController( - mContext, mContentResolver, mHandler, mNetworkRecommendationProvider, - mWifiManager, mNotificationManager, mWifiNotificationHelper); - mWifiNotificationController.start(); - - ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = - ArgumentCaptor.forClass(BroadcastReceiver.class); - verify(mContext) - .registerReceiver(broadcastReceiverCaptor.capture(), any(IntentFilter.class), - anyString(), any(Handler.class)); - mBroadcastReceiver = broadcastReceiverCaptor.getValue(); - } - - @After - public void tearDown() throws Exception { - Settings.Global.putInt(mContentResolver, - Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, - mNotificationsEnabledOriginalValue); - } - - private void setOpenAccessPoints(int numAccessPoints) { - List<ScanResult> scanResults = new ArrayList<>(); - for (int i = 0; i < numAccessPoints; i++) { - ScanResult scanResult = createScanResult("testSSID" + i, "00:00:00:00:00:00"); - scanResults.add(scanResult); - } - when(mWifiManager.getScanResults()).thenReturn(scanResults); - } - - private ScanResult createScanResult(String ssid, String bssid) { - ScanResult scanResult = new ScanResult(); - scanResult.capabilities = "[ESS]"; - scanResult.SSID = ssid; - scanResult.BSSID = bssid; - return scanResult; - } - - private WifiConfiguration createFakeConfig() { - WifiConfiguration config = new WifiConfiguration(); - config.SSID = "\"TestSsid\""; - config.BSSID = "00:00:00:00:00:00"; - config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); - return config; - } - - private void createFakeBitmap() { - when(mWifiNotificationHelper.createNotificationBadgeBitmap(any(), any())) - .thenReturn(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)); - } - - /** Verifies that a notification is displayed (and retracted) given system events. */ - @Test - public void verifyNotificationDisplayedWhenNetworkRecommended() throws Exception { - when(mWifiManager.getWifiState()).thenReturn(WifiManager.WIFI_STATE_ENABLED); - - TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext); - TestUtil.sendNetworkStateChanged(mBroadcastReceiver, mContext, - NetworkInfo.DetailedState.DISCONNECTED); - setOpenAccessPoints(3); - createFakeBitmap(); - - when(mNetworkRecommendationProvider.requestRecommendation(any(RecommendationRequest.class))) - .thenReturn( - RecommendationResult.createConnectRecommendation(createFakeConfig())); - - // The notification should not be displayed after only two scan results. - TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext); - TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext); - verify(mNotificationManager, never()) - .notify(any(String.class), anyInt(), any(Notification.class)); - - // Changing to and from "SCANNING" state should not affect the counter. - TestUtil.sendNetworkStateChanged(mBroadcastReceiver, mContext, - NetworkInfo.DetailedState.SCANNING); - TestUtil.sendNetworkStateChanged(mBroadcastReceiver, mContext, - NetworkInfo.DetailedState.DISCONNECTED); - verify(mNotificationManager, never()) - .notify(any(String.class), anyInt(), any(Notification.class)); - // The third scan result notification will trigger the notification. - TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext); - verify(mWifiNotificationHelper).createMainNotification(any(WifiConfiguration.class), - any(Bitmap.class)); - verify(mNotificationManager) - .notify(any(String.class), anyInt(), any(Notification.class)); - verify(mNotificationManager, never()) - .cancelAsUser(any(String.class), anyInt(), any(UserHandle.class)); - } - - /** Verifies that a notification is not displayed for bad networks. */ - @Test - public void verifyNotificationNotDisplayedWhenNoNetworkRecommended() throws Exception { - when(mWifiManager.getWifiState()).thenReturn(WifiManager.WIFI_STATE_ENABLED); - - TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext); - TestUtil.sendNetworkStateChanged(mBroadcastReceiver, mContext, - NetworkInfo.DetailedState.DISCONNECTED); - setOpenAccessPoints(3); - createFakeBitmap(); - - // Recommendation result with no WifiConfiguration returned. - when(mNetworkRecommendationProvider.requestRecommendation(any(RecommendationRequest.class))) - .thenReturn(RecommendationResult.createDoNotConnectRecommendation()); - - TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext); - TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext); - TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext); - TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext); - verify(mNotificationManager, never()) - .notify(any(String.class), anyInt(), any(Notification.class)); - - // DoNotConnect Recommendation result. - when(mNetworkRecommendationProvider.requestRecommendation(any(RecommendationRequest.class))) - .thenReturn(RecommendationResult.createDoNotConnectRecommendation()); - TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext); - verify(mNotificationManager, never()) - .notify(any(String.class), anyInt(), any(Notification.class)); - } - - /** - * Verifies the notifications flow (Connect -> connecting -> connected) when user clicks - * on Connect button. - */ - @Test - public void verifyNotificationsFlowOnConnectToNetwork() { - when(mWifiManager.getWifiState()).thenReturn(WifiManager.WIFI_STATE_ENABLED); - - TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext); - TestUtil.sendNetworkStateChanged(mBroadcastReceiver, mContext, - NetworkInfo.DetailedState.DISCONNECTED); - setOpenAccessPoints(3); - createFakeBitmap(); - when(mNetworkRecommendationProvider.requestRecommendation(any(RecommendationRequest.class))) - .thenReturn( - RecommendationResult.createConnectRecommendation(createFakeConfig())); - - TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext); - TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext); - TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext); - verify(mWifiNotificationHelper).createMainNotification(any(WifiConfiguration.class), - any(Bitmap.class)); - verify(mNotificationManager) - .notify(any(String.class), anyInt(), any(Notification.class)); - - // Send connect intent, should attempt to connect to Wi-Fi - Intent intent = new Intent( - WifiNotificationController.ACTION_CONNECT_TO_RECOMMENDED_NETWORK); - mBroadcastReceiver.onReceive(mContext, intent); - verify(mWifiManager).connect(any(WifiConfiguration.class), any(ActionListener.class)); - verify(mWifiNotificationHelper).createConnectingNotification(any(WifiConfiguration.class), - any(Bitmap.class)); - - // Show connecting notification. - verify(mNotificationManager, times(2)) - .notify(any(String.class), anyInt(), any(Notification.class)); - // Verify callback to dismiss connecting notification exists. - assertTrue(mHandler.hasCallbacks( - mWifiNotificationController.mShowFailedToConnectNotificationRunnable)); - - // Verify show connected notification. - TestUtil.sendNetworkStateChanged(mBroadcastReceiver, mContext, - NetworkInfo.DetailedState.CONNECTED); - verify(mWifiNotificationHelper).createConnectedNotification(any(WifiConfiguration.class), - any(Bitmap.class)); - verify(mNotificationManager, times(3)) - .notify(any(String.class), anyInt(), any(Notification.class)); - - // Verify callback to dismiss connected notification exists. - assertTrue(mHandler.hasCallbacks( - mWifiNotificationController.mDismissNotificationRunnable)); - } -} |