diff options
author | Joe LaPenna <jlapenna@google.com> | 2017-03-30 23:31:52 +0000 |
---|---|---|
committer | android-build-merger <android-build-merger@google.com> | 2017-03-30 23:31:52 +0000 |
commit | f355ffa3106e05b70cef0b45b2bc6261d2b6eeee (patch) | |
tree | 41b378cfbd250827874d04c25e9d51fdc2f7be57 | |
parent | 019e6518fe5e95880703ca43b7a5f2e81437319f (diff) | |
parent | 569d8792466fae368bf56b75b8506f1933ca2522 (diff) | |
download | NetworkRecommendation-f355ffa3106e05b70cef0b45b2bc6261d2b6eeee.tar.gz |
Update netrec. am: 69639e9bb9
am: 569d879246
Change-Id: Id52b9cd7493b9f2cfc2ae2b352b1c7faa45c2311
26 files changed, 1099 insertions, 456 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 0b21a24..3c61f98 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -27,7 +27,8 @@ <uses-permission android:name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/> <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/> - <application android:allowBackup="false" + <application android:name=".NetworkRecommendationApp" + android:allowBackup="false" android:directBootAware="true"> <!-- Only the platform can bind to this service --> @@ -35,6 +36,8 @@ android:permission="android.permission.BIND_NETWORK_RECOMMENDATION_SERVICE"> <meta-data android:name="android.net.scoring.recommendation_service_label" android:value="@string/network_recommendation_service_label"/> + <meta-data android:name="android.net.wifi.notification_channel_id_network_available" + android:value="com.android.networkrecommendation.Notifications.WifiMessageGroup.NetworkAvailableChannel" /> <intent-filter> <action android:name="android.net.action.RECOMMEND_NETWORKS" /> </intent-filter> diff --git a/res/values/config.xml b/res/values/config.xml index 3762e90..4167ebd 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -18,9 +18,13 @@ <integer translatable="false" name="config_netrec_5GHz_preference_boost_factor">40</integer> <integer translatable="false" name="config_netrec_RSSI_SCORE_OFFSET">85</integer> <integer translatable="false" name="config_netrec_RSSI_SCORE_SLOPE">4</integer> + <integer translatable="false" name="config_netrec_SAME_BSSID_AWARD">24</integer> + <integer translatable="false" name="config_netrec_LAST_SELECTION_AWARD">480</integer> <integer translatable="false" name="config_netrec_PASSPOINT_SECURITY_AWARD">40</integer> <integer translatable="false" name="config_netrec_SECURITY_AWARD">80</integer> <integer translatable="false" name="config_netrec_wifi_score_low_rssi_threshold_5GHz">-70</integer> <integer translatable="false" name="config_netrec_wifi_score_low_rssi_threshold_24GHz">-73</integer> <integer translatable="false" name="config_netrec_wifi_score_good_rssi_threshold_24GHz">-60</integer> + <integer translatable="false" name="config_netrec_score_good_rssi_threshold">-60</integer> + <integer translatable="false" name="config_netrec_current_network_boost">16</integer> </resources> diff --git a/res/values/strings.xml b/res/values/strings.xml index 4916767..e208b4c 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -18,18 +18,6 @@ <!-- The service label for network recommendation service. [CHAR LIMIT=40] --> <string name="network_recommendation_service_label">System default</string> - <!-- A notification is shown when there is a high quality open wireless network nearby. This is the notification's title. --> - <string name="wifi_available">Connect to Wi\u2011Fi Network</string> - - <!-- A notification is shown when there is a high quality open wireless network nearby. These are the different notification actions. --> - <string name="wifi_available_options">Options</string> - <string name="wifi_available_connect">Connect</string> - <string name="wifi_available_connecting">Connecting</string> - <string name="wifi_available_connected">Connected</string> - <string name="wifi_available_failed">FAILED TO CONNECT</string> - - <string name="android_system_label">Android System</string> - <!-- Notification title when there is a high quality open wireless network nearby.--> <string name="wifi_available_title">Connect to open Wi\u2011Fi network</string> <!-- Notification title when the system is connecting to a high quality open network. --> @@ -52,4 +40,11 @@ <!-- Text used to indicate that a connection attempt is ongoing [CHAR LIMIT=20] --> <string name="common_connecting">Connecting…</string> + + <!-- Name of the notification channel group of our Wi-Fi messages --> + <string name="notification_channel_group_name">Wi\u2011Fi messages</string> + <!-- Name of the notification channel for a notification shown when there is a high quality open wireless network nearby. --> + <string name="notification_channel_wakeup_name">Wi\u2011Fi turned on</string> + <!-- Name of the notification channel for a notification shown when Wi-Fi Wakeup enables Wi-Fi.. --> + <string name="notification_channel_network_available">Network available</string> </resources> diff --git a/robotests/src/com/android/networkrecommendation/notify/WifiNotificationControllerTest.java b/robotests/src/com/android/networkrecommendation/notify/WifiNotificationControllerTest.java index 943ffc7..3f4828f 100644 --- a/robotests/src/com/android/networkrecommendation/notify/WifiNotificationControllerTest.java +++ b/robotests/src/com/android/networkrecommendation/notify/WifiNotificationControllerTest.java @@ -30,7 +30,6 @@ 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; @@ -129,11 +128,6 @@ public class WifiNotificationControllerTest { 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. @@ -157,7 +151,6 @@ public class WifiNotificationControllerTest { when(mNetworkInfo.getDetailedState()).thenReturn(DetailedState.DISCONNECTED); mBroadcastIntentTestHelper.sendNetworkStateChanged(mNetworkInfo); setOpenAccessPoints(); - createFakeBitmap(); when(mNetworkRecommendationProvider.requestRecommendation(any(RecommendationRequest.class))) .thenReturn(RecommendationResult.createConnectRecommendation(createFakeConfig())); @@ -182,8 +175,7 @@ public class WifiNotificationControllerTest { // The third scan result notification will trigger the notification. mBroadcastIntentTestHelper.sendScanResultsAvailable(); - verify(mWifiNotificationHelper) - .createMainNotification(any(WifiConfiguration.class), any(Bitmap.class)); + verify(mWifiNotificationHelper).createMainNotification(any(WifiConfiguration.class)); verify(mNotificationManager).notify(anyString(), anyInt(), any(Notification.class)); verify(mNotificationManager, never()).cancel(anyString(), anyInt()); } @@ -197,7 +189,6 @@ public class WifiNotificationControllerTest { when(mNetworkInfo.getDetailedState()).thenReturn(DetailedState.DISCONNECTED); mBroadcastIntentTestHelper.sendNetworkStateChanged(mNetworkInfo); setOpenAccessPoints(); - createFakeBitmap(); // Recommendation result with no WifiConfiguration returned. when(mNetworkRecommendationProvider.requestRecommendation(any(RecommendationRequest.class))) @@ -223,7 +214,6 @@ public class WifiNotificationControllerTest { when(mNetworkInfo.getDetailedState()).thenReturn(DetailedState.DISCONNECTED); mBroadcastIntentTestHelper.sendNetworkStateChanged(mNetworkInfo); setOpenAccessPoints(); - createFakeBitmap(); when(mNetworkRecommendationProvider.requestRecommendation(any(RecommendationRequest.class))) .thenReturn(RecommendationResult.createConnectRecommendation(createFakeConfig())); @@ -231,8 +221,7 @@ public class WifiNotificationControllerTest { mBroadcastIntentTestHelper.sendScanResultsAvailable(); mBroadcastIntentTestHelper.sendScanResultsAvailable(); mBroadcastIntentTestHelper.sendScanResultsAvailable(); - verify(mWifiNotificationHelper) - .createMainNotification(any(WifiConfiguration.class), any(Bitmap.class)); + verify(mWifiNotificationHelper).createMainNotification(any(WifiConfiguration.class)); verify(mNotificationManager).notify(anyString(), anyInt(), any(Notification.class)); // Send connect intent, should attempt to connect to Wi-Fi @@ -240,8 +229,7 @@ public class WifiNotificationControllerTest { 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)); + verify(mWifiNotificationHelper).createConnectingNotification(any(WifiConfiguration.class)); // Show connecting notification. verify(mNotificationManager, times(2)) @@ -250,8 +238,7 @@ public class WifiNotificationControllerTest { // Verify show connected notification. when(mNetworkInfo.getDetailedState()).thenReturn(DetailedState.CONNECTED); mBroadcastIntentTestHelper.sendNetworkStateChanged(mNetworkInfo); - verify(mWifiNotificationHelper) - .createConnectedNotification(any(WifiConfiguration.class), any(Bitmap.class)); + verify(mWifiNotificationHelper).createConnectedNotification(any(WifiConfiguration.class)); verify(mNotificationManager, times(3)) .notify(anyString(), anyInt(), any(Notification.class)); @@ -269,7 +256,6 @@ public class WifiNotificationControllerTest { when(mNetworkInfo.getDetailedState()).thenReturn(DetailedState.DISCONNECTED); mBroadcastIntentTestHelper.sendNetworkStateChanged(mNetworkInfo); setOpenAccessPoints(); - createFakeBitmap(); when(mNetworkRecommendationProvider.requestRecommendation(any(RecommendationRequest.class))) .thenReturn(RecommendationResult.createConnectRecommendation(createFakeConfig())); @@ -277,8 +263,7 @@ public class WifiNotificationControllerTest { mBroadcastIntentTestHelper.sendScanResultsAvailable(); mBroadcastIntentTestHelper.sendScanResultsAvailable(); mBroadcastIntentTestHelper.sendScanResultsAvailable(); - verify(mWifiNotificationHelper) - .createMainNotification(any(WifiConfiguration.class), any(Bitmap.class)); + verify(mWifiNotificationHelper).createMainNotification(any(WifiConfiguration.class)); verify(mNotificationManager).notify(anyString(), anyInt(), any(Notification.class)); // Send connect intent, should attempt to connect to Wi-Fi @@ -286,8 +271,7 @@ public class WifiNotificationControllerTest { 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)); + verify(mWifiNotificationHelper).createConnectingNotification(any(WifiConfiguration.class)); // Show connecting notification. verify(mNotificationManager, times(2)) @@ -295,8 +279,7 @@ public class WifiNotificationControllerTest { // Show failed to connect notification. ShadowLooper.runMainLooperToNextTask(); - verify(mWifiNotificationHelper) - .createFailedToConnectNotification(any(WifiConfiguration.class)); + verify(mWifiNotificationHelper).createFailedToConnectNotification(); // Dismissed the cancel notification. ShadowLooper.runMainLooperToNextTask(); @@ -312,7 +295,6 @@ public class WifiNotificationControllerTest { when(mNetworkInfo.getDetailedState()).thenReturn(DetailedState.DISCONNECTED); mBroadcastIntentTestHelper.sendNetworkStateChanged(mNetworkInfo); setOpenAccessPoints(); - createFakeBitmap(); when(mNetworkRecommendationProvider.requestRecommendation(any(RecommendationRequest.class))) .thenReturn(RecommendationResult.createConnectRecommendation(createFakeConfig())); @@ -322,8 +304,7 @@ public class WifiNotificationControllerTest { mBroadcastIntentTestHelper.sendScanResultsAvailable(); // Show main notification - verify(mWifiNotificationHelper) - .createMainNotification(any(WifiConfiguration.class), any(Bitmap.class)); + verify(mWifiNotificationHelper).createMainNotification(any(WifiConfiguration.class)); verify(mNotificationManager).notify(anyString(), anyInt(), any(Notification.class)); // Send dismiss intent @@ -340,7 +321,6 @@ public class WifiNotificationControllerTest { when(mNetworkInfo.getDetailedState()).thenReturn(DetailedState.DISCONNECTED); mBroadcastIntentTestHelper.sendNetworkStateChanged(mNetworkInfo); setOpenAccessPoints(); - createFakeBitmap(); when(mNetworkRecommendationProvider.requestRecommendation(any(RecommendationRequest.class))) .thenReturn(RecommendationResult.createConnectRecommendation(createFakeConfig())); @@ -350,8 +330,7 @@ public class WifiNotificationControllerTest { mBroadcastIntentTestHelper.sendScanResultsAvailable(); // Show main notification - verify(mWifiNotificationHelper) - .createMainNotification(any(WifiConfiguration.class), any(Bitmap.class)); + verify(mWifiNotificationHelper).createMainNotification(any(WifiConfiguration.class)); verify(mNotificationManager).notify(anyString(), anyInt(), any(Notification.class)); // Send click settings intent @@ -369,7 +348,6 @@ public class WifiNotificationControllerTest { when(mNetworkInfo.getDetailedState()).thenReturn(DetailedState.DISCONNECTED); mBroadcastIntentTestHelper.sendNetworkStateChanged(mNetworkInfo); setOpenAccessPoints(); - createFakeBitmap(); when(mNetworkRecommendationProvider.requestRecommendation(any(RecommendationRequest.class))) .thenReturn(RecommendationResult.createConnectRecommendation(createFakeConfig())); @@ -379,8 +357,7 @@ public class WifiNotificationControllerTest { mBroadcastIntentTestHelper.sendScanResultsAvailable(); // Show main notification - verify(mWifiNotificationHelper) - .createMainNotification(any(WifiConfiguration.class), any(Bitmap.class)); + verify(mWifiNotificationHelper).createMainNotification(any(WifiConfiguration.class)); verify(mNotificationManager).notify(anyString(), anyInt(), any(Notification.class)); when(mNetworkInfo.getDetailedState()).thenReturn(DetailedState.CAPTIVE_PORTAL_CHECK); @@ -426,7 +403,6 @@ public class WifiNotificationControllerTest { when(mNetworkInfo.getDetailedState()).thenReturn(DetailedState.DISCONNECTED); mBroadcastIntentTestHelper.sendNetworkStateChanged(mNetworkInfo); setOpenAccessPoints(); - createFakeBitmap(); when(mNetworkRecommendationProvider.requestRecommendation(any(RecommendationRequest.class))) .thenReturn(RecommendationResult.createConnectRecommendation(createFakeConfig())); @@ -437,8 +413,7 @@ public class WifiNotificationControllerTest { verify(mNetworkRecommendationProvider, times(3)).requestRecommendation(any()); // Show main notification - verify(mWifiNotificationHelper) - .createMainNotification(any(WifiConfiguration.class), any(Bitmap.class)); + verify(mWifiNotificationHelper).createMainNotification(any(WifiConfiguration.class)); verify(mNotificationManager).notify(anyString(), anyInt(), any(Notification.class)); // Send connect intent, should attempt to connect to Wi-Fi @@ -446,8 +421,7 @@ public class WifiNotificationControllerTest { 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)); + verify(mWifiNotificationHelper).createConnectingNotification(any(WifiConfiguration.class)); verify(mNotificationManager, times(2)) .notify(anyString(), anyInt(), any(Notification.class)); @@ -465,7 +439,6 @@ public class WifiNotificationControllerTest { when(mNetworkInfo.getDetailedState()).thenReturn(DetailedState.DISCONNECTED); mBroadcastIntentTestHelper.sendNetworkStateChanged(mNetworkInfo); setOpenAccessPoints(); - createFakeBitmap(); when(mNetworkRecommendationProvider.requestRecommendation(any(RecommendationRequest.class))) .thenReturn(RecommendationResult.createConnectRecommendation(createFakeConfig())); @@ -476,8 +449,7 @@ public class WifiNotificationControllerTest { verify(mNetworkRecommendationProvider, times(3)).requestRecommendation(any()); // Show main notification - verify(mWifiNotificationHelper) - .createMainNotification(any(WifiConfiguration.class), any(Bitmap.class)); + verify(mWifiNotificationHelper).createMainNotification(any(WifiConfiguration.class)); verify(mNotificationManager).notify(anyString(), anyInt(), any(Notification.class)); // Update main notification. diff --git a/robotests/src/com/android/networkrecommendation/notify/WifiNotificationHelperTest.java b/robotests/src/com/android/networkrecommendation/notify/WifiNotificationHelperTest.java index 35856aa..4a37e6c 100644 --- a/robotests/src/com/android/networkrecommendation/notify/WifiNotificationHelperTest.java +++ b/robotests/src/com/android/networkrecommendation/notify/WifiNotificationHelperTest.java @@ -15,42 +15,30 @@ */ 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.NetworkBadging; -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.shadows.ShadowNotificationChannelUtil; +import com.android.networkrecommendation.config.Flag; 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}) +shadows={BitmapGetPixelsShadow.class, ShadowNotificationChannelUtil.class}) public class WifiNotificationHelperTest { private Context mContext; @@ -65,11 +53,11 @@ public class WifiNotificationHelperTest { @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + Flag.initForTest(); RoboCompatUtil.setInstanceForTesting(mRoboCompatUtil); mContext = RuntimeEnvironment.application; - mWifiNotificationHelper = - new WifiNotificationHelper(mContext, mSynchronousNetworkRecommendationProvider); + mWifiNotificationHelper = new WifiNotificationHelper(mContext); } private static WifiConfiguration createFakeConfig() { @@ -80,71 +68,30 @@ public class WifiNotificationHelperTest { 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())); + assertValidNotification(mWifiNotificationHelper.createMainNotification(createFakeConfig())); } @Test public void createConnectingNotification() { assertValidNotification( - mWifiNotificationHelper.createConnectingNotification( - createFakeConfig(), createFakeIcon())); + mWifiNotificationHelper.createConnectingNotification(createFakeConfig())); } @Test public void createConnectedNotification() { assertValidNotification( - mWifiNotificationHelper.createConnectedNotification( - createFakeConfig(), createFakeIcon())); + mWifiNotificationHelper.createConnectedNotification(createFakeConfig())); } @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(NetworkBadging.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)); + assertValidNotification(mWifiNotificationHelper.createFailedToConnectNotification()); } } diff --git a/robotests/src/com/android/networkrecommendation/shadows/BitmapGetPixelsShadow.java b/robotests/src/com/android/networkrecommendation/shadows/BitmapGetPixelsShadow.java index 8912305..c1a8c23 100644 --- a/robotests/src/com/android/networkrecommendation/shadows/BitmapGetPixelsShadow.java +++ b/robotests/src/com/android/networkrecommendation/shadows/BitmapGetPixelsShadow.java @@ -1,3 +1,18 @@ +/* + * 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.shadows; import android.annotation.ColorInt; diff --git a/robotests/src/com/android/networkrecommendation/shadows/ShadowNotificationChannelUtil.java b/robotests/src/com/android/networkrecommendation/shadows/ShadowNotificationChannelUtil.java new file mode 100644 index 0000000..c8e6d26 --- /dev/null +++ b/robotests/src/com/android/networkrecommendation/shadows/ShadowNotificationChannelUtil.java @@ -0,0 +1,42 @@ +/* + * 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.shadows; + +import android.app.Notification.Builder; +import android.app.NotificationManager; +import android.content.Context; +import com.android.networkrecommendation.util.NotificationChannelUtil; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +/** + * A temporary shadow which is only useful until NotificationChannel and NotificationChannelGroup + * classes is available to Robolectric tests. TODO(b/35959851): remove this class. + */ +@Implements(NotificationChannelUtil.class) +public class ShadowNotificationChannelUtil { + + @Implementation + public static void configureNotificationChannels( + NotificationManager notificationManager, Context context) { + // Do nothing + } + + @Implementation + public static Builder setChannel(Builder notificationBuilder, String channelId) { + return notificationBuilder; + } +} diff --git a/robotests/src/com/android/networkrecommendation/util/SsidUtilTest.java b/robotests/src/com/android/networkrecommendation/util/SsidUtilTest.java index e3cdff8..1c1bd07 100644 --- a/robotests/src/com/android/networkrecommendation/util/SsidUtilTest.java +++ b/robotests/src/com/android/networkrecommendation/util/SsidUtilTest.java @@ -15,7 +15,7 @@ */ package com.android.networkrecommendation.util; -import static com.android.networkrecommendation.TestData.NETWORK_KEY1; +import static com.android.networkrecommendation.TestData.BSSID_1; import static com.android.networkrecommendation.TestData.SSID_1; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; @@ -71,12 +71,12 @@ public class SsidUtilTest { @Test public void testRedactedId() { G.Netrec.enableSensitiveLogging.override(false); - assertThat(SsidUtil.getRedactedId(NETWORK_KEY1)).doesNotContain(SSID_1); + assertThat(SsidUtil.getRedactedId(SSID_1, BSSID_1)).doesNotContain(SSID_1); } @Test public void testRedactedId_sensitiveEnabled() { G.Netrec.enableSensitiveLogging.override(true); - assertEquals("\"ssid1\"/01:01:01:01:01:01", SsidUtil.getRedactedId(NETWORK_KEY1)); + assertEquals("\"ssid1\"/01:01:01:01:01:01", SsidUtil.getRedactedId(SSID_1, BSSID_1)); } } diff --git a/robotests/src/com/android/networkrecommendation/wakeup/WifiWakeupControllerTest.java b/robotests/src/com/android/networkrecommendation/wakeup/WifiWakeupControllerTest.java index ac30e8e..01e7db1 100644 --- a/robotests/src/com/android/networkrecommendation/wakeup/WifiWakeupControllerTest.java +++ b/robotests/src/com/android/networkrecommendation/wakeup/WifiWakeupControllerTest.java @@ -15,6 +15,7 @@ */ package com.android.networkrecommendation.wakeup; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.anyList; @@ -36,8 +37,12 @@ import android.os.PowerManager; import android.provider.Settings; import com.android.networkrecommendation.BroadcastIntentTestHelper; import com.android.networkrecommendation.config.Flag; +import com.android.networkrecommendation.config.PreferenceFile; +import com.android.networkrecommendation.config.Preferences; import com.android.networkrecommendation.config.WideAreaNetworks; +import com.android.networkrecommendation.scoring.util.HashUtil; import com.android.networkrecommendation.util.RoboCompatUtil; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -94,6 +99,7 @@ public class WifiWakeupControllerTest { MockitoAnnotations.initMocks(this); Flag.initForTest(); RoboCompatUtil.setInstanceForTesting(mRoboCompatUtil); + PreferenceFile.init(RuntimeEnvironment.application); mSavedWifiConfiguration = new WifiConfiguration(); mSavedWifiConfiguration.SSID = "\"" + SAVED_SCAN_RESULT.SSID + "\""; @@ -117,6 +123,7 @@ public class WifiWakeupControllerTest { Settings.Global.putInt(mContentResolver, Settings.Global.AIRPLANE_MODE_ON, 0); when(mWifiManager.getWifiApState()).thenReturn(WifiManager.WIFI_AP_STATE_DISABLED); ShadowLooper.resetThreadLoopers(); + Preferences.savedSsidsOnDisable.remove(); mWifiWakeupController = new WifiWakeupController( @@ -146,6 +153,44 @@ public class WifiWakeupControllerTest { } /** + * When {@link Settings.Global.WIFI_WAKEUP_ENABLED} is disabled, scan results should not be + * processed. + */ + @Test + public void wifiWakeupControllerStarted_settingDisabled() { + Settings.Global.putInt(mContentResolver, Settings.Global.WIFI_WAKEUP_ENABLED, 0); + when(mWifiManager.getConfiguredNetworks()) + .thenReturn(Lists.newArrayList(mSavedWifiConfiguration, mSavedWifiConfiguration2)); + when(mWifiManager.getScanResults()) + .thenReturn( + Lists.newArrayList(SAVED_SCAN_RESULT), + Lists.newArrayList(SAVED_SCAN_RESULT2)); + when(mWifiWakeupNetworkSelector.selectNetwork(anyMap(), anyList())) + .thenReturn(mSavedWifiConfiguration2); + when(mWifiManager.getWifiState()) + .thenReturn(WifiManager.WIFI_STATE_ENABLED, WifiManager.WIFI_STATE_DISABLED); + + mWifiWakeupController.mContentObserver.onChange(true); + mBroadcastIntentTestHelper.sendWifiStateChanged(); + mBroadcastIntentTestHelper.sendConfiguredNetworksChanged(); + mBroadcastIntentTestHelper.sendScanResultsAvailable(); + mBroadcastIntentTestHelper.sendWifiStateChanged(); + mBroadcastIntentTestHelper.sendScanResultsAvailable(); + mBroadcastIntentTestHelper.sendScanResultsAvailable(); + mBroadcastIntentTestHelper.sendScanResultsAvailable(); + + verify(mWifiManager, never()).setWifiEnabled(true); + + Settings.Global.putInt(mContentResolver, Settings.Global.WIFI_WAKEUP_ENABLED, 1); + mWifiWakeupController.mContentObserver.onChange(true); + + mBroadcastIntentTestHelper.sendScanResultsAvailable(); + + verify(mWifiManager).setWifiEnabled(true); + verify(mWifiWakeupHelper).startWifiSession(mSavedWifiConfiguration2); + } + + /** * When Wi-Fi is disabled and a saved network is in the scan list, and then this network is not * in the scan list 3x, and then it is, Wi-Fi should be enabled. */ @@ -263,6 +308,47 @@ public class WifiWakeupControllerTest { } /** + * When Wi-Fi is disabled near a saved network, and WifiWakeupController stops and starts, Wi-Fi + * should not be enabled. + */ + @Test + public void userDisabledWifiNearSavedNetwork_controllerStopped_controllerStarted() { + when(mWifiManager.getConfiguredNetworks()) + .thenReturn(Lists.newArrayList(mSavedWifiConfiguration)); + when(mWifiManager.getScanResults()).thenReturn(Lists.newArrayList(SAVED_SCAN_RESULT)); + when(mWifiWakeupNetworkSelector.selectNetwork(anyMap(), anyList())) + .thenReturn(mSavedWifiConfiguration); + when(mWifiManager.getWifiState()) + .thenReturn(WifiManager.WIFI_STATE_ENABLED, WifiManager.WIFI_STATE_DISABLED); + + mBroadcastIntentTestHelper.sendWifiStateChanged(); + mBroadcastIntentTestHelper.sendConfiguredNetworksChanged(); + mBroadcastIntentTestHelper.sendScanResultsAvailable(); + mBroadcastIntentTestHelper.sendWifiStateChanged(); + mWifiWakeupController.stop(); + + assertEquals( + ImmutableSet.of(HashUtil.getSsidHash(SAVED_SCAN_RESULT.SSID)), + Preferences.savedSsidsOnDisable.get()); + + mWifiWakeupController = + new WifiWakeupController( + RuntimeEnvironment.application, + mContentResolver, + new Handler(ShadowLooper.getMainLooper()), + mWifiManager, + mPowerManager, + mWifiWakeupNetworkSelector, + mWifiWakeupHelper); + mWifiWakeupController.start(); + mBroadcastIntentTestHelper.sendScanResultsAvailable(); + mBroadcastIntentTestHelper.sendScanResultsAvailable(); + mBroadcastIntentTestHelper.sendScanResultsAvailable(); + + verify(mWifiManager, never()).setWifiEnabled(true); + } + + /** * When Wi-Fi is disabled and a saved network is in the scan list, but {@link * WifiWakeupNetworkSelector}, does not choose this network, Wi-Fi should not be enabled. */ diff --git a/robotests/src/com/android/networkrecommendation/wakeup/WifiWakeupHelperTest.java b/robotests/src/com/android/networkrecommendation/wakeup/WifiWakeupHelperTest.java index a74e5fc..3840771 100644 --- a/robotests/src/com/android/networkrecommendation/wakeup/WifiWakeupHelperTest.java +++ b/robotests/src/com/android/networkrecommendation/wakeup/WifiWakeupHelperTest.java @@ -15,6 +15,8 @@ */ package com.android.networkrecommendation.wakeup; +import static com.android.networkrecommendation.TestData.SSID_1; +import static com.android.networkrecommendation.TestData.UNQUOTED_SSID_1; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -30,14 +32,18 @@ import android.app.Notification; import android.app.NotificationManager; import android.content.Context; import android.content.Intent; -import android.content.SharedPreferences; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.os.Handler; import android.os.Looper; +import android.provider.Settings; +import android.util.ArraySet; import com.android.networkrecommendation.config.Flag; -import com.google.common.collect.ImmutableSet; +import com.android.networkrecommendation.config.PreferenceFile; +import com.android.networkrecommendation.config.Preferences; +import com.android.networkrecommendation.scoring.util.HashUtil; +import com.android.networkrecommendation.shadows.ShadowNotificationChannelUtil; import java.util.Set; import org.junit.Before; import org.junit.Test; @@ -52,17 +58,15 @@ import org.robolectric.shadows.ShadowLooper; /** Unit tests for {@link WifiWakeupHelper} */ @RunWith(RobolectricTestRunner.class) -@Config(manifest = "packages/services/NetworkRecommendation/AndroidManifest.xml", sdk = 23) +@Config(manifest = "packages/services/NetworkRecommendation/AndroidManifest.xml", sdk = 23, +shadows={ShadowNotificationChannelUtil.class}) public class WifiWakeupHelperTest { - private static final String SSID = "ssid"; - private Context mContext; private WifiConfiguration mWifiConfiguration; @Mock private NotificationManager mNotificationManager; @Mock private WifiManager mWifiManager; @Mock private WifiInfo mWifiInfo; - private SharedPreferences mSharedPreferences; private WifiWakeupHelper mWifiWakeupHelper; @@ -70,11 +74,12 @@ public class WifiWakeupHelperTest { public void setUp() throws Exception { MockitoAnnotations.initMocks(this); Flag.initForTest(); - mWifiConfiguration = new WifiConfiguration(); - mWifiConfiguration.SSID = "\"" + SSID + "\""; - mContext = RuntimeEnvironment.application; - mSharedPreferences = mContext.getSharedPreferences("wifi_wakeup", Context.MODE_PRIVATE); + PreferenceFile.init(mContext); + Preferences.ssidsForWakeupShown.remove(); + + mWifiConfiguration = new WifiConfiguration(); + mWifiConfiguration.SSID = SSID_1; when(mWifiManager.getConnectionInfo()).thenReturn(mWifiInfo); @@ -84,8 +89,7 @@ public class WifiWakeupHelperTest { mContext.getResources(), new Handler(Looper.getMainLooper()), mNotificationManager, - mWifiManager, - mSharedPreferences); + mWifiManager); } @Test @@ -97,10 +101,9 @@ public class WifiWakeupHelperTest { verify(mNotificationManager, times(1)) .notify(anyString(), anyInt(), any(Notification.class)); - Set<String> ssidSet = - mSharedPreferences.getStringSet(WifiWakeupHelper.KEY_SHOWN_SSIDS, null); + Set<String> ssidSet = Preferences.ssidsForWakeupShown.get(); assertEquals(1, ssidSet.size()); - assertTrue(ssidSet.contains(mWifiConfiguration.SSID)); + assertTrue(ssidSet.contains(HashUtil.getSsidHash(UNQUOTED_SSID_1))); } @Test @@ -115,7 +118,7 @@ public class WifiWakeupHelperTest { public void notificationCanceledWhenWifiDisabled() { mWifiWakeupHelper.startWifiSession(mWifiConfiguration); - when(mWifiInfo.getSSID()).thenReturn(SSID); + when(mWifiInfo.getSSID()).thenReturn(UNQUOTED_SSID_1); when(mWifiManager.isWifiEnabled()).thenReturn(true, false); mContext.sendBroadcast(new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION)); @@ -130,7 +133,7 @@ public class WifiWakeupHelperTest { public void notificationCanceledWhenSsidChanged() throws Exception { mWifiWakeupHelper.startWifiSession(mWifiConfiguration); - when(mWifiInfo.getSSID()).thenReturn(SSID, "blah"); + when(mWifiInfo.getSSID()).thenReturn(UNQUOTED_SSID_1, "blah"); when(mWifiManager.isWifiEnabled()).thenReturn(true); mContext.sendBroadcast(new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION)); @@ -144,12 +147,10 @@ public class WifiWakeupHelperTest { @Test public void sessionLoggedWithoutNotification() { - mSharedPreferences - .edit() - .putStringSet( - WifiWakeupHelper.KEY_SHOWN_SSIDS, ImmutableSet.of(mWifiConfiguration.SSID)) - .commit(); - when(mWifiInfo.getSSID()).thenReturn(SSID, "blah"); + Set<String> ssidsShown = new ArraySet<>(); + ssidsShown.add(HashUtil.getSsidHash(mWifiConfiguration.SSID)); + Preferences.ssidsForWakeupShown.put(ssidsShown); + when(mWifiInfo.getSSID()).thenReturn(UNQUOTED_SSID_1, "blah"); when(mWifiManager.isWifiEnabled()).thenReturn(true); mWifiWakeupHelper.startWifiSession(mWifiConfiguration); @@ -169,7 +170,7 @@ public class WifiWakeupHelperTest { Intent intent = Shadows.shadowOf(RuntimeEnvironment.application).getNextStartedActivity(); - assertThat(intent.getAction()).isEqualTo("android.settings.CONFIGURE_WIFI_SETTINGS"); + assertThat(intent.getAction()).isEqualTo(Settings.ACTION_WIFI_SETTINGS); } @Test diff --git a/src/com/android/networkrecommendation/NetworkRecommendationApp.java b/src/com/android/networkrecommendation/NetworkRecommendationApp.java new file mode 100644 index 0000000..c63c875 --- /dev/null +++ b/src/com/android/networkrecommendation/NetworkRecommendationApp.java @@ -0,0 +1,17 @@ +package com.android.networkrecommendation; + +import android.app.Application; + +import com.android.networkrecommendation.config.PreferenceFile; + +/** + * Initialize app-wide state. + */ +public class NetworkRecommendationApp extends Application { + + @Override + public void onCreate() { + super.onCreate(); + PreferenceFile.init(this); + } +} diff --git a/src/com/android/networkrecommendation/NetworkRecommendationService.java b/src/com/android/networkrecommendation/NetworkRecommendationService.java index bf86c6c..2207dda 100644 --- a/src/com/android/networkrecommendation/NetworkRecommendationService.java +++ b/src/com/android/networkrecommendation/NetworkRecommendationService.java @@ -31,6 +31,7 @@ import android.os.PowerManager; import com.android.networkrecommendation.notify.WifiNotificationController; import com.android.networkrecommendation.notify.WifiNotificationHelper; +import com.android.networkrecommendation.util.NotificationChannelUtil; import com.android.networkrecommendation.wakeup.WifiWakeupController; import com.android.networkrecommendation.wakeup.WifiWakeupHelper; import com.android.networkrecommendation.wakeup.WifiWakeupNetworkSelector; @@ -64,14 +65,15 @@ public class NetworkRecommendationService extends Service { mControllerHandlerThread.start(); mControllerHandler = new Handler(mControllerHandlerThread.getLooper()); NotificationManager notificationManager = getSystemService(NotificationManager.class); + NotificationChannelUtil.configureNotificationChannels(notificationManager, this); + WifiManager wifiManager = getSystemService(WifiManager.class); PowerManager powerManager = getSystemService(PowerManager.class); Resources resources = getResources(); ContentResolver contentResolver = getContentResolver(); mWifiNotificationController = new WifiNotificationController( this, contentResolver, mControllerHandler, mProvider, - wifiManager, notificationManager, - new WifiNotificationHelper(this, mProvider)); + wifiManager, notificationManager, new WifiNotificationHelper(this)); WifiWakeupNetworkSelector wifiWakeupNetworkSelector = new WifiWakeupNetworkSelector(resources, mProvider); WifiWakeupHelper wifiWakeupHelper = new WifiWakeupHelper(this, resources, mControllerHandler, diff --git a/src/com/android/networkrecommendation/config/PreferenceFile.java b/src/com/android/networkrecommendation/config/PreferenceFile.java new file mode 100644 index 0000000..9066a6a --- /dev/null +++ b/src/com/android/networkrecommendation/config/PreferenceFile.java @@ -0,0 +1,299 @@ +/* + * 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.config; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.os.Build; + +import java.util.Collection; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +/** + * Utility class for retrieving and storing key/value pairs in a SharedPreferences file. + * + * Typical usage: + * + * In your application's onCreate(); + * PreferenceFile.init(this); + * + * In common preferences declaration area: + * private static final PreferenceFile sFile = new PreferenceFile("my_prefs"); + * public static final SharedPreference<String> pageUrl = sFile.value("page_url", "http://blah"); + * + * At usage time: + * String pageUrl = Preferences.pageUrl.get(); + * Preferences.pageUrl.put("http://www.newurl.com/"); + */ +public class PreferenceFile { + private static final String TAG = "PreferenceFile"; + + private static Context sContext; + + public static void init(Context context) { + sContext = context; + } + + private final String mName; + private final int mMode; + + @SuppressWarnings("deprecation") + public PreferenceFile(String name) { + this(name, Context.MODE_PRIVATE); + } + + /** + * @deprecated any mode other than MODE_PRIVATE is a bad idea. If you need multi-process + * support, see if {@link MultiProcessPreferenceFile} is suitable. + */ + @Deprecated + public PreferenceFile(String name, int mode) { + mName = name; + mMode = mode; + } + + /** + * Returns a text dump of all preferences in this file; for debugging. + */ + public String dump() { + SharedPreferences sp = open(); + Map<String, ?> allPrefs = sp.getAll(); + String format = "%" + longestString(allPrefs.keySet()) + "s = %s\n"; + StringBuilder s = new StringBuilder(); + for (String key : allPrefs.keySet()) { + s.append(String.format(Locale.US, format, key, allPrefs.get(key))); + } + return s.toString(); + } + + /** Return a SharedPreferences for this file. */ + public SharedPreferences open() { + return sContext.getSharedPreferences(mName, mMode); + } + + public void remove(SharedPreference<?>... preferences) { + SharedPreferences.Editor editor = open().edit(); + for (SharedPreference<?> preference : preferences) { + editor.remove(preference.getKey()); + } + commit(editor); + } + + /** Synchronously clear all SharedPreferences in this file. */ + public void clear() { + open().edit().clear().commit(); + } + + + /** + * If on API >= 9, use the asynchronous + * {@link Editor#apply()} method. Otherwise, use the + * synchronous {@link Editor#commit()} method. <br /> + * <br /> + * If commit() is used, the result will be returned. If apply() is used, it + * will always return true. + * + * @param editor The editor to save + * @return the result of commit() on API < 9 and true on API >= 9. + */ + @SuppressLint("NewApi") + public static boolean commit(Editor editor) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD) { + return editor.commit(); + } + editor.apply(); + return true; + } + + /** Define a new Long value shared pref key. */ + public SharedPreference<Long> longValue(final String key, final Long defaultValue) { + return new SharedPreference<Long>(this, key) { + @Override + protected Long read(SharedPreferences sp) { + if (sp.contains(key)) { + return sp.getLong(key, 0L); + } + return defaultValue; + } + + @Override + protected void write(Editor editor, Long value) { + if (value == null) { + throw new IllegalArgumentException("null cannot be written for Long"); + } + editor.putLong(key, value); + } + }; + } + + /** Define a new String value shared pref key. */ + public SharedPreference<String> stringValue(final String key, final String defaultValue) { + return new SharedPreference<String>(this, key) { + @Override + protected String read(SharedPreferences sp) { + if (sp.contains(key)) { + return sp.getString(key, null); + } + return defaultValue; + } + + @Override + protected void write(Editor editor, String value) { + if (value == null) { + throw new IllegalArgumentException("null cannot be written for String"); + } + editor.putString(key, value); + } + }; + } + + /** Define a new Boolean value shared pref key. */ + public SharedPreference<Boolean> booleanValue(final String key, final Boolean defaultValue) { + return new SharedPreference<Boolean>(this, key) { + @Override + protected Boolean read(SharedPreferences sp) { + if (sp.contains(key)) { + return sp.getBoolean(key, false); + } + return defaultValue; + } + + @Override + protected void write(Editor editor, Boolean value) { + if (value == null) { + throw new IllegalArgumentException("null cannot be written for Boolean"); + } + editor.putBoolean(key, value); + } + }; + } + + /** Define a new Integer value shared pref key. */ + public SharedPreference<Integer> intValue(final String key, final Integer defaultValue) { + return new SharedPreference<Integer>(this, key) { + @Override + protected Integer read(SharedPreferences sp) { + if (sp.contains(key)) { + return sp.getInt(key, 0); + } + return defaultValue; + } + + @Override + protected void write(Editor editor, Integer value) { + if (value == null) { + throw new IllegalArgumentException("null cannot be written for Integer"); + } + editor.putInt(key, value); + } + }; + } + + /** Define a new Set<String> value shared pref key. */ + public SharedPreference<Set<String>> stringSetValue(final String key, + final Set<String> defaultValue) { + return new SharedPreference<Set<String>>(this, key) { + @Override + protected Set<String> read(SharedPreferences sp) { + if (sp.contains(key)) { + return sp.getStringSet(key, null); + } + return defaultValue; + } + + @Override + protected void write(Editor editor, Set<String> value) { + if (value == null) { + throw new IllegalArgumentException("null cannot be written for Set<String>"); + } + editor.putStringSet(key, value); + } + }; + } + + /** + * A class representing a key/value pair in a given {@link PreferenceFile}. + */ + public static abstract class SharedPreference<T> { + PreferenceFile mFile; + final String mKey; + + protected SharedPreference(PreferenceFile file, String key) { + mFile = file; + mKey = key; + } + + /** Read the value stored for this pref, or the default value if none is stored. */ + public final T get() { + return read(mFile.open()); + } + + /** Get the representation in string of the value for this pref. */ + public String getValueString() { + T value = get(); + if (value == null) { + return null; + } + return value.toString(); + } + + /** Return this pref's key. */ + public final String getKey() { + return mKey; + } + + /** Return true if this key is defined in its file. */ + public final boolean exists() { + return mFile.open().contains(mKey); + } + + /** Write a new value for this pref to its file. */ + public final void put(T value) { + SharedPreferences sp = mFile.open(); + Editor editor = sp.edit(); + write(editor, value); + commit(editor); + } + + /** Removes this pref from its file. */ + public final void remove() { + commit(mFile.open().edit().remove(mKey)); + } + + /** Override the PreferenceFile used by this preference (for testing). */ + public final void override(PreferenceFile file) { + mFile = file; + } + + protected abstract T read(SharedPreferences sp); + protected abstract void write(Editor editor, T value); + } + + /** + * Returns the length of the longest string in the provided Collection. + */ + private static int longestString(Collection<String> strings) { + int longest = 0; + for (String s : strings) { + longest = Math.max(longest, s.length()); + } + return longest; + } +} diff --git a/src/com/android/networkrecommendation/config/Preferences.java b/src/com/android/networkrecommendation/config/Preferences.java new file mode 100644 index 0000000..a9e6fb0 --- /dev/null +++ b/src/com/android/networkrecommendation/config/Preferences.java @@ -0,0 +1,75 @@ +/* + * 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.config; + +import com.android.networkrecommendation.config.PreferenceFile.SharedPreference; +import java.util.Collections; +import java.util.Set; + +/** The NetRec preferences file. */ +public final class Preferences { + private Preferences() {} + + private static final PreferenceFile sPrefs = + new PreferenceFile("com.android.networkrecommendation"); + + /** + * {@link ScoreNetworksChimeraBroadcastReceiver} sets this to true when the scorer is enabled. + * {@link com.android.networkrecommendation.scoring.service.FutureRefreshRequestor} checks for + * this value and triggers a quick score refresh if this is set. + */ + public static final SharedPreference<Boolean> justEnabled = + sPrefs.booleanValue("justEnabled", false); + + /** + * The next time, in ms since system boot, that a rapid (i.e. outside the usual refresh window) + * will be allowed to make a network request. + */ + public static final SharedPreference<Long> nextRapidRefreshAllowed = + sPrefs.longValue("nextRapidRefreshAllowedMillis", 0L); + + /** + * The set of saved ssid hashes in previous scan result list when the user disabled Wi-Fi. Saved + * to preferences when {@link com.android.networkrecommendation.wakeup.WifiWakeupController} + * stops. + */ + public static final SharedPreference<Set<String>> savedSsidsOnDisable = + sPrefs.stringSetValue("savedSsidsOnDisable", Collections.emptySet()); + + /** + * The set of saved ssid hashes that were previously shown as Wi-Fi Enabled notifications + * through {@link com.android.networkrecommendation.wakeup.WifiWakeupController}. + */ + public static final SharedPreference<Set<String>> ssidsForWakeupShown = + sPrefs.stringSetValue("ssidsForWakeupShown", Collections.emptySet()); + + /** Key for {@link com.android.networkrecommendation.storage.Encrypter} on pre-MNC devices. */ + public static final SharedPreference<String> encrypterKey = + sPrefs.stringValue("encrypterKey", null); + + /** + * How long we should wait before requesting another network. Used by {@link + * PersistentNetworkRequest} to support GCS/WFA. + */ + public static final SharedPreference<Integer> nextNetworkRequestDelayMs = + sPrefs.intValue("nextNetworkRequestDelayMs", 0); + + /** + * Hash of the SSID that last satisfied the network request in {@link PersistentNetworkRequest}. + */ + public static final SharedPreference<String> lastSsidHash = + sPrefs.stringValue("lastSsidHash", ""); +} diff --git a/src/com/android/networkrecommendation/notify/WifiNotificationController.java b/src/com/android/networkrecommendation/notify/WifiNotificationController.java index aaba9c9..a6a9e70 100644 --- a/src/com/android/networkrecommendation/notify/WifiNotificationController.java +++ b/src/com/android/networkrecommendation/notify/WifiNotificationController.java @@ -25,7 +25,6 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.database.ContentObserver; -import android.graphics.Bitmap; import android.net.NetworkInfo; import android.net.NetworkScoreManager; import android.net.RecommendationRequest; @@ -54,7 +53,7 @@ import java.util.concurrent.atomic.AtomicBoolean; /** Takes care of handling the "open wi-fi network available" notification */ public class WifiNotificationController { /** The unique ID of the Notification given to the NotificationManager. */ - private static final int NOTIFICATION_ID = R.string.wifi_available; + private static final int NOTIFICATION_ID = R.string.wifi_available_title; /** When a notification is shown, we wait this amount before possibly showing it again. */ private final long mNotificationRepeatDelayMs; @@ -138,9 +137,6 @@ public class WifiNotificationController { /** Network recommended by {@link NetworkScoreManager#requestRecommendation}. */ private WifiConfiguration mRecommendedNetwork; - /** Badge icon of the recommended network. */ - private Bitmap mNotificationBadgeBitmap; - /** Whether {@link WifiNotificationController} has been started. */ private final AtomicBoolean mStarted; @@ -212,7 +208,7 @@ public class WifiNotificationController { return; } mContext.unregisterReceiver(mBroadcastReceiver); - mNotificationEnabledSettingObserver.register(); + mNotificationEnabledSettingObserver.unregister(); } private final BroadcastReceiver mBroadcastReceiver = @@ -257,7 +253,7 @@ public class WifiNotificationController { } } else if (intent.getAction() .equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) { - checkAndSetNotification(mNetworkInfo, mWifiManager.getScanResults()); + checkAndSetNotification(mNetworkInfo); } else if (intent.getAction() .equals(ACTION_CONNECT_TO_RECOMMENDED_NETWORK)) { connectToRecommendedNetwork(); @@ -274,15 +270,13 @@ public class WifiNotificationController { } }; - private void checkAndSetNotification(NetworkInfo networkInfo, List<ScanResult> scanResults) { + private void checkAndSetNotification(NetworkInfo networkInfo) { // 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 || mWifiState != WifiManager.WIFI_STATE_ENABLED - || mNotificationState > State.SHOWING_CONNECT_ACTIONS - || scanResults == null - || scanResults.isEmpty()) { + || mNotificationState > State.SHOWING_CONNECT_ACTIONS) { return; } @@ -293,15 +287,11 @@ public class WifiNotificationController { if (state == NetworkInfo.State.DISCONNECTED || state == NetworkInfo.State.UNKNOWN) { maybeLogOpenNetworksAvailable(); - RecommendationResult result = getOpenNetworkRecommendation(scanResults); + RecommendationResult result = getOpenNetworkRecommendation(); if (result != null && result.getWifiConfiguration() != null) { mRecommendedNetwork = result.getWifiConfiguration(); - mNotificationBadgeBitmap = - mWifiNotificationHelper.createNotificationBadgeBitmap( - mRecommendedNetwork, scanResults); - if (++mNumScansSinceNetworkStateChange >= NUM_SCANS_BEFORE_ACTUALLY_SCANNING - && mNotificationBadgeBitmap != null) { + if (++mNumScansSinceNetworkStateChange >= NUM_SCANS_BEFORE_ACTUALLY_SCANNING) { /* * We have scanned continuously at least * NUM_SCANS_BEFORE_NOTIFICATION times. The user @@ -334,7 +324,8 @@ public class WifiNotificationController { * @return returns the best qualified open networks, if any. */ @Nullable - private RecommendationResult getOpenNetworkRecommendation(List<ScanResult> scanResults) { + private RecommendationResult getOpenNetworkRecommendation() { + List<ScanResult> scanResults = mWifiManager.getScanResults(); if (scanResults == null || scanResults.isEmpty()) { return null; } @@ -353,6 +344,7 @@ public class WifiNotificationController { } } + Blog.d(TAG, "Sending RecommendationRequest. [num_open_networks=%d]", openNetworks.size()); RecommendationRequest request = new RecommendationRequest.Builder() .setScanResults(openNetworks.toArray(new ScanResult[openNetworks.size()])) @@ -387,8 +379,7 @@ public class WifiNotificationController { return; } Notification notification = - mWifiNotificationHelper.createMainNotification( - mRecommendedNetwork, mNotificationBadgeBitmap); + mWifiNotificationHelper.createMainNotification(mRecommendedNetwork); mNotificationRepeatTime = System.currentTimeMillis() + mNotificationRepeatDelayMs; postNotification(notification); if (mNotificationState != State.SHOWING_CONNECT_ACTIONS) { @@ -419,8 +410,7 @@ public class WifiNotificationController { // Update notification to connecting status. Notification notification = - mWifiNotificationHelper.createConnectingNotification( - mRecommendedNetwork, mNotificationBadgeBitmap); + mWifiNotificationHelper.createConnectingNotification(mRecommendedNetwork); postNotification(notification); mNotificationState = State.SHOWING_CONNECTING; mHandler.postDelayed( @@ -442,8 +432,7 @@ public class WifiNotificationController { } Notification notification = - mWifiNotificationHelper.createConnectedNotification( - mRecommendedNetwork, mNotificationBadgeBitmap); + mWifiNotificationHelper.createConnectedNotification(mRecommendedNetwork); postNotification(notification); mNotificationState = State.SHOWING_CONNECTED; mHandler.postDelayed( @@ -460,8 +449,7 @@ public class WifiNotificationController { * {@link #TIME_TO_SHOW_CONNECTING_MILLIS} duration. */ private void showFailedToConnectNotification() { - Notification notification = - mWifiNotificationHelper.createFailedToConnectNotification(mRecommendedNetwork); + Notification notification = mWifiNotificationHelper.createFailedToConnectNotification(); postNotification(notification); mNotificationState = State.SHOWING_FAILURE; mHandler.postDelayed( @@ -477,7 +465,6 @@ public class WifiNotificationController { private void handleNotificationDeleted() { mNotificationState = State.HIDDEN; mRecommendedNetwork = null; - mNotificationBadgeBitmap = null; } private void postNotification(Notification notification) { @@ -502,7 +489,6 @@ public class WifiNotificationController { mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID); mNotificationState = State.HIDDEN; mRecommendedNetwork = null; - mNotificationBadgeBitmap = null; } public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { diff --git a/src/com/android/networkrecommendation/notify/WifiNotificationHelper.java b/src/com/android/networkrecommendation/notify/WifiNotificationHelper.java index 2fbeeab..c265e8f 100644 --- a/src/com/android/networkrecommendation/notify/WifiNotificationHelper.java +++ b/src/com/android/networkrecommendation/notify/WifiNotificationHelper.java @@ -16,54 +16,24 @@ package com.android.networkrecommendation.notify; import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; -import static com.android.networkrecommendation.Constants.TAG; +import static com.android.networkrecommendation.util.NotificationChannelUtil.CHANNEL_ID_NETWORK_AVAILABLE; import android.app.Notification; import android.app.Notification.Action; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.Color; -import android.graphics.drawable.Drawable; -import android.net.NetworkBadging; -import android.net.NetworkKey; -import android.net.ScoredNetwork; -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; +import com.android.networkrecommendation.util.NotificationChannelUtil; /** 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; - public WifiNotificationHelper( - Context context, SynchronousNetworkRecommendationProvider cachedScoredNetworkProvider) { + public WifiNotificationHelper(Context context) { mContext = context; - mCachedScoredNetworkProvider = cachedScoredNetworkProvider; } /** @@ -71,18 +41,18 @@ public class WifiNotificationHelper { * picker activity, and "Connect" prompts {@link WifiNotificationController} to connect to the * recommended network. */ - public Notification createMainNotification(WifiConfiguration config, Bitmap badge) { - PendingIntent optionsIntent = + public Notification createMainNotification(WifiConfiguration config) { + PendingIntent allNetworksIntent = PendingIntent.getBroadcast( mContext, 0, new Intent(WifiNotificationController.ACTION_PICK_WIFI_NETWORK), FLAG_UPDATE_CURRENT); - Action optionsAction = + Action allNetworksAction = new Action.Builder( null /* icon */, - mContext.getText(R.string.wifi_available_options), - optionsIntent) + mContext.getText(R.string.wifi_available_action_all_networks), + allNetworksIntent) .build(); PendingIntent connectIntent = PendingIntent.getBroadcast( @@ -94,12 +64,12 @@ public class WifiNotificationHelper { Action connectAction = new Action.Builder( null /* icon */, - mContext.getText(R.string.wifi_available_connect), + mContext.getText(R.string.wifi_available_action_connect), connectIntent) .build(); - return createNotificationBuilder(config, badge) + return createNotificationBuilder(R.string.wifi_available_title, config.SSID) .addAction(connectAction) - .addAction(optionsAction) + .addAction(allNetworksAction) .build(); } @@ -107,15 +77,8 @@ public class WifiNotificationHelper { * 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.common_connecting), - null /* pendingIntent */) - .build(); - return createNotificationBuilder(config, badge) - .addAction(connecting) + public Notification createConnectingNotification(WifiConfiguration config) { + return createNotificationBuilder(R.string.wifi_available_title_connecting, config.SSID) .setProgress(0 /* max */, 0 /* progress */, true /* indeterminate */) .build(); } @@ -124,126 +87,58 @@ public class WifiNotificationHelper { * 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(); + public Notification createConnectedNotification(WifiConfiguration config) { + return createNotificationBuilder(R.string.wifi_available_title_connected, config.SSID) + .setAutoCancel(true) + .build(); } /** * Creates the notification that indicates the controller failed to connect to the recommended * network. */ - public Notification createFailedToConnectNotification(WifiConfiguration config) { - Spannable failedText = - new SpannableString(mContext.getText(R.string.wifi_available_failed)); - 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(); + public Notification createFailedToConnectNotification() { + PendingIntent allNetworksIntent = + PendingIntent.getBroadcast( + mContext, + 0, + new Intent(WifiNotificationController.ACTION_PICK_WIFI_NETWORK), + FLAG_UPDATE_CURRENT); + return createNotificationBuilder( + R.string.wifi_available_title_failed, + mContext.getString(R.string.wifi_available_content_failed)) + .setContentIntent(allNetworksIntent) + .setAutoCancel(true) + .build(); } - private Notification.Builder createNotificationBuilder(WifiConfiguration config, Bitmap badge) { - CharSequence title = mContext.getText(R.string.wifi_available); + private Notification.Builder createNotificationBuilder(int titleRid, String content) { + CharSequence title = mContext.getText(titleRid); 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.ic_signal_wifi_statusbar_not_connected) - .setLargeIcon(badge) - .setAutoCancel(true) - .setTicker(title) - .setContentTitle(title) - .setColor(mContext.getColor(R.color.color_tint)) - .setContentText(WifiConfigurationUtil.getPrintableSsid(config)) - .addExtras(getSystemLabelExtras()); + int smallIcon = R.drawable.ic_signal_wifi_statusbar_not_connected; + Notification.Builder builder = + new Notification.Builder(mContext) + .setDeleteIntent(deleteIntent) + .setSmallIcon(smallIcon) + .setTicker(title) + .setContentTitle(title) + .setColor(mContext.getColor(R.color.color_tint)) + .setContentText(content) + .setLocalOnly(true) + .addExtras(getOverrideLabelExtras()); + return NotificationChannelUtil.setChannel(builder, CHANNEL_ID_NETWORK_AVAILABLE); } - private Bundle getSystemLabelExtras() { + private Bundle getOverrideLabelExtras() { Bundle extras = new Bundle(); extras.putString( Notification.EXTRA_SUBSTITUTE_APP_NAME, - mContext.getString(R.string.android_system_label)); + mContext.getString(R.string.notification_channel_group_name)); return extras; } - - /** - * 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) { - ScanResult matchingScanResult = findMatchingScanResult(scanResults, config); - if (matchingScanResult == null) { - return null; - } - int rssi = matchingScanResult.level; - - NetworkKey networkKey; - try { - networkKey = ScanResultUtil.createNetworkKey(matchingScanResult); - } catch (IllegalArgumentException e) { - Blog.e(TAG, e, "Error creating NetworkKey."); - return null; - } - - ScoredNetwork scoredNetwork = - mCachedScoredNetworkProvider.getCachedScoredNetwork(networkKey); - if (scoredNetwork != null) { - return getBadgedWifiBitmap( - RoboCompatUtil.getInstance().calculateBadge(scoredNetwork, rssi), rssi); - } - return null; - } - - private Bitmap getBadgedWifiBitmap(int badgeEnum, int rssi) { - if (badgeEnum == NetworkBadging.BADGING_NONE) { - return null; - } - if (Settings.Global.getInt(mContext.getContentResolver(), NETWORK_SCORING_UI_ENABLED, 0) - == 0) { - badgeEnum = NetworkBadging.BADGING_NONE; - } - int signalLevel = WifiManager.calculateSignalLevel(rssi, 5); - Drawable drawable = - RoboCompatUtil.getInstance() - .getWifiIcon(signalLevel, badgeEnum, mContext.getTheme()); - drawable.setTint(mContext.getColor(R.color.color_tint)); - Resources resources = mContext.getResources(); - return ImageUtil.buildScaledBitmap( - drawable, - resources.getDimensionPixelSize(android.R.dimen.notification_large_icon_width), - resources.getDimensionPixelSize(android.R.dimen.notification_large_icon_height)); - } - - private static ScanResult findMatchingScanResult( - List<ScanResult> scanResults, WifiConfiguration wifiConfiguration) { - for (ScanResult scanResult : scanResults) { - if (ScanResultUtil.doesScanResultMatchWithNetwork(scanResult, wifiConfiguration)) { - return scanResult; - } - } - return null; - } } diff --git a/src/com/android/networkrecommendation/scoring/util/HashUtil.java b/src/com/android/networkrecommendation/scoring/util/HashUtil.java new file mode 100644 index 0000000..19fbd6a --- /dev/null +++ b/src/com/android/networkrecommendation/scoring/util/HashUtil.java @@ -0,0 +1,93 @@ +/* + * 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.scoring.util; + +import android.util.Base64; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** Hashcode and encoding utils. */ +public final class HashUtil { + private HashUtil() {} + + /** + * Returns a base64-encoded secure hash (using the SHA-256 algorithm) of the provided input. + * + * @param input the bytes for which the secure hash should be computed. + * @return the hash + */ + public static String secureHash(String input) { + return encodeBase64(getHash(input, "SHA-256")); + } + + public static String encodeBase64(byte[] input) { + return Base64.encodeToString(input, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP); + } + + /** Retrieves the message digest instance for a given hash algorithm. */ + public static MessageDigest getMessageDigest(String hashAlgorithm) { + try { + return MessageDigest.getInstance(hashAlgorithm); + } catch (NoSuchAlgorithmException e) { + return null; + } + } + + /** + * @return hash of input using hashAlgorithm, or 0-length byte array if input is null, or null + * if hashAlgorithm can't be loaded + */ + public static byte[] getHash(String input, String hashAlgorithm) { + if (input != null) { + MessageDigest digest = getMessageDigest(hashAlgorithm); + if (digest == null) { + return null; + } + return digest.digest(input.getBytes()); + } + return new byte[0]; + } + + /** + * Gets an SSID-specific hash which also ensures the SSID is in cannonical form (stripped of + * quotes). + */ + public static String getSsidHash(String ssid) { + return secureHash(NetworkUtil.canonicalizeSsid(ssid)); + } + + /** Gets a single hash of over the combined SSID and BSSID. */ + public static String getSsidBssidHash(String ssid, String bssid) { + String canonicalSsid = NetworkUtil.canonicalizeSsid(ssid); + return secureHash(canonicalSsid + bssid); + } + + /** Return the first 8 bytes of the SHA-256 hash of the given ssid as a long value. */ + public static long hashAsLong(String ssid) { + byte[] h = getHash(ssid, "SHA-256"); + if (h == null || h.length < 8) { + return 0; + } + return (h[0] & 0xFFL) << 56 + | (h[1] & 0xFFL) << 48 + | (h[2] & 0xFFL) << 40 + | (h[3] & 0xFFL) << 32 + | (h[4] & 0xFFL) << 24 + | (h[5] & 0xFFL) << 16 + | (h[6] & 0xFFL) << 8 + | (h[7] & 0xFFL); + } +} diff --git a/src/com/android/networkrecommendation/scoring/util/NetworkUtil.java b/src/com/android/networkrecommendation/scoring/util/NetworkUtil.java new file mode 100644 index 0000000..55112a3 --- /dev/null +++ b/src/com/android/networkrecommendation/scoring/util/NetworkUtil.java @@ -0,0 +1,164 @@ +/* + * 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.scoring.util; + +import static android.net.wifi.WifiConfiguration.KeyMgmt.IEEE8021X; +import static android.net.wifi.WifiConfiguration.KeyMgmt.WPA_EAP; +import static android.net.wifi.WifiConfiguration.KeyMgmt.WPA_PSK; + +import android.content.Context; +import android.net.NetworkKey; +import android.net.WifiKey; +import android.net.wifi.ScanResult; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.text.TextUtils; +import com.android.networkrecommendation.Constants; +import com.android.networkrecommendation.config.G; +import com.android.networkrecommendation.util.Blog; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** Network Utils. */ +public final class NetworkUtil { + + private NetworkUtil() { + // do not instantiate + } + + /** + * Canonicalize the given SSID returned by WifiInfo#getSSID(). + * + * <p>This method should only be called once on a given SSID! If an SSID contains outer quotes, + * we will strip them twice and change the SSID to a different one. + * + * <p>The SSID should be returned surrounded by double quotation marks if it is valid UTF-8. + * This behavior was only implemented correctly after + * https://googleplex-android-review.googlesource.com/#/c/224602/ which went into JB-MR1. + * + * <p>This method does not account for non-UTF-8 SSIDs, which are returned as a string of hex + * digits from getSSID(). + * + * <p>For more details, see: http://stackoverflow.com/questions/13563032 + */ + public static String canonicalizeSsid(String ssid) { + if (ssid == null) { + return null; + } + return removeQuotesIfNeeded(ssid); + } + + /** Remove the leading quote and trailing quote. */ + private static String removeQuotesIfNeeded(String text) { + if (text.length() > 1 && text.startsWith("\"") && text.endsWith("\"")) { + return text.substring(1, text.length() - 1); + } + return text; + } + + /** + * @return a map from NetworkKey to true if that network is open, and false otherwise, for all + * visible networks in the last set of Wi-Fi scan results. + */ + public static Map<NetworkKey, Boolean> getOpenWifiNetworkKeys(Context context) { + WifiManager wifiMgr = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + List<ScanResult> scanResults = null; + if (Util.isScorerActive(context)) { + try { + scanResults = wifiMgr.getScanResults(); + } catch (SecurityException e) { + Blog.w(Constants.TAG, e, "No permission to get scan results"); + scanResults = null; + } + } + if (scanResults == null) { + return Collections.emptyMap(); + } + Map<NetworkKey, Boolean> openKeys = new HashMap<>(); + for (int i = 0; i < scanResults.size(); i++) { + ScanResult scanResult = scanResults.get(i); + try { + openKeys.put( + new NetworkKey( + new WifiKey("\"" + scanResult.SSID + "\"", scanResult.BSSID)), + isOpenNetwork(scanResult)); + } catch (IllegalArgumentException iae) { + // WifiKey rejects some SSIDs that ScanResults considers valid, e.g. those + // containing a carriage return (as of L MR1). It's okay to just exclude those + // SSIDs from this list, because the platform uses the same WifiKey implementation, and so + // it would never be asking about such SSIDs in the first place. + Blog.v( + Constants.TAG, + "Couldn't make a wifi key from " + + Blog.pii(scanResult.SSID, G.Netrec.enableSensitiveLogging.get()) + + "/" + + Blog.pii(scanResult.BSSID, G.Netrec.enableSensitiveLogging.get()) + + ", skipping"); + } + } + return openKeys; + } + + private static boolean isOpenNetwork(ScanResult result) { + return !TextUtils.isEmpty(result.SSID) + && !TextUtils.isEmpty(result.BSSID) + && !result.capabilities.contains("WEP") + && !result.capabilities.contains("PSK") + && !result.capabilities.contains("EAP"); + } + + /** Returns true if the given config is for an "open" network. */ + public static boolean isOpenNetwork(@NonNull WifiConfiguration config) { + if (config.allowedKeyManagement.get(WPA_PSK) // covers WPA_PSK and WPA2_PSK + || config.allowedKeyManagement.get(WPA_EAP) + || config.allowedKeyManagement.get(IEEE8021X) + || (config.wepKeys != null && config.wepKeys[0] != null)) { + return false; + } + return true; + } + + @NonNull + public static String getCurrentWifiSsid(Context context) { + WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + WifiInfo wifiInfo = wifiManager.getConnectionInfo(); + return wifiInfo == null ? "" : wifiInfo.getSSID(); + } + + /** Returns the config for the given SSID, or null if one cannot be found. */ + @Nullable + public static WifiConfiguration getConfig(Context context, String ssid) { + WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + List<WifiConfiguration> configs = wifiManager.getConfiguredNetworks(); + if (configs == null) { + return null; + } + + WifiConfiguration config; + for (int i = 0; i < configs.size(); i++) { + config = configs.get(i); + if (TextUtils.equals(ssid, config.SSID)) { + return config; + } + } + return null; + } +} diff --git a/src/com/android/networkrecommendation/scoring/util/Util.java b/src/com/android/networkrecommendation/scoring/util/Util.java new file mode 100644 index 0000000..3390531 --- /dev/null +++ b/src/com/android/networkrecommendation/scoring/util/Util.java @@ -0,0 +1,33 @@ +/* + * 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.scoring.util; + +import android.content.Context; +import android.net.NetworkScoreManager; + +/** Utility methods for the scorer. */ +public final class Util { + + /** @return true if GmsCore is the active network scorer. */ + public static boolean isScorerActive(Context context) { + NetworkScoreManager scoreManager = + (NetworkScoreManager) context.getSystemService(Context.NETWORK_SCORE_SERVICE); + final String activeScorer = scoreManager.getActiveScorerPackage(); + final String packageName = context.getPackageName(); + + return packageName.equals(activeScorer); + } +} diff --git a/src/com/android/networkrecommendation/util/ImageUtil.java b/src/com/android/networkrecommendation/util/ImageUtil.java deleted file mode 100644 index 8bf3de0..0000000 --- a/src/com/android/networkrecommendation/util/ImageUtil.java +++ /dev/null @@ -1,62 +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.util; - -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; - -/** Helper for image manipulation */ -public class ImageUtil { - - /** - * Convert a drawable to a bitmap, scaled to fit within maxWidth and maxHeight. - */ - public static Bitmap buildScaledBitmap(Drawable drawable, int maxWidth, - int maxHeight) { - if (drawable == null) { - return null; - } - int originalWidth = drawable.getIntrinsicWidth(); - int originalHeight = drawable.getIntrinsicHeight(); - - if ((originalWidth <= maxWidth) && (originalHeight <= maxHeight) - && (drawable instanceof BitmapDrawable)) { - return ((BitmapDrawable) drawable).getBitmap(); - } - if (originalHeight <= 0 || originalWidth <= 0) { - return null; - } - - // create a new bitmap, scaling down to fit the max dimensions of - // a large notification icon if necessary - float ratio = Math.min((float) maxWidth / (float) originalWidth, - (float) maxHeight / (float) originalHeight); - ratio = Math.min(1.0f, ratio); - int scaledWidth = (int) (ratio * originalWidth); - int scaledHeight = (int) (ratio * originalHeight); - Bitmap result = Bitmap.createBitmap(scaledWidth, scaledHeight, Bitmap.Config.ARGB_8888); - - // and paint our app bitmap on it - Canvas canvas = new Canvas(result); - drawable.setBounds(0, 0, scaledWidth, scaledHeight); - drawable.draw(canvas); - - return result; - } -} diff --git a/src/com/android/networkrecommendation/util/NotificationChannelUtil.java b/src/com/android/networkrecommendation/util/NotificationChannelUtil.java new file mode 100644 index 0000000..8be71bd --- /dev/null +++ b/src/com/android/networkrecommendation/util/NotificationChannelUtil.java @@ -0,0 +1,69 @@ +/* + * 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.util; + +import android.app.Notification.Builder; +import android.app.NotificationChannel; +import android.app.NotificationChannelGroup; +import android.app.NotificationManager; +import android.content.Context; +import com.android.networkrecommendation.R; + +/** + * Util class for managing {@link android.app.NotificationChannel}s for our notifications. + * TODO(netrec): b/36486429 Add tests for this class once relevant class constructors are supported + * for Robolectric. + */ +public class NotificationChannelUtil { + + private static final String CHANNEL_GROUP_ID = + "com.android.networkrecommendation.Notifications.WifiMessageGroup"; + public static final String CHANNEL_ID_NETWORK_AVAILABLE = + "com.android.networkrecommendation.Notifications.WifiMessageGroup.NetworkAvailableChannel"; + public static final String CHANNEL_ID_WAKEUP = + "com.android.networkrecommendation.Notifications.WifiMessageGroup.WakeupChannel"; + + /** Configures the {@link android.app.NotificationChannel}s for our module. */ + public static void configureNotificationChannels( + NotificationManager notificationManager, Context context) { + NotificationChannelGroup notificationChannelGroup = + new NotificationChannelGroup( + CHANNEL_GROUP_ID, + context.getString(R.string.notification_channel_group_name)); + notificationManager.createNotificationChannelGroup(notificationChannelGroup); + + NotificationChannel networkAvailableChannel = + new NotificationChannel( + CHANNEL_ID_NETWORK_AVAILABLE, + context.getString(R.string.notification_channel_network_available), + NotificationManager.IMPORTANCE_LOW); + networkAvailableChannel.setGroup(CHANNEL_GROUP_ID); + notificationManager.createNotificationChannel(networkAvailableChannel); + + NotificationChannel wakeupChannel = + new NotificationChannel( + CHANNEL_ID_WAKEUP, + context.getString(R.string.notification_channel_wakeup_name), + NotificationManager.IMPORTANCE_LOW); + wakeupChannel.setGroup(CHANNEL_GROUP_ID); + notificationManager.createNotificationChannel(wakeupChannel); + } + + /** Wraps Notification.Builder.setChannel. */ + public static Builder setChannel(Builder notificationBuilder, String channelId) { + return notificationBuilder.setChannel(channelId); + } +} diff --git a/src/com/android/networkrecommendation/util/RoboCompatUtil.java b/src/com/android/networkrecommendation/util/RoboCompatUtil.java index 06b48fe..24c6d03 100644 --- a/src/com/android/networkrecommendation/util/RoboCompatUtil.java +++ b/src/com/android/networkrecommendation/util/RoboCompatUtil.java @@ -74,11 +74,6 @@ public class RoboCompatUtil { return wifiConfiguration.isPasspoint(); } - /** Wraps ScoredNetwork.calculateBadge. */ - public int calculateBadge(ScoredNetwork scoredNetwork, int rssi) { - return scoredNetwork.calculateBadge(rssi); - } - /** Wraps NetworkBadging.getWifiIcon. */ public Drawable getWifiIcon(int signalLevel, int badging, Theme theme) { return NetworkBadging.getWifiIcon(signalLevel, badging, theme); diff --git a/src/com/android/networkrecommendation/util/SsidUtil.java b/src/com/android/networkrecommendation/util/SsidUtil.java index 5f7e5e8..52ed7de 100644 --- a/src/com/android/networkrecommendation/util/SsidUtil.java +++ b/src/com/android/networkrecommendation/util/SsidUtil.java @@ -17,7 +17,6 @@ package com.android.networkrecommendation.util; import static com.android.networkrecommendation.Constants.TAG; -import android.net.NetworkKey; import android.net.WifiKey; import android.support.annotation.Nullable; import com.android.networkrecommendation.config.G; @@ -78,15 +77,12 @@ public final class SsidUtil { } /** - * Returns a string version of the NetworkKey SSID/BSSID pair for logging which is typically - * redacted. + * Returns a string version of the SSID/BSSID pair for logging which is typically redacted. * - * <p>The IDs will only be returned verbatim if the enableSentitiveLogging flag is set. + * <p>The IDs will only be returned verbatim if the enableSensitiveLogging flag is set. */ - public static String getRedactedId(NetworkKey networkKey) { - return Blog.pii( - String.format("%s/%s", networkKey.wifiKey.ssid, networkKey.wifiKey.bssid), - G.Netrec.enableSensitiveLogging.get()); + public static String getRedactedId(String ssid, String bssid) { + return Blog.pii(String.format("%s/%s", ssid, bssid), G.Netrec.enableSensitiveLogging.get()); } // Can't instantiate. diff --git a/src/com/android/networkrecommendation/wakeup/WifiWakeupController.java b/src/com/android/networkrecommendation/wakeup/WifiWakeupController.java index 100e652..a9aa61f 100644 --- a/src/com/android/networkrecommendation/wakeup/WifiWakeupController.java +++ b/src/com/android/networkrecommendation/wakeup/WifiWakeupController.java @@ -34,7 +34,9 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import com.android.networkrecommendation.config.G; +import com.android.networkrecommendation.config.Preferences; import com.android.networkrecommendation.config.WideAreaNetworks; +import com.android.networkrecommendation.scoring.util.HashUtil; import com.android.networkrecommendation.util.Blog; import com.android.networkrecommendation.util.RoboCompatUtil; import com.android.networkrecommendation.util.WifiConfigurationUtil; @@ -121,23 +123,26 @@ public class WifiWakeupController { new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - if (!mWifiWakeupEnabled) { - return; - } - - if (WifiManager.WIFI_AP_STATE_CHANGED_ACTION.equals(intent.getAction())) { - handleWifiApStateChanged(); - } else if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(intent.getAction())) { - handleWifiStateChanged(); - } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals( - intent.getAction())) { - handleScanResultsAvailable(); - } else if (WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals( - intent.getAction())) { - handleConfiguredNetworksChanged(); - } else if (PowerManager.ACTION_POWER_SAVE_MODE_CHANGED.equals( - intent.getAction())) { - handlePowerSaverModeChanged(); + try { + if (WifiManager.WIFI_AP_STATE_CHANGED_ACTION.equals(intent.getAction())) { + handleWifiApStateChanged(); + } else if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals( + intent.getAction())) { + handleWifiStateChanged(false); + } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals( + intent.getAction())) { + handleScanResultsAvailable(); + } else if (WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals( + intent.getAction())) { + handleConfiguredNetworksChanged(); + } else if (PowerManager.ACTION_POWER_SAVE_MODE_CHANGED.equals( + intent.getAction())) { + handlePowerSaverModeChanged(); + } + } catch (RuntimeException re) { + // TODO(b/35044022) Remove try/catch after a couple of releases when we are confident + // this is not going to throw. + Blog.e(TAG, re, "RuntimeException in broadcast receiver."); } } }; @@ -166,10 +171,10 @@ public class WifiWakeupController { true, mContentObserver); mContentObserver.onChange(true); - handleWifiStateChanged(); handlePowerSaverModeChanged(); handleWifiApStateChanged(); handleConfiguredNetworksChanged(); + handleWifiStateChanged(true); handleScanResultsAvailable(); } @@ -240,7 +245,7 @@ public class WifiWakeupController { mSavedSsidsInLastScan.retainAll(mSavedSsids); } - private void handleWifiStateChanged() { + private void handleWifiStateChanged(boolean calledOnStart) { mWifiState = mWifiManager.getWifiState(); Blog.v(TAG, "handleWifiStateChanged: %d", mWifiState); @@ -250,8 +255,13 @@ public class WifiWakeupController { if (!mAutopilotEnabledWifi) {} break; case WifiManager.WIFI_STATE_DISABLED: - for (String ssid : mSavedSsidsInLastScan) { - mSavedSsidsOnDisable.put(ssid, NUM_SCANS_TO_CONFIRM_AP_LOSS); + if (calledOnStart) { + readDisabledSsidsFromSharedPreferences(); + } else { + for (String ssid : mSavedSsidsInLastScan) { + mSavedSsidsOnDisable.put(ssid, NUM_SCANS_TO_CONFIRM_AP_LOSS); + } + writeDisabledSsidsToSharedPreferences(); } Blog.d(TAG, "Disabled ssid set: %s", mSavedSsidsOnDisable); @@ -261,7 +271,27 @@ public class WifiWakeupController { } } + private void readDisabledSsidsFromSharedPreferences() { + Set<String> ssidsOnDisable = Preferences.savedSsidsOnDisable.get(); + for (String ssid : mSavedSsids) { + if (ssidsOnDisable.contains(HashUtil.getSsidHash(ssid))) { + mSavedSsidsOnDisable.put(ssid, NUM_SCANS_TO_CONFIRM_AP_LOSS); + } + } + } + + private void writeDisabledSsidsToSharedPreferences() { + Set<String> ssids = new ArraySet<>(); + for (String ssid : mSavedSsidsOnDisable.keySet()) { + ssids.add(HashUtil.getSsidHash(ssid)); + } + Preferences.savedSsidsOnDisable.put(ssids); + } + private void handleScanResultsAvailable() { + if (!mWifiWakeupEnabled) { + return; + } List<ScanResult> scanResults = mWifiManager.getScanResults(); if (scanResults == null) { return; diff --git a/src/com/android/networkrecommendation/wakeup/WifiWakeupHelper.java b/src/com/android/networkrecommendation/wakeup/WifiWakeupHelper.java index 6b1c48d..4e56aa2 100644 --- a/src/com/android/networkrecommendation/wakeup/WifiWakeupHelper.java +++ b/src/com/android/networkrecommendation/wakeup/WifiWakeupHelper.java @@ -16,6 +16,7 @@ package com.android.networkrecommendation.wakeup; import static com.android.networkrecommendation.Constants.TAG; +import static com.android.networkrecommendation.util.NotificationChannelUtil.CHANNEL_ID_WAKEUP; import android.app.Notification; import android.app.NotificationManager; @@ -24,20 +25,23 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.SharedPreferences; import android.content.res.Resources; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.os.Bundle; import android.os.Handler; +import android.provider.Settings; import android.support.annotation.NonNull; import android.support.annotation.VisibleForTesting; import android.text.TextUtils; import android.util.ArraySet; import com.android.networkrecommendation.R; import com.android.networkrecommendation.config.G; +import com.android.networkrecommendation.config.Preferences; +import com.android.networkrecommendation.scoring.util.HashUtil; import com.android.networkrecommendation.util.Blog; +import com.android.networkrecommendation.util.NotificationChannelUtil; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -49,8 +53,6 @@ public class WifiWakeupHelper { /** Unique ID used for the Wi-Fi Enabled notification. */ private static final int NOTIFICATION_ID = R.string.wifi_wakeup_enabled_notification_title; - @VisibleForTesting static final String KEY_SHOWN_SSIDS = "key_shown_ssids"; - @VisibleForTesting static final String ACTION_WIFI_SETTINGS = "com.android.networkrecommendation.wakeup.ACTION_WIFI_SETTINGS"; @@ -73,7 +75,6 @@ public class WifiWakeupHelper { private final NotificationManager mNotificationManager; private final Handler mHandler; private final WifiManager mWifiManager; - private final SharedPreferences mSharedPreferences; /** Whether the wakeup notification is currently displayed. */ private boolean mNotificationShown; @@ -88,9 +89,8 @@ public class WifiWakeupHelper { public void onReceive(Context context, Intent intent) { try { if (ACTION_WIFI_SETTINGS.equals(intent.getAction())) { - // TODO(netrec): Change to @SystemApi Settings.CONFIGURE_WIFI_SETTINGS mContext.startActivity( - new Intent("android.settings.CONFIGURE_WIFI_SETTINGS") + new Intent(Settings.ACTION_WIFI_SETTINGS) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); } else if (ACTION_DISMISS_WIFI_ENABLED_NOTIFICATION.equals( intent.getAction())) { @@ -113,30 +113,11 @@ public class WifiWakeupHelper { Handler handler, NotificationManager notificationManager, WifiManager wifiManager) { - this( - context, - resources, - handler, - notificationManager, - wifiManager, - context.getSharedPreferences("wifi_wakeup", Context.MODE_PRIVATE)); - // BUG(26641175): Code coverage does not like line wraps at 'this($' - } - - @VisibleForTesting - WifiWakeupHelper( - Context context, - Resources resources, - Handler handler, - NotificationManager notificationManager, - WifiManager wifiManager, - SharedPreferences sharedPreferences) { mContext = context; mResources = resources; mNotificationManager = notificationManager; mHandler = handler; mWifiManager = wifiManager; - mSharedPreferences = sharedPreferences; mWifiSessionStarted = false; mNotificationShown = false; mConnectedSsid = null; @@ -160,18 +141,19 @@ public class WifiWakeupHelper { }, NETWORK_CONNECTED_TIMEOUT_MILLIS); - Set<String> ssidSet = mSharedPreferences.getStringSet(KEY_SHOWN_SSIDS, null); - if (ssidSet == null) { - ssidSet = new ArraySet<>(); - } else if (ssidSet.contains(wifiConfiguration.SSID)) { + Set<String> hashedSsidSet = Preferences.ssidsForWakeupShown.get(); + String hashedSsid = HashUtil.getSsidHash(wifiConfiguration.SSID); + if (hashedSsidSet.isEmpty()) { + hashedSsidSet = new ArraySet<>(); + } else if (hashedSsidSet.contains(hashedSsid)) { Blog.d( TAG, "Already showed Wi-Fi Enabled notification for ssid: %s", Blog.pii(wifiConfiguration.SSID, G.Netrec.enableSensitiveLogging.get())); return; } - ssidSet.add(wifiConfiguration.SSID); - mSharedPreferences.edit().putStringSet(KEY_SHOWN_SSIDS, ssidSet).apply(); + hashedSsidSet.add(hashedSsid); + Preferences.ssidsForWakeupShown.put(hashedSsidSet); String title = mResources.getString(R.string.wifi_wakeup_enabled_notification_title); String summary = @@ -192,8 +174,8 @@ public class WifiWakeupHelper { Bundle extras = new Bundle(); extras.putString( Notification.EXTRA_SUBSTITUTE_APP_NAME, - mResources.getString(R.string.android_system_label)); - Notification notification = + mResources.getString(R.string.notification_channel_group_name)); + Notification.Builder notificationBuilder = new Notification.Builder(mContext) .setContentTitle(title) .setSmallIcon(R.drawable.ic_signal_wifi_statusbar_not_connected) @@ -205,9 +187,10 @@ public class WifiWakeupHelper { .setVisibility(Notification.VISIBILITY_PUBLIC) .setCategory(Notification.CATEGORY_STATUS) .setContentIntent(savedNetworkSettingsPendingIntent) - .addExtras(extras) - .build(); - mNotificationManager.notify(TAG, NOTIFICATION_ID, notification); + .setLocalOnly(true) + .addExtras(extras); + NotificationChannelUtil.setChannel(notificationBuilder, CHANNEL_ID_WAKEUP); + mNotificationManager.notify(TAG, NOTIFICATION_ID, notificationBuilder.build()); mNotificationShown = true; } diff --git a/tests/src/com/android/networkrecommendation/NetworkRecommendationServiceTest.java b/tests/src/com/android/networkrecommendation/NetworkRecommendationServiceTest.java index 47656be..644cb93 100644 --- a/tests/src/com/android/networkrecommendation/NetworkRecommendationServiceTest.java +++ b/tests/src/com/android/networkrecommendation/NetworkRecommendationServiceTest.java @@ -45,6 +45,8 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import com.android.networkrecommendation.config.PreferenceFile; + @RunWith(AndroidJUnit4.class) public class NetworkRecommendationServiceTest { @@ -56,6 +58,7 @@ public class NetworkRecommendationServiceTest { public final ServiceTestRule serviceRule = new ServiceTestRule(); private INetworkRecommendationProvider bind() throws TimeoutException { + PreferenceFile.init(InstrumentationRegistry.getTargetContext()); Intent bindIntent = new Intent(InstrumentationRegistry.getTargetContext(), NetworkRecommendationService.class); bindIntent.setAction(NetworkScoreManager.ACTION_RECOMMEND_NETWORKS); |