Add OWNERS in packages/services/NetworkRecommendation am: 87a0ebe5a0
am: a455993a49
Change-Id: I691abcf21f782706f3de52f7e1276f2f60ce8adc
diff --git a/Android.mk b/Android.mk
index 48bce25..8692412 100644
--- a/Android.mk
+++ b/Android.mk
@@ -6,8 +6,9 @@
guava \
jsr305 \
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+# Don't build anything, the needed system APIs have been removed.
+#LOCAL_SRC_FILES := $(call all-java-files-under, src)
+#LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
LOCAL_PACKAGE_NAME := NetworkRecommendation
LOCAL_CERTIFICATE := platform
@@ -24,4 +25,4 @@
include $(BUILD_PACKAGE)
# This finds and builds the test apk as well, so a single make does both.
-include $(call all-makefiles-under,$(LOCAL_PATH))
+#include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 881c8c5..3c61f98 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -27,12 +27,17 @@
<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 -->
- <service android:name=".DefaultNetworkRecommendationService"
+ <service android:name=".NetworkRecommendationService"
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/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 2673295..c0da365 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,5 +1,5 @@
[Hook Scripts]
-checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
+google_java_format = true
[Builtin Hooks]
commit_msg_bug_field = true
diff --git a/proguard.flags b/proguard.flags
index f925f1f..2d236f7 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -1 +1 @@
--keep class com.android.networkrecommendation.DefaultNetworkRecommendationService* { *; }
+-keep class com.android.networkrecommendation.NetworkRecommendationService* { *; }
diff --git a/res/drawable/ic_signal_wifi_statusbar_not_connected.xml b/res/drawable/ic_signal_wifi_statusbar_not_connected.xml
new file mode 100644
index 0000000..d74c31b
--- /dev/null
+++ b/res/drawable/ic_signal_wifi_statusbar_not_connected.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="26dp"
+ android:height="24dp"
+ android:viewportWidth="26"
+ android:viewportHeight="24">
+
+ <path android:pathData="M0 0h26v24H0z" />
+ <path android:fillColor="#FFFFFF"
+ android:fillAlpha=".3"
+ android:pathData="M21 8.5c.85 0 1.64 .23 2.34 .62 l2.24-2.79C25.1 5.96 20.26 2 13 2S.9 5.96 .42 6.32l12.57 15.66 .01 .02 .01 -.01 4.21-5.24c-.76-.87-1.22-2-1.22-3.25 0-2.76 2.24-5 5-5z" />
+ <path android:fillColor="#FFFFFF"
+ android:pathData="M21 10c-1.93 0-3.5 1.57-3.5 3.5h1.75c0-.97 .78 -1.75 1.75-1.75s1.75 .78 1.75 1.75c0 .48-.2 .92 -.51 1.24l-1.09 1.1c-.63 .63 -1.02 1.51-1.02 2.47v.44h1.75c0-1.31 .39 -1.84 1.03-2.47l.78-.8c.5-.5 .82 -1.2 .82 -1.97C24.5 11.57 22.93 10 21 10zm-.95 11.95h1.9v-1.9h-1.9v1.9z" />
+</vector>
+
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 c70c1d2..e208b4c 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -15,19 +15,36 @@
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- 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>
+ <!-- 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. 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>
+ <!-- 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. -->
+ <string name="wifi_available_title_connecting">Connecting to open Wi\u2011Fi network</string>
+ <!-- Notification title when the system has connected to the high quality open network. -->
+ <string name="wifi_available_title_connected">Connected to Wi\u2011Fi network</string>
+ <!-- Notification title when the system failed to connect to the high quality open network. -->
+ <string name="wifi_available_title_failed">Couldn\u0027t connect to Wi\u2011Fi network</string>
+ <!-- Notification content informing the user that tapping on this notification will open the wifi picker. -->
+ <string name="wifi_available_content_failed">Tap to see all networks</string>
- <string name="android_system_label">Android System</string>
+ <!-- Notification action name for connecting to the network specified in the notification body. -->
+ <string name="wifi_available_action_connect">Connect</string>
+ <!-- Notification action name for opening the wifi picker, showing the user all the nearby networks. -->
+ <string name="wifi_available_action_all_networks">All Networks</string>
<!-- A notification is shown when Wi-Fi Wakeup enables Wi-Fi. -->
- <string name="wifi_wakeup_enabled_notification_title">Wi\u2011Fi was turned on automatically</string>
- <string name="wifi_wakeup_enabled_notification_context">"Wi\u2011Fi turned on because you are near saved network, %1$s."</string>
+ <string name="wifi_wakeup_enabled_notification_title">Wi\u2011Fi turned on automatically</string>
+ <string name="wifi_wakeup_enabled_notification_context">You\u0027re near a saved network: %1$s</string>
+
+ <!-- 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/src/com/android/networkrecommendation/Constants.java b/src/com/android/networkrecommendation/Constants.java
new file mode 100644
index 0000000..d680f9f
--- /dev/null
+++ b/src/com/android/networkrecommendation/Constants.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2016 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;
+
+/**
+ * Created by jlapenna on 2/14/17.
+ */
+public final class Constants {
+
+ public static final String TAG = "NetRec";
+
+ private Constants() {}
+}
diff --git a/src/com/android/networkrecommendation/DefaultNetworkRecommendationProvider.java b/src/com/android/networkrecommendation/DefaultNetworkRecommendationProvider.java
index 355d2b8..2f8afa8 100644
--- a/src/com/android/networkrecommendation/DefaultNetworkRecommendationProvider.java
+++ b/src/com/android/networkrecommendation/DefaultNetworkRecommendationProvider.java
@@ -16,6 +16,7 @@
package com.android.networkrecommendation;
+import android.content.Context;
import android.net.NetworkKey;
import android.net.NetworkRecommendationProvider;
import android.net.NetworkScoreManager;
@@ -27,16 +28,18 @@
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.os.Bundle;
-import android.os.Handler;
import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
import android.util.ArrayMap;
-import android.util.Log;
+
+import com.android.networkrecommendation.util.Blog;
+import com.android.networkrecommendation.util.SsidUtil;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.Executor;
import javax.annotation.concurrent.GuardedBy;
@@ -49,13 +52,13 @@
* <p>This recommender is not yet recommended for non-development devices.
*
* <p>To debug:
- * $ adb shell dumpsys activity service DefaultNetworkRecommendationService
+ * $ adb shell dumpsys activity service NetworkRecommendationService
*
* <p>Clear stored scores:
- * $ adb shell dumpsys activity service DefaultNetworkRecommendationService clear
+ * $ adb shell dumpsys activity service NetworkRecommendationService clear
*
* <p>Score a network:
- * $ adb shell dumpsys activity service DefaultNetworkRecommendationService addScore $SCORE
+ * $ adb shell dumpsys activity service NetworkRecommendationService addScore $SCORE
*
* <p>SCORE: "Quoted SSID",bssid|$RSSI_CURVE|metered|captivePortal|BADGE
*
@@ -67,17 +70,17 @@
*
* <p>All commands should be executed on one line, no spaces between each line of the command..
* <p>Eg, A high quality, paid network with captive portal:
- * $ adb shell dumpsys activity service DefaultNetworkRecommendationService addScore \
+ * $ adb shell dumpsys activity service NetworkRecommendationService addScore \
* '\"Metered\",aa:bb:cc:dd:ee:ff\|
* 10,-128,-128,-128,-128,-128,-128,-128,-128,27,27,27,27,27,-128\|1\|1'
*
* <p>Eg, A high quality, unmetered network with captive portal:
- * $ adb shell dumpsys activity service DefaultNetworkRecommendationService addScore \
+ * $ adb shell dumpsys activity service NetworkRecommendationService addScore \
* '\"Captive\",aa:bb:cc:dd:ee:ff\|
* 10,-128,-128,-128,-128,-128,-128,-128,-128,28,28,28,28,28,-128\|0\|1'
*
* <p>Eg, A high quality, unmetered network with any bssid:
- * $ adb shell dumpsys activity service DefaultNetworkRecommendationService addScore \
+ * $ adb shell dumpsys activity service NetworkRecommendationService addScore \
* '\"AnySsid\",00:00:00:00:00:00\|
* 10,-128,-128,-128,-128,-128,-128,-128,-128,29,29,29,29,29,-128\|0\|0'
*/
@@ -85,8 +88,6 @@
public class DefaultNetworkRecommendationProvider
extends NetworkRecommendationProvider implements SynchronousNetworkRecommendationProvider {
static final String TAG = "DefaultNetRecProvider";
- static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
private static final String WILDCARD_MAC = "00:00:00:00:00:00";
@@ -132,9 +133,10 @@
@GuardedBy("mStatsLock")
private int mScoreCounter = 0;
- public DefaultNetworkRecommendationProvider(Handler handler,
+
+ public DefaultNetworkRecommendationProvider(Context context, Executor executor,
NetworkScoreManager scoreManager, ScoreStorage storage) {
- super(handler);
+ super(context, executor);
mScoreManager = scoreManager;
mStorage = storage;
}
@@ -159,39 +161,39 @@
if (results != null) {
for (int i = 0; i < results.length; i++) {
final ScanResult scanResult = results[i];
- if (VERBOSE) Log.v(TAG, "Scan: " + scanResult + " " + i);
+ Blog.v(TAG, "Scan: " + scanResult + " " + i);
// We only want to recommend open networks. This check is taken from
// places like WifiNotificationController and will be extracted to ScanResult in
// a future CL.
if (!"[ESS]".equals(scanResult.capabilities)) {
- if (VERBOSE) Log.v(TAG, "Discarding closed network: " + scanResult);
+ Blog.v(TAG, "Discarding closed network: " + scanResult);
continue;
}
final NetworkKey networkKey = new NetworkKey(
- new WifiKey(ScanResultUtil.createQuotedSSID(scanResult.SSID),
+ new WifiKey(SsidUtil.quoteSsid(scanResult.SSID),
scanResult.BSSID));
- if (VERBOSE) Log.v(TAG, "Evaluating network: " + networkKey);
+ Blog.v(TAG, "Evaluating network: " + networkKey);
// We will only score networks we know about.
final ScoredNetwork network = mStorage.get(networkKey);
if (network == null) {
- if (VERBOSE) Log.v(TAG, "Discarding unscored network: " + scanResult);
+ Blog.v(TAG, "Discarding unscored network: " + scanResult);
continue;
}
final int score = network.rssiCurve.lookupScore(scanResult.level);
- if (VERBOSE) Log.d(TAG, "Scored " + scanResult + ": " + score);
+ Blog.v(TAG, "Scored " + scanResult + ": " + score);
if (score > recommendedScore) {
recommendedScanResult = scanResult;
recommendedScore = score;
- if (VERBOSE) Log.d(TAG, "New recommended network: " + scanResult);
+ Blog.v(TAG, "New recommended network: " + scanResult);
continue;
}
}
} else {
- Log.w(TAG, "Received null scan results in request.");
+ Blog.w(TAG, "Received null scan results in request.");
}
// If we ended up without a recommendation, recommend the provided configuration
@@ -208,7 +210,7 @@
} else {
// Build a configuration based on the scan.
WifiConfiguration recommendedConfig = new WifiConfiguration();
- recommendedConfig.SSID = ScanResultUtil.createQuotedSSID(recommendedScanResult.SSID);
+ recommendedConfig.SSID = SsidUtil.quoteSsid(recommendedScanResult.SSID);
recommendedConfig.BSSID = recommendedScanResult.BSSID;
recommendedConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
recommendationResult = RecommendationResult
@@ -217,7 +219,7 @@
synchronized (mStatsLock) {
mLastRecommended = recommendationResult.getWifiConfiguration();
mRecommendationCounter++;
- if (DEBUG) Log.d(TAG, "Recommending network: " + configToString(mLastRecommended));
+ Blog.d(TAG, "Recommending network: " + configToString(mLastRecommended));
}
return recommendationResult;
}
@@ -254,7 +256,7 @@
return;
}
- if (DEBUG) Log.d(TAG, "Scored networks: " + scoredNetworks);
+ Blog.d(TAG, "Scored networks: " + scoredNetworks);
safelyUpdateScores(scoredNetworks.toArray(new ScoredNetwork[scoredNetworks.size()]));
}
@@ -303,7 +305,7 @@
try {
mScoreManager.updateScores(networkScores);
} catch (SecurityException e) {
- Log.w(TAG, "Tried to update scores when not the active scorer.");
+ Blog.w(TAG, "Tried to update scores when not the active scorer.");
}
}
@@ -313,7 +315,7 @@
try {
mScoreManager.clearScores();
} catch (SecurityException e) {
- Log.w(TAG, "Tried to update scores when not the active scorer.");
+ Blog.w(TAG, "Tried to update scores when not the active scorer.");
}
}
@@ -380,7 +382,7 @@
* score as applying to any bssid with the provided ssid.
*/
public void addScore(ScoredNetwork scoredNetwork) {
- if (DEBUG) Log.d(TAG, "addScore: " + scoredNetwork);
+ Blog.d(TAG, "addScore: " + scoredNetwork);
synchronized (mScores) {
mScores.put(scoredNetwork.networkKey, scoredNetwork);
}
@@ -427,5 +429,4 @@
public ScoredNetwork getCachedScoredNetwork(NetworkKey networkKey) {
return mStorage.get(networkKey);
}
-
}
diff --git a/src/com/android/networkrecommendation/DefaultNetworkRecommendationService.java b/src/com/android/networkrecommendation/DefaultNetworkRecommendationService.java
deleted file mode 100644
index 64e4f13..0000000
--- a/src/com/android/networkrecommendation/DefaultNetworkRecommendationService.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.networkrecommendation;
-
-import android.app.NotificationManager;
-import android.app.Service;
-import android.content.ContentResolver;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.net.NetworkScoreManager;
-import android.net.wifi.WifiManager;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.Looper;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-
-/**
- * Provides network recommendations for the platform.
- */
-public class DefaultNetworkRecommendationService extends Service {
-
- private HandlerThread mHandlerThread;
- private Handler mHandler;
- private DefaultNetworkRecommendationProvider mProvider;
- private WifiNotificationController mWifiNotificationController;
- private WifiWakeupController mWifiWakeupController;
-
- @Override
- public void onCreate() {
- mHandlerThread = new HandlerThread("RecommendationProvider");
- mHandlerThread.start();
- Looper looper = mHandlerThread.getLooper();
- mHandler = new Handler(looper);
- NetworkScoreManager networkScoreManager = getSystemService(NetworkScoreManager.class);
- mProvider = new DefaultNetworkRecommendationProvider(mHandler,
- networkScoreManager, new DefaultNetworkRecommendationProvider.ScoreStorage());
- NotificationManager notificationManager = getSystemService(NotificationManager.class);
- WifiManager wifiManager = getSystemService(WifiManager.class);
- Resources resources = getResources();
- ContentResolver contentResolver = getContentResolver();
- mWifiNotificationController = new WifiNotificationController(
- this, contentResolver, new Handler(looper), mProvider,
- wifiManager, notificationManager,
- new WifiNotificationHelper(this, mProvider));
- WifiWakeupNetworkSelector wifiWakeupNetworkSelector =
- new WifiWakeupNetworkSelector(resources);
- WifiWakeupNotificationHelper wifiWakeupNotificationHelper =
- new WifiWakeupNotificationHelper(this, resources, new Handler(looper),
- notificationManager, wifiManager);
- mWifiWakeupController = new WifiWakeupController(this, contentResolver, looper,
- wifiManager, wifiWakeupNetworkSelector, wifiWakeupNotificationHelper);
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- mWifiWakeupController.start();
- mWifiNotificationController.start();
- return mProvider.getBinder();
- }
-
- @Override
- public boolean onUnbind(Intent intent) {
- mWifiWakeupController.stop();
- mWifiNotificationController.stop();
- return super.onUnbind(intent);
- }
-
- @Override
- protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
- mProvider.dump(fd, writer, args);
- mWifiNotificationController.dump(fd, writer, args);
- mWifiWakeupController.dump(fd, writer, args);
- }
-}
diff --git a/src/com/android/networkrecommendation/Example.java b/src/com/android/networkrecommendation/Example.java
new file mode 100644
index 0000000..a85a112
--- /dev/null
+++ b/src/com/android/networkrecommendation/Example.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.android.networkrecommendation;
+
+import android.net.RecommendationRequest;
+
+import java.lang.reflect.Method;
+
+public class Example {
+
+ public Example() {}
+
+ public void buildRecommendationRequest() {
+ new RecommendationRequest.Builder().build();
+ }
+
+ @SuppressWarnings("unchecked")
+ public RecommendationRequest reflectRecommendationRequest() {
+ try {
+ Class builder = RecommendationRequest.class.getClasses()[0];
+ Object builderObj = builder.getConstructor().newInstance();
+ Method build = builder.getDeclaredMethod("build");
+ return (RecommendationRequest) build.invoke(builderObj);
+ } catch (Exception e) {
+ throw new RuntimeException("Noooo", e);
+ }
+ }
+}
diff --git a/src/com/android/networkrecommendation/ImageUtils.java b/src/com/android/networkrecommendation/ImageUtils.java
deleted file mode 100644
index 4b3d042..0000000
--- a/src/com/android/networkrecommendation/ImageUtils.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;
-
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-
-/** Helper for image manipulation */
-public class ImageUtils {
-
- /**
- * 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/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
new file mode 100644
index 0000000..d4923d0
--- /dev/null
+++ b/src/com/android/networkrecommendation/NetworkRecommendationService.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkrecommendation;
+
+import android.app.NotificationManager;
+import android.app.Service;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.net.NetworkScoreManager;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.UserManager;
+
+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;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Provides network recommendations for the platform.
+ */
+public class NetworkRecommendationService extends Service {
+
+ private HandlerThread mProviderHandlerThread;
+ private Handler mProviderHandler;
+ private HandlerThread mControllerHandlerThread;
+ private Handler mControllerHandler;
+ private DefaultNetworkRecommendationProvider mProvider;
+ private WifiNotificationController mWifiNotificationController;
+ private WifiWakeupController mWifiWakeupController;
+
+ @Override
+ public void onCreate() {
+ mProviderHandlerThread = new HandlerThread("RecommendationProvider");
+ mProviderHandlerThread.start();
+ mProviderHandler = new Handler(mProviderHandlerThread.getLooper());
+ NetworkScoreManager networkScoreManager = getSystemService(NetworkScoreManager.class);
+ mProvider = new DefaultNetworkRecommendationProvider(this, mProviderHandler::post,
+ networkScoreManager, new DefaultNetworkRecommendationProvider.ScoreStorage());
+
+ mControllerHandlerThread = new HandlerThread("RecommendationProvider");
+ 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);
+ UserManager userManager = getSystemService(UserManager.class);
+ Resources resources = getResources();
+ ContentResolver contentResolver = getContentResolver();
+ mWifiNotificationController = new WifiNotificationController(
+ this, contentResolver, mControllerHandler, mProvider,
+ wifiManager, notificationManager, userManager, new WifiNotificationHelper(this));
+ WifiWakeupNetworkSelector wifiWakeupNetworkSelector =
+ new WifiWakeupNetworkSelector(resources, mProvider);
+ WifiWakeupHelper wifiWakeupHelper = new WifiWakeupHelper(this, resources, mControllerHandler,
+ notificationManager, wifiManager);
+ mWifiWakeupController =
+ new WifiWakeupController(this, getContentResolver(), mControllerHandler, wifiManager,
+ powerManager, userManager, wifiWakeupNetworkSelector, wifiWakeupHelper);
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ mWifiWakeupController.start();
+ mWifiNotificationController.start();
+ return mProvider.getBinder();
+ }
+
+ @Override
+ public boolean onUnbind(Intent intent) {
+ mWifiWakeupController.stop();
+ mWifiNotificationController.stop();
+ return super.onUnbind(intent);
+ }
+
+ @Override
+ public void onDestroy() {
+ mProviderHandlerThread.quit();
+ super.onDestroy();
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ mProvider.dump(fd, writer, args);
+ mWifiNotificationController.dump(fd, writer, args);
+ mWifiWakeupController.dump(fd, writer, args);
+ }
+}
diff --git a/src/com/android/networkrecommendation/SynchronousNetworkRecommendationProvider.java b/src/com/android/networkrecommendation/SynchronousNetworkRecommendationProvider.java
index 44a7e0d..b2ff12c 100644
--- a/src/com/android/networkrecommendation/SynchronousNetworkRecommendationProvider.java
+++ b/src/com/android/networkrecommendation/SynchronousNetworkRecommendationProvider.java
@@ -24,7 +24,7 @@
/**
* Provider to return {@link ScoredNetwork} from cached scores in NetworkRecommendationProvider.
*/
-interface SynchronousNetworkRecommendationProvider {
+public interface SynchronousNetworkRecommendationProvider {
/** Returns a {@link ScoredNetwork} if present in the cache. Otherwise, return null. */
ScoredNetwork getCachedScoredNetwork(NetworkKey networkKey);
diff --git a/src/com/android/networkrecommendation/WifiNotificationController.java b/src/com/android/networkrecommendation/WifiNotificationController.java
deleted file mode 100644
index 9dbb665..0000000
--- a/src/com/android/networkrecommendation/WifiNotificationController.java
+++ /dev/null
@@ -1,456 +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;
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.database.ContentObserver;
-import android.graphics.Bitmap;
-import android.net.NetworkInfo;
-import android.net.NetworkScoreManager;
-import android.net.RecommendationRequest;
-import android.net.RecommendationResult;
-import android.net.wifi.ScanResult;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiManager;
-import android.os.Handler;
-import android.provider.Settings;
-import android.support.annotation.Nullable;
-import android.support.annotation.VisibleForTesting;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * Takes care of handling the "open wi-fi network available" notification
- * @hide
- */
-public class WifiNotificationController {
- /**
- * The icon to show in the 'available networks' notification. This will also
- * be the ID of the Notification given to the NotificationManager.
- */
- private static final int ICON_NETWORKS_AVAILABLE = R.drawable.stat_notify_wifi_in_range;
- /**
- * When a notification is shown, we wait this amount before possibly showing it again.
- */
- private final long mNotificationRepeatDelayMs;
- /**
- * Whether the user has set the setting to show the 'available networks' notification.
- */
- private boolean mNotificationEnabled;
- /**
- * Observes the user setting to keep {@link #mNotificationEnabled} in sync.
- */
- private final NotificationEnabledSettingObserver mNotificationEnabledSettingObserver;
- /**
- * The {@link System#currentTimeMillis()} must be at least this value for us
- * to show the notification again.
- */
- private long mNotificationRepeatTime;
- /**
- * Whether the notification is being shown.
- */
- private boolean mNotificationShown;
- /**
- * The number of continuous scans that must occur before consider the
- * supplicant in a scanning state. This allows supplicant to associate with
- * remembered networks that are in the scan results.
- */
- private static final int NUM_SCANS_BEFORE_ACTUALLY_SCANNING = 3;
- /**
- * The number of scans since the last network state change. When this
- * exceeds {@link #NUM_SCANS_BEFORE_ACTUALLY_SCANNING}, we consider the
- * supplicant to actually be scanning. When the network state changes to
- * something other than scanning, we reset this to 0.
- */
- private int mNumScansSinceNetworkStateChange;
- /**
- * Time in milliseconds to display the Connecting notification.
- */
- private static final int TIME_TO_SHOW_CONNECTING_MILLIS = 10000;
- /**
- * Time in milliseconds to display the Connected notification.
- */
- private static final int TIME_TO_SHOW_CONNECTED_MILLIS = 5000;
- /**
- * Time in milliseconds to display the Failed To Connect notification.
- */
- private static final int TIME_TO_SHOW_FAILED_MILLIS = 5000;
- /**
- * Try to connect to provided WifiConfiguration since user wants to
- * connect to the recommended open access point.
- */
- static final String ACTION_CONNECT_TO_RECOMMENDED_NETWORK =
- "com.android.networkrecommendation.CONNECT_TO_RECOMMENDED_NETWORK";
- /**
- * Handles behavior when notification is deleted.
- */
- static final String ACTION_NOTIFICATION_DELETED =
- "com.android.networkrecommendation.NOTIFICATION_DELETED";
- /**
- * Network recommended by {@link NetworkScoreManager#requestRecommendation}.
- */
- private WifiConfiguration mRecommendedNetwork;
- private Bitmap mNotificationBadgeBitmap;
- /**
- * Whether {@link WifiNotificationController} has been started.
- */
- private final AtomicBoolean mStarted;
- /**
- * Runnable to dismiss notification.
- */
- @VisibleForTesting
- final Runnable mDismissNotificationRunnable = () -> {
- removeNotification();
- };
- /**
- * Runnable to show Failed To Connect notification.
- */
- @VisibleForTesting
- final Runnable mShowFailedToConnectNotificationRunnable = () -> {
- showFailedToConnectNotification();
- };
-
- private static final String TAG = "WifiNotification";
-
- private final Context mContext;
- private final Handler mHandler;
- private final ContentResolver mContentResolver;
- private final SynchronousNetworkRecommendationProvider mNetworkRecommendationProvider;
- private final WifiManager mWifiManager;
- private final NotificationManager mNotificationManager;
- private final WifiNotificationHelper mWifiNotificationHelper;
- private NetworkInfo mNetworkInfo;
- private NetworkInfo.DetailedState mDetailedState;
- private volatile int mWifiState;
-
- WifiNotificationController(Context context, ContentResolver contentResolver, Handler handler,
- SynchronousNetworkRecommendationProvider networkRecommendationProvider,
- WifiManager wifiManager, NotificationManager notificationManager,
- WifiNotificationHelper helper) {
- mContext = context;
- mContentResolver = contentResolver;
- mNetworkRecommendationProvider = networkRecommendationProvider;
- mWifiManager = wifiManager;
- mNotificationManager = notificationManager;
- mHandler = handler;
- mWifiNotificationHelper = helper;
- mStarted = new AtomicBoolean(false);
-
- // Setting is in seconds
- mNotificationRepeatDelayMs = Settings.Global.getInt(
- contentResolver, Settings.Global.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, 900) * 1000L;
- mNotificationEnabledSettingObserver = new NotificationEnabledSettingObserver(mHandler);
- }
-
- /** Starts {@link WifiNotificationController}. */
- public void start() {
- if (!mStarted.compareAndSet(false, true)) {
- return;
- }
-
- mWifiState = WifiManager.WIFI_STATE_UNKNOWN;
- mDetailedState = NetworkInfo.DetailedState.IDLE;
-
- IntentFilter filter = new IntentFilter();
- filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
- filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
- filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
- filter.addAction(ACTION_CONNECT_TO_RECOMMENDED_NETWORK);
- filter.addAction(ACTION_NOTIFICATION_DELETED);
-
- mContext.registerReceiver(
- mBroadcastReceiver, filter, null /* broadcastPermission */, mHandler);
- mNotificationEnabledSettingObserver.register();
- }
-
- /** Stops {@link WifiNotificationController}. */
- public void stop() {
- if (!mStarted.compareAndSet(true, false)) {
- return;
- }
- mContext.unregisterReceiver(mBroadcastReceiver);
- mNotificationEnabledSettingObserver.register();
- }
-
- private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent.getAction().equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
- mWifiState = mWifiManager.getWifiState();
- resetNotification();
- } else if (intent.getAction().equals(
- WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
- mNetworkInfo = (NetworkInfo) intent.getParcelableExtra(
- WifiManager.EXTRA_NETWORK_INFO);
- NetworkInfo.DetailedState detailedState =
- mNetworkInfo.getDetailedState();
- if (detailedState != NetworkInfo.DetailedState.SCANNING
- && detailedState != mDetailedState) {
- mDetailedState = detailedState;
- switch (mDetailedState) {
- case CONNECTED:
- updateNotificationOnConnect();
- break;
- case DISCONNECTED:
- case CAPTIVE_PORTAL_CHECK:
- resetNotification();
- break;
-
- // TODO: figure out if these are failure cases when connecting
- case IDLE:
- case SCANNING:
- case CONNECTING:
- case AUTHENTICATING:
- case OBTAINING_IPADDR:
- case SUSPENDED:
- case FAILED:
- case BLOCKED:
- case VERIFYING_POOR_LINK:
- break;
- }
- }
- } else if (intent.getAction().equals(
- WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
- checkAndSetNotification(mNetworkInfo, mWifiManager.getScanResults());
- } else if (intent.getAction().equals(ACTION_CONNECT_TO_RECOMMENDED_NETWORK)) {
- connectToRecommendedNetwork();
- } else if (intent.getAction().equals(ACTION_NOTIFICATION_DELETED)) {
- handleNotificationDeleted();
- }
- }
- };
-
- private void checkAndSetNotification(NetworkInfo networkInfo,
- List<ScanResult> scanResults) {
-
- // TODO: unregister broadcast so we do not have to check here
- // If we shouldn't place a notification on available networks, then
- // don't bother doing any of the following
- if (!mNotificationEnabled) return;
- if (mWifiState != WifiManager.WIFI_STATE_ENABLED) return;
- if (scanResults == null || scanResults.isEmpty()) return;
-
- NetworkInfo.State state = NetworkInfo.State.DISCONNECTED;
- if (networkInfo != null) {
- state = networkInfo.getState();
- }
-
-
- if (state == NetworkInfo.State.DISCONNECTED
- || state == NetworkInfo.State.UNKNOWN) {
- RecommendationResult result = getOpenNetworkRecommendation(scanResults);
- if (result != null
- && result.getWifiConfiguration() != null) {
- mRecommendedNetwork = result.getWifiConfiguration();
- mNotificationBadgeBitmap = mWifiNotificationHelper.createNotificationBadgeBitmap(
- mRecommendedNetwork, scanResults);
- if (++mNumScansSinceNetworkStateChange >= NUM_SCANS_BEFORE_ACTUALLY_SCANNING
- && mNotificationBadgeBitmap != null) {
- /*
- * We have scanned continuously at least
- * NUM_SCANS_BEFORE_NOTIFICATION times. The user
- * probably does not have a remembered network in range,
- * since otherwise supplicant would have tried to
- * associate and thus resetting this counter.
- */
- displayNotification();
- }
- return;
- }
- }
-
- // No open networks in range, remove the notification
- removeNotification();
- }
-
- /**
- * Uses {@link NetworkScoreManager} to choose a qualified network out of the list of
- * {@link ScanResult}s.
- *
- * @return returns the best qualified open networks, if any.
- */
- @Nullable
- private RecommendationResult getOpenNetworkRecommendation(List<ScanResult> scanResults) {
- if (scanResults == null || scanResults.isEmpty()) {
- return null;
- }
- ArrayList<ScanResult> openNetworks = new ArrayList<>();
- for (ScanResult scanResult : scanResults) {
- //A capability of [ESS] represents an open access point
- //that is available for an STA to connect
- if ("[ESS]".equals(scanResult.capabilities)) {
- openNetworks.add(scanResult);
- }
- }
- RecommendationRequest request = new RecommendationRequest.Builder()
- .setScanResults(openNetworks.toArray(new ScanResult[openNetworks.size()]))
- .build();
-
- return mNetworkRecommendationProvider.requestRecommendation(request);
- }
-
- /**
- * Display's a notification that there are open Wi-Fi networks.
- */
- private void displayNotification() {
-
- // Since we use auto cancel on the notification, when the
- // mNetworksAvailableNotificationShown is true, the notification may
- // have actually been canceled. However, when it is false we know
- // for sure that it is not being shown (it will not be shown any other
- // place than here)
-
- // Not enough time has passed to show the notification again
-
- if (System.currentTimeMillis() < mNotificationRepeatTime) {
- return;
- }
- Notification notification = mWifiNotificationHelper.createMainNotification(
- mRecommendedNetwork, mNotificationBadgeBitmap);
- mNotificationRepeatTime = System.currentTimeMillis() + mNotificationRepeatDelayMs;
-
- postNotification(notification);
- mNotificationShown = true;
- }
-
-
- /**
- * Attempts to connect to recommended network and updates the notification to
- * show Connecting state.
- * TODO(33668991): work with UX to polish notification UI and figure out failure states
- */
- private void connectToRecommendedNetwork() {
- if (mRecommendedNetwork == null) {
- return;
- }
- // Attempts to connect to recommended network.
- mWifiManager.connect(mRecommendedNetwork, null /* actionListener */);
-
- // Update notification to connecting status.
- Notification notification = mWifiNotificationHelper.createConnectingNotification(
- mRecommendedNetwork, mNotificationBadgeBitmap);
- postNotification(notification);
- mHandler.postDelayed(mShowFailedToConnectNotificationRunnable,
- TIME_TO_SHOW_CONNECTING_MILLIS);
- }
-
- /**
- * When detailed state changes to CONNECTED, show connected notification or
- * reset notification.
- * TODO: determine failure state where main notification shows but connected.
- */
- private void updateNotificationOnConnect() {
- if (!mNotificationShown) {
- return;
- }
-
- Notification notification = mWifiNotificationHelper.createConnectedNotification(
- mRecommendedNetwork, mNotificationBadgeBitmap);
- postNotification(notification);
- // Remove any previous reset notification callbacks.
- mHandler.removeCallbacks(mShowFailedToConnectNotificationRunnable);
- mHandler.postDelayed(mDismissNotificationRunnable, TIME_TO_SHOW_CONNECTED_MILLIS);
- }
-
- /**
- * Displays the Failed To Connect notification after the Connecting notification
- * is shown for {@link #TIME_TO_SHOW_CONNECTING_MILLIS} duration.
- */
- private void showFailedToConnectNotification() {
- Notification notification =
- mWifiNotificationHelper.createFailedToConnectNotification(mRecommendedNetwork);
- postNotification(notification);
- mHandler.postDelayed(mDismissNotificationRunnable, TIME_TO_SHOW_FAILED_MILLIS);
- }
-
- /**
- * Handles behavior when notification is dismissed.
- */
- private void handleNotificationDeleted() {
- mNotificationShown = false;
- mRecommendedNetwork = null;
- mNotificationBadgeBitmap = null;
- }
-
- private void postNotification(Notification notification) {
- mNotificationManager.notify(null /* tag */, ICON_NETWORKS_AVAILABLE, notification);
- }
-
- /**
- * Clears variables related to tracking whether a notification has been
- * shown recently and clears the current notification.
- */
- private void resetNotification() {
- mNotificationRepeatTime = 0;
- mNumScansSinceNetworkStateChange = 0;
- if (mNotificationShown) {
- removeNotification();
- }
- }
-
- private void removeNotification() {
- mNotificationManager.cancel(null /* tag */, ICON_NETWORKS_AVAILABLE);
- mNotificationShown = false;
- }
-
- void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println("mNotificationEnabled " + mNotificationEnabled);
- pw.println("mNotificationRepeatTime " + mNotificationRepeatTime);
- pw.println("mNotificationShown " + mNotificationShown);
- pw.println("mNumScansSinceNetworkStateChange " + mNumScansSinceNetworkStateChange);
- }
-
- private class NotificationEnabledSettingObserver extends ContentObserver {
- NotificationEnabledSettingObserver(Handler handler) {
- super(handler);
- }
-
- public void register() {
- mContentResolver.registerContentObserver(Settings.Global.getUriFor(
- Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON), true, this);
- mNotificationEnabled = getValue();
- }
-
- public void unregister() {
- mContentResolver.unregisterContentObserver(this);
- }
-
- @Override
- public void onChange(boolean selfChange) {
- super.onChange(selfChange);
-
- mNotificationEnabled = getValue();
- resetNotification();
- }
-
- private boolean getValue() {
- return Settings.Global.getInt(
- mContentResolver,
- Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0) == 1;
- }
- }
-}
diff --git a/src/com/android/networkrecommendation/WifiNotificationHelper.java b/src/com/android/networkrecommendation/WifiNotificationHelper.java
deleted file mode 100644
index e4c8c4e..0000000
--- a/src/com/android/networkrecommendation/WifiNotificationHelper.java
+++ /dev/null
@@ -1,235 +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;
-
-import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
-
-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.graphics.drawable.LayerDrawable;
-import android.net.NetworkKey;
-import android.net.ScoredNetwork;
-import android.net.WifiKey;
-import android.net.wifi.ScanResult;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiManager;
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.text.Spannable;
-import android.text.SpannableString;
-import android.text.style.ForegroundColorSpan;
-
-import java.util.List;
-
-/**
- * Helper class that creates notifications for {@link WifiNotificationController}.
- */
-public class WifiNotificationHelper {
- private final Context mContext;
- private final SynchronousNetworkRecommendationProvider mCachedScoredNetworkProvider;
-
- public WifiNotificationHelper(
- Context context, SynchronousNetworkRecommendationProvider cachedScoredNetworkProvider) {
- mContext = context;
- mCachedScoredNetworkProvider = cachedScoredNetworkProvider;
- }
-
- /**
- * Creates the main open networks notification with two actions. "Options" link to the
- * Wi-Fi picker activity, and "Connect" prompts {@link WifiNotificationController}
- * to connect to the recommended network.
- */
- public Notification createMainNotification(WifiConfiguration config, Bitmap badge) {
- PendingIntent optionsIntent = PendingIntent.getActivity(
- mContext, 0, new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK), FLAG_UPDATE_CURRENT);
- Action optionsAction = new Action.Builder(
- null /* icon */,
- mContext.getText(R.string.wifi_available_options),
- optionsIntent)
- .build();
- PendingIntent connectIntent = PendingIntent.getBroadcast(
- mContext,
- 0,
- new Intent(WifiNotificationController.ACTION_CONNECT_TO_RECOMMENDED_NETWORK),
- FLAG_UPDATE_CURRENT);
- Action connectAction = new Action.Builder(
- null /* icon */,
- mContext.getText(R.string.wifi_available_connect),
- connectIntent)
- .build();
- return createNotificationBuilder(config, badge)
- .addAction(connectAction)
- .addAction(optionsAction)
- .build();
- }
-
- /**
- * Creates the notification that indicates the controller is attempting to connect
- * to the recommended network.
- */
- public Notification createConnectingNotification(WifiConfiguration config, Bitmap badge) {
- Action connecting = new Action.Builder(
- null /* icon */,
- mContext.getText(R.string.wifi_available_connecting),
- null /* pendingIntent */)
- .build();
- return createNotificationBuilder(config, badge)
- .addAction(connecting)
- .setProgress(0 /* max */, 0 /* progress */, true /* indeterminate */)
- .build();
- }
-
- /**
- * 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();
- }
-
- /**
- * 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 = ImageUtils.buildScaledBitmap(
- iconDrawable,
- resources.getDimensionPixelSize(android.R.dimen.notification_large_icon_width),
- resources.getDimensionPixelSize(android.R.dimen.notification_large_icon_height));
- failedText.setSpan(new ForegroundColorSpan(
- Color.RED), 0, failedText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- return createNotificationBuilder(config, icon)
- .setContentText(failedText)
- .build();
- }
-
- private Notification.Builder createNotificationBuilder(WifiConfiguration config, Bitmap badge) {
- CharSequence title = mContext.getText(R.string.wifi_available);
- PendingIntent deleteIntent = PendingIntent.getBroadcast(
- mContext,
- 0,
- new Intent(WifiNotificationController.ACTION_NOTIFICATION_DELETED),
- FLAG_UPDATE_CURRENT);
- return new Notification.Builder(mContext)
- .setDeleteIntent(deleteIntent)
- .setSmallIcon(R.drawable.stat_notify_wifi_in_range)
- .setLargeIcon(badge)
- .setAutoCancel(true)
- .setTicker(title)
- .setContentTitle(title)
- .setContentText(WifiConfigurationUtil.getPrintableSsid(config))
- .addExtras(getSystemLabelExtras());
- }
-
- private Bundle getSystemLabelExtras() {
- Bundle extras = new Bundle();
- extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
- mContext.getString(R.string.android_system_label));
- return extras;
- }
-
- //TODO(34177812): Share this logic between systemUi and Settings.
- static final int[] WIFI_PIE_FOR_BADGING = {
- R.drawable.ic_signal_wifi_badged_0_bars,
- R.drawable.ic_signal_wifi_badged_1_bar,
- R.drawable.ic_signal_wifi_badged_2_bars,
- R.drawable.ic_signal_wifi_badged_3_bars,
- R.drawable.ic_signal_wifi_badged_4_bars
- };
-
- private int getWifiBadgeResourceForEnum(int badgeEnum) {
- switch (badgeEnum) {
- case ScoredNetwork.BADGING_NONE:
- return 0;
- case ScoredNetwork.BADGING_SD:
- return R.drawable.ic_signal_wifi_badged_sd;
- case ScoredNetwork.BADGING_HD:
- return R.drawable.ic_signal_wifi_badged_hd;
- case ScoredNetwork.BADGING_4K:
- return R.drawable.ic_signal_wifi_badged_4k;
- default:
- throw new IllegalArgumentException("No badge resource for enum :" + badgeEnum);
- }
- }
-
- /**
- * Creates a Wi-Fi badge for the notification using matching {@link ScanResult}'s RSSI
- * and badging from {@link CachedScoredNetworkProvider}.
- */
- public Bitmap createNotificationBadgeBitmap(
- @NonNull WifiConfiguration config,
- @NonNull List<ScanResult> scanResults) {
- ScanResult matchingScanResult = findMatchingScanResult(scanResults, config);
- if (matchingScanResult == null) {
- return null;
- }
- int rssi = matchingScanResult.level;
- WifiKey wifiKey = new WifiKey(config.SSID, config.BSSID);
- ScoredNetwork scoredNetwork =
- mCachedScoredNetworkProvider.getCachedScoredNetwork(new NetworkKey(wifiKey));
- if (scoredNetwork != null) {
- return getBadgedWifiBitmap(scoredNetwork.calculateBadge(rssi), rssi);
- }
- return null;
- }
-
- private Bitmap getBadgedWifiBitmap(int badgeEnum, int rssi) {
- if (badgeEnum == ScoredNetwork.BADGING_NONE) {
- return null;
- }
- int signalLevel = WifiManager.calculateSignalLevel(rssi, 5);
- LayerDrawable layerDrawable = new LayerDrawable(new Drawable[]{
- mContext.getDrawable(WIFI_PIE_FOR_BADGING[signalLevel]),
- mContext.getDrawable(getWifiBadgeResourceForEnum(badgeEnum))});
- layerDrawable.setTint(mContext.getColor(R.color.color_tint));
- Resources resources = mContext.getResources();
- return ImageUtils.buildScaledBitmap(layerDrawable,
- resources.getDimensionPixelSize(android.R.dimen.notification_large_icon_width),
- resources.getDimensionPixelSize(android.R.dimen.notification_large_icon_height));
- }
-
- private ScanResult findMatchingScanResult(List<ScanResult> scanResults,
- WifiConfiguration wifiConfiguration) {
- String ssid = WifiConfigurationUtil.removeDoubleQuotes(wifiConfiguration);
- String bssid = wifiConfiguration.BSSID;
- for (ScanResult scanResult : scanResults) {
- if (ssid.equals(scanResult.SSID) && bssid.equals(scanResult.BSSID)) {
- return scanResult;
- }
- }
- return null;
- }
-}
diff --git a/src/com/android/networkrecommendation/WifiWakeupController.java b/src/com/android/networkrecommendation/WifiWakeupController.java
deleted file mode 100644
index 5fe65ef..0000000
--- a/src/com/android/networkrecommendation/WifiWakeupController.java
+++ /dev/null
@@ -1,279 +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;
-
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.database.ContentObserver;
-import android.net.wifi.ScanResult;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiManager;
-import android.os.Handler;
-import android.os.Looper;
-import android.provider.Settings;
-import android.support.annotation.VisibleForTesting;
-import android.text.TextUtils;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.Log;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * Handles enabling Wi-Fi for the Wi-Fi Wakeup feature.
- *
- * <p>
- * This class enables Wi-Fi when the user is near a network that would would autojoined if Wi-Fi
- * were enabled. When a user disables Wi-Fi, Wi-Fi Wakeup will not enable Wi-Fi until the
- * user's context has changed. For saved networks, this context change is defined by the user
- * leaving the range of the saved SSIDs that were in range when the user disabled Wi-Fi.
- *
- * @hide
- */
-public class WifiWakeupController {
- private static final String TAG = "WifiWakeupController";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
-
- /** Number of scans to ensure that a previously in range AP is now out of range. */
- private static final int NUM_SCANS_TO_CONFIRM_AP_LOSS = 3;
-
- private final Context mContext;
- private final ContentResolver mContentResolver;
- private final WifiManager mWifiManager;
- private final WifiWakeupNetworkSelector mWifiWakeupNetworkSelector;
- private final Handler mHandler;
- private final WifiWakeupNotificationHelper mWifiWakeupNotificationHelper;
- private final AtomicBoolean mStarted;
- @VisibleForTesting final ContentObserver mContentObserver;
-
- private final Map<String, WifiConfiguration> mSavedNetworks = new ArrayMap<>();
- private final Set<String> mSavedSsidsInLastScan = new ArraySet<>();
- private final Set<String> mSavedSsids = new ArraySet<>();
- private final Map<String, Integer> mSavedSsidsOnDisable = new ArrayMap<>();
- private int mWifiState;
- private int mWifiApState;
- private boolean mWifiWakeupEnabled;
- private boolean mAirplaneModeEnabled;
-
- WifiWakeupController(Context context, ContentResolver contentResolver, Looper looper,
- WifiManager wifiManager, WifiWakeupNetworkSelector wifiWakeupNetworkSelector,
- WifiWakeupNotificationHelper wifiWakeupNotificationHelper) {
- mContext = context;
- mContentResolver = contentResolver;
- mHandler = new Handler(looper);
- mWifiWakeupNotificationHelper = wifiWakeupNotificationHelper;
- mStarted = new AtomicBoolean(false);
- mWifiManager = wifiManager;
- mWifiWakeupNetworkSelector = wifiWakeupNetworkSelector;
- mContentObserver = new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- mWifiWakeupEnabled = Settings.Global.getInt(mContentResolver,
- Settings.Global.WIFI_WAKEUP_ENABLED, 0) == 1;
- mAirplaneModeEnabled = Settings.Global.getInt(mContentResolver,
- Settings.Global.AIRPLANE_MODE_ON, 0) == 1;
- }
- };
- }
-
- private final BroadcastReceiver mBroadcastReceiver = 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();
- }
- }
- };
-
- /** Starts {@link WifiWakeupController}. */
- public void start() {
- if (!mStarted.compareAndSet(false, true)) {
- return;
- }
- if (DEBUG) {
- Log.d(TAG, "Starting WifiWakeupController.");
- }
- IntentFilter filter = new IntentFilter();
- filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
- filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
- filter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
- filter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
- // TODO(b/33695273): conditionally register this receiver based on wifi enabled setting
- mContext.registerReceiver(mBroadcastReceiver, filter, null, mHandler);
- mContentResolver.registerContentObserver(Settings.Global.getUriFor(
- Settings.Global.WIFI_WAKEUP_ENABLED), true, mContentObserver);
- mContentResolver.registerContentObserver(Settings.Global.getUriFor(
- Settings.Global.AIRPLANE_MODE_ON), true, mContentObserver);
- mContentObserver.onChange(true);
- handleWifiStateChanged();
- handleWifiApStateChanged();
- handleConfiguredNetworksChanged();
- handleScanResultsAvailable();
- }
-
- /** Stops {@link WifiWakeupController}. */
- public void stop() {
- if (!mStarted.compareAndSet(true, false)) {
- return;
- }
- if (DEBUG) {
- Log.d(TAG, "Stopping WifiWakeupController.");
- }
- mContext.unregisterReceiver(mBroadcastReceiver);
- mContentResolver.unregisterContentObserver(mContentObserver);
- }
-
- private void handleWifiApStateChanged() {
- mWifiApState = mWifiManager.getWifiApState();
- if (VERBOSE) {
- Log.v(TAG, "handleWifiApStateChanged: " + mWifiApState);
- }
- }
-
- private void handleConfiguredNetworksChanged() {
- List<WifiConfiguration> wifiConfigurations = mWifiManager.getConfiguredNetworks();
- if (wifiConfigurations == null) {
- return;
- }
- if (VERBOSE) {
- Log.v(TAG, "handleConfiguredNetworksChanged: " + wifiConfigurations.size());
- }
-
- mSavedNetworks.clear();
- mSavedSsids.clear();
- for (int i = 0; i < wifiConfigurations.size(); i++) {
- WifiConfiguration wifiConfiguration = wifiConfigurations.get(i);
- if (wifiConfiguration.status != WifiConfiguration.Status.ENABLED
- && wifiConfiguration.status != WifiConfiguration.Status.CURRENT) {
- continue; // Ignore networks that are not connected or enabled.
- }
- if (wifiConfiguration.useExternalScores) {
- continue; // Ignore externally scored networks.
- }
- if (wifiConfiguration.hasNoInternetAccess()
- || wifiConfiguration.isNoInternetAccessExpected()) {
- continue; // Ignore networks that will likely not have internet access.
- }
- String ssid = WifiConfigurationUtil.removeDoubleQuotes(wifiConfiguration);
- if (TextUtils.isEmpty(ssid)) {
- continue;
- }
- mSavedNetworks.put(ssid, wifiConfiguration);
- mSavedSsids.add(ssid);
- }
- mSavedSsidsInLastScan.retainAll(mSavedSsids);
- }
-
- private void handleWifiStateChanged() {
- mWifiState = mWifiManager.getWifiState();
- if (VERBOSE) {
- Log.v(TAG, "handleWifiStateChanged: " + mWifiState);
- }
- switch (mWifiState) {
- case WifiManager.WIFI_STATE_ENABLED:
- mSavedSsidsOnDisable.clear();
- break;
- case WifiManager.WIFI_STATE_DISABLED:
- for (String ssid : mSavedSsidsInLastScan) {
- mSavedSsidsOnDisable.put(ssid, NUM_SCANS_TO_CONFIRM_AP_LOSS);
- }
- break;
- }
- }
-
- private void handleScanResultsAvailable() {
- List<ScanResult> scanResults = mWifiManager.getScanResults();
- if (scanResults == null) {
- return;
- }
- if (VERBOSE) {
- Log.v(TAG, "handleScanResultsAvailable: " + scanResults.size());
- }
-
- mSavedSsidsInLastScan.clear();
- for (int i = 0; i < scanResults.size(); i++) {
- String ssid = scanResults.get(i).SSID;
- if (mSavedSsids.contains(ssid)) {
- mSavedSsidsInLastScan.add(ssid);
- }
- }
-
- if (mAirplaneModeEnabled
- || mWifiState != WifiManager.WIFI_STATE_DISABLED
- || mWifiApState != WifiManager.WIFI_AP_STATE_DISABLED) {
- return;
- }
-
- // Update mSavedSsidsOnDisable to remove ssids that the user has moved away from.
- for (Map.Entry<String, Integer> entry : mSavedSsidsOnDisable.entrySet()) {
- if (mSavedSsidsInLastScan.contains(entry.getKey())) {
- mSavedSsidsOnDisable.put(entry.getKey(), NUM_SCANS_TO_CONFIRM_AP_LOSS);
- } else {
- if (entry.getValue() > 1) {
- mSavedSsidsOnDisable.put(entry.getKey(), entry.getValue() - 1);
- } else {
- mSavedSsidsOnDisable.remove(entry.getKey());
- }
- }
- }
-
- if (!mSavedSsidsOnDisable.isEmpty()) {
- if (DEBUG) {
- Log.d(TAG, "Latest scan result contains ssids from the disabled set: "
- + mSavedSsidsOnDisable);
- }
- return;
- }
-
- WifiConfiguration selectedNetwork = mWifiWakeupNetworkSelector.selectNetwork(mSavedNetworks,
- scanResults);
- if (selectedNetwork != null) {
- if (DEBUG) {
- Log.d(TAG, "Enabling wifi for ssid: " + selectedNetwork.SSID);
- }
- mWifiManager.setWifiEnabled(true /* enabled */);
- mWifiWakeupNotificationHelper.maybeShowWifiEnabledNotification(selectedNetwork);
- }
- }
-
- void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println("mStarted " + mStarted.get());
- pw.println("mWifiWakeupEnabled: " + mWifiWakeupEnabled);
- pw.println("mSavedSsids: " + mSavedSsids);
- pw.println("mSavedSsidsInLastScan: " + mSavedSsidsInLastScan);
- pw.println("mSavedSsidsOnDisable: " + mSavedSsidsOnDisable);
- }
-}
diff --git a/src/com/android/networkrecommendation/WifiWakeupNetworkSelector.java b/src/com/android/networkrecommendation/WifiWakeupNetworkSelector.java
deleted file mode 100644
index 94fce56..0000000
--- a/src/com/android/networkrecommendation/WifiWakeupNetworkSelector.java
+++ /dev/null
@@ -1,112 +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;
-
-import android.content.res.Resources;
-import android.net.wifi.ScanResult;
-import android.net.wifi.WifiConfiguration;
-
-import java.util.List;
-import java.util.Map;
-
-/**
- * This class determines which network the framework would connect to if Wi-Fi was enabled.
- */
-public class WifiWakeupNetworkSelector {
- private final int mThresholdQualifiedRssi24;
- private final int mThresholdQualifiedRssi5;
- private final int mRssiScoreSlope;
- private final int mRssiScoreOffset;
- private final int mPasspointSecurityAward;
- private final int mSecurityAward;
- private final int mBand5GHzAward;
- private final int mThresholdSaturatedRssi24;
-
- public WifiWakeupNetworkSelector(Resources resources) {
- mThresholdQualifiedRssi24 = resources.getInteger(
- R.integer.config_netrec_wifi_score_low_rssi_threshold_24GHz);
- mThresholdQualifiedRssi5 = resources.getInteger(
- R.integer.config_netrec_wifi_score_low_rssi_threshold_5GHz);
- mRssiScoreSlope = resources.getInteger(
- R.integer.config_netrec_RSSI_SCORE_SLOPE);
- mRssiScoreOffset = resources.getInteger(
- R.integer.config_netrec_RSSI_SCORE_OFFSET);
- mPasspointSecurityAward = resources.getInteger(
- R.integer.config_netrec_PASSPOINT_SECURITY_AWARD);
- mSecurityAward = resources.getInteger(
- R.integer.config_netrec_SECURITY_AWARD);
- mBand5GHzAward = resources.getInteger(
- R.integer.config_netrec_5GHz_preference_boost_factor);
- mThresholdSaturatedRssi24 = resources.getInteger(
- R.integer.config_netrec_wifi_score_good_rssi_threshold_24GHz);
- }
-
- /**
- * Returns the network that the framework would most likely connect to if Wi-Fi was enabled.
- */
- public WifiConfiguration selectNetwork(Map<String, WifiConfiguration> savedNetworks,
- List<ScanResult> scanResults) {
- WifiConfiguration candidateWifiConfiguration = null;
- ScanResult candidateScanResult = null;
- int candidateScore = -1;
- for (int i = 0; i < scanResults.size(); i++) {
- ScanResult scanResult = scanResults.get(i);
- WifiConfiguration wifiConfiguration = savedNetworks.get(scanResult.SSID);
- if (wifiConfiguration == null) {
- continue;
- }
- if (ScanResultUtil.is5GHz(scanResult) && scanResult.level < mThresholdQualifiedRssi5
- || ScanResultUtil.is24GHz(scanResult)
- && scanResult.level < mThresholdQualifiedRssi24) {
- continue;
- }
- if (!ScanResultUtil.doesScanResultMatchWithNetwork(scanResult, wifiConfiguration)) {
- continue;
- }
- int score = calculateScore(scanResult, wifiConfiguration);
- if (candidateScanResult == null
- || calculateScore(scanResult, wifiConfiguration) > candidateScore) {
- candidateScanResult = scanResult;
- candidateWifiConfiguration = wifiConfiguration;
- candidateScore = score;
- }
- }
- return candidateWifiConfiguration;
- }
-
- private int calculateScore(ScanResult scanResult, WifiConfiguration wifiConfiguration) {
- int score = 0;
- // Calculate the RSSI score.
- int rssi = scanResult.level <= mThresholdSaturatedRssi24
- ? scanResult.level : mThresholdSaturatedRssi24;
- score += (rssi + mRssiScoreOffset) * mRssiScoreSlope;
-
- // 5GHz band bonus.
- if (ScanResultUtil.is5GHz(scanResult)) {
- score += mBand5GHzAward;
- }
-
- // Security award.
- if (wifiConfiguration.isPasspoint()) {
- score += mPasspointSecurityAward;
- } else if (!WifiConfigurationUtil.isConfigForOpenNetwork(wifiConfiguration)) {
- score += mSecurityAward;
- }
-
- return score;
- }
-}
diff --git a/src/com/android/networkrecommendation/WifiWakeupNotificationHelper.java b/src/com/android/networkrecommendation/WifiWakeupNotificationHelper.java
deleted file mode 100644
index e044555..0000000
--- a/src/com/android/networkrecommendation/WifiWakeupNotificationHelper.java
+++ /dev/null
@@ -1,190 +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;
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-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 android.util.Log;
-
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Helper class for building and showing notifications for {@link WifiWakeupController}.
- */
-public class WifiWakeupNotificationHelper {
- private static final String TAG = "WifiWakeupNotifHelper";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
- /** 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";
- private static final String ACTION_DISMISS_WIFI_ENABLED_NOTIFICATION =
- "com.android.networkrecommendation.ACTION_DISMISS_WIFI_ENABLED_NOTIFICATION";
- private static final IntentFilter INTENT_FILTER = new IntentFilter();
- private static final long NETWORK_CONNECTED_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(30);
-
- static {
- INTENT_FILTER.addAction(ACTION_DISMISS_WIFI_ENABLED_NOTIFICATION);
- INTENT_FILTER.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
- }
-
- private final Context mContext;
- private final Resources mResources;
- private final NotificationManager mNotificationManager;
- private final Handler mHandler;
- private final WifiManager mWifiManager;
- private final SharedPreferences mSharedPreferences;
-
- @VisibleForTesting
- final Runnable mCancelNotification = new Runnable() {
- @Override
- public void run() {
- cancelNotificationAndUnregisterReceiver();
- }
- };
-
- @VisibleForTesting
- final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (ACTION_DISMISS_WIFI_ENABLED_NOTIFICATION.equals(intent.getAction())) {
- cancelNotificationAndUnregisterReceiver();
- } else if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(intent.getAction())) {
- networkStateChanged();
- }
- }
- };
- private boolean mNotificationShown;
- private String mConnectedSsid;
-
- public WifiWakeupNotificationHelper(Context context, Resources resources, Handler handler,
- NotificationManager notificationManager, WifiManager wifiManager) {
- this(context, resources, handler, notificationManager, wifiManager,
- context.getSharedPreferences("wifi_wakeup", Context.MODE_PRIVATE));
- }
-
- @VisibleForTesting
- WifiWakeupNotificationHelper(Context context, Resources resources, Handler handler,
- NotificationManager notificationManager, WifiManager wifiManager,
- SharedPreferences sharedPreferences) {
- mContext = context;
- mResources = resources;
- mNotificationManager = notificationManager;
- mHandler = handler;
- mWifiManager = wifiManager;
- mSharedPreferences = sharedPreferences;
- mNotificationShown = false;
- mConnectedSsid = null;
- }
-
- /**
- * Show a notification that Wi-Fi has been enabled by Wi-Fi Wakeup.
- *
- * @param wifiConfiguration the {@link WifiConfiguration} that triggered Wi-Fi to wakeup
- */
- public void maybeShowWifiEnabledNotification(@NonNull WifiConfiguration wifiConfiguration) {
- Set<String> ssidSet = mSharedPreferences.getStringSet(KEY_SHOWN_SSIDS, null);
- if (ssidSet == null) {
- ssidSet = new ArraySet<>();
- } else if (ssidSet.contains(wifiConfiguration.SSID)) {
- if (DEBUG) {
- Log.d(TAG, "Already showed Wi-Fi Enabled notification for ssid: "
- + wifiConfiguration.SSID);
- }
- return;
- }
- ssidSet.add(wifiConfiguration.SSID);
- mSharedPreferences.edit().putStringSet(KEY_SHOWN_SSIDS, ssidSet).apply();
-
- String title = mResources.getString(
- R.string.wifi_wakeup_enabled_notification_title);
- String summary = mResources.getString(
- R.string.wifi_wakeup_enabled_notification_context, wifiConfiguration.SSID);
- PendingIntent savedNetworkSettingsPendingIntent = PendingIntent.getActivity(mContext, 0,
- // TODO(b/34135303): update action
- new Intent(Settings.ACTION_WIFI_SETTINGS),
- PendingIntent.FLAG_UPDATE_CURRENT);
- PendingIntent deletePendingIntent = PendingIntent.getActivity(mContext, 0,
- new Intent(ACTION_DISMISS_WIFI_ENABLED_NOTIFICATION),
- PendingIntent.FLAG_UPDATE_CURRENT);
- Bundle extras = new Bundle();
- extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
- mResources.getString(R.string.android_system_label));
- Notification notification = new Notification.Builder(mContext)
- .setContentTitle(title)
- .setSmallIcon(R.drawable.ic_wifi_signal_4)
- .setStyle(new Notification.BigTextStyle().bigText(summary))
- .setAutoCancel(true)
- .setDeleteIntent(deletePendingIntent)
- .setPriority(Notification.PRIORITY_LOW)
- .setVisibility(Notification.VISIBILITY_PUBLIC)
- .setCategory(Notification.CATEGORY_STATUS)
- .setContentIntent(savedNetworkSettingsPendingIntent)
- .addExtras(extras)
- .build();
- mNotificationManager.notify(TAG, NOTIFICATION_ID, notification);
- mNotificationShown = true;
- mContext.registerReceiver(mBroadcastReceiver, INTENT_FILTER, null /* broadcastPermission*/,
- mHandler);
- mHandler.postDelayed(mCancelNotification, NETWORK_CONNECTED_TIMEOUT_MILLIS);
- }
-
- private void cancelNotificationAndUnregisterReceiver() {
- if (mNotificationShown) {
- mNotificationShown = false;
- mConnectedSsid = null;
- mNotificationManager.cancel(TAG, NOTIFICATION_ID);
- mContext.unregisterReceiver(mBroadcastReceiver);
- }
- }
-
- private void networkStateChanged() {
- if (!mWifiManager.isWifiEnabled()) {
- cancelNotificationAndUnregisterReceiver();
- return;
- }
-
- WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
- String ssid = wifiInfo == null ? null : wifiInfo.getSSID();
- if (mConnectedSsid == null) {
- mConnectedSsid = ssid;
- mHandler.removeCallbacks(mCancelNotification);
- } else {
- if (!TextUtils.equals(ssid, mConnectedSsid)) {
- cancelNotificationAndUnregisterReceiver();
- }
- }
- }
-}
diff --git a/src/com/android/networkrecommendation/config/Csv.java b/src/com/android/networkrecommendation/config/Csv.java
new file mode 100644
index 0000000..35b6182
--- /dev/null
+++ b/src/com/android/networkrecommendation/config/Csv.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2009 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 java.io.BufferedReader;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Utilities for parsing and serializing Comma-Separated Value data.
+ * See http://en.wikipedia.org/wiki/Comma-separated_values and
+ * http://tools.ietf.org/html/rfc4180 for details of the format.
+ */
+public class Csv {
+ /** Field delimiter. The C in CSV. */
+ public static final String COMMA = ",";
+
+ /** Record delimiter. "Proper" CSV uses CR-LF, but that would be annoying. */
+ public static final String NEWLINE = "\n";
+
+ /**
+ * Appends a single value to an output string. If the value
+ * contains quotes, commas, newlines, or leading or trailing whitespace,
+ * the value will be written in quotes (with internal quotes doubled).
+ * Newlines are converted to standard CR-LF form. This function does not
+ * write field delimiters -- append {@link COMMA} yourself between values.
+ *
+ * @param value to write, quoted as necessary; must be non-null.
+ * @param output to append (possibly quoted) value to
+ * @throws java.io.IOException if writing to 'output' fails
+ */
+ public static void writeValue(String value, Appendable output) throws IOException {
+ int len = value.length();
+ if (len == 0) return;
+
+ char first = value.charAt(0);
+ char last = value.charAt(len - 1);
+ if (first != ' ' && first != '\t' && last != ' ' && last != '\t' &&
+ value.indexOf('"') < 0 && value.indexOf(',') < 0 &&
+ value.indexOf('\r') < 0 && value.indexOf('\n') < 0) {
+ // No quoting needed.
+ output.append(value);
+ return;
+ }
+
+ output.append('"').append(value.replace("\"", "\"\"")).append('"');
+ }
+
+ /**
+ * Parse a record of comma separated values from an input file.
+ * May read multiple physical lines if values contain embedded newlines.
+ *
+ * @param reader to read one or more physical lines from
+ * @param out array to append unquoted CSV values to
+ * @return true if values were read, false on EOF
+ * @throws java.io.IOException if reading from 'reader' fails
+ */
+ public static boolean parseLine(BufferedReader reader, List<String> out) throws IOException {
+ String text = reader.readLine();
+ if (text == null) return false;
+
+ int pos = 0;
+ do {
+ StringBuilder buf = new StringBuilder();
+ int comma;
+ for (;;) {
+ comma = text.indexOf(',', pos);
+ int quote = text.indexOf('"', pos);
+ if (quote == -1 || (comma != -1 && comma < quote)) break;
+
+ if (pos > 0 && text.charAt(pos - 1) == '"') buf.append('"');
+ buf.append(text, pos, quote);
+ while ((quote = text.indexOf('"', (pos = quote + 1))) == -1) {
+ buf.append(text, pos, text.length()).append('\n');
+ text = reader.readLine();
+ if (text == null) {
+ out.add(buf.toString());
+ return true;
+ }
+ quote = -1;
+ }
+
+ buf.append(text, pos, quote);
+ pos = quote + 1;
+ }
+
+ buf.append(text, pos, comma == -1 ? text.length() : comma);
+ out.add(buf.toString());
+ pos = comma + 1;
+ } while (pos > 0);
+ return true;
+ }
+
+ private Csv() {} // Do not instantiate
+}
diff --git a/src/com/android/networkrecommendation/config/Flag.java b/src/com/android/networkrecommendation/config/Flag.java
new file mode 100644
index 0000000..953fb1e
--- /dev/null
+++ b/src/com/android/networkrecommendation/config/Flag.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2016 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;
+
+/**
+ * Simple configuration parameters for application behavior.
+ * @param <T> A type for a flag value.
+ */
+public class Flag<T> {
+
+ private final T mDefaultValue;
+ private T mOverride;
+
+ public Flag(T defaultValue) {
+ mDefaultValue = defaultValue;
+ mOverride = null;
+ }
+
+ /** Get the currently set flag value. */
+ public T get() {
+ return mOverride != null ? mOverride : mDefaultValue;
+ }
+
+ /** Force a value for testing. */
+ public void override(T value) {
+ mOverride = value;
+ }
+
+ /** Ensure flag state is initialized for tests. */
+ public static void initForTest() {
+ }
+}
diff --git a/src/com/android/networkrecommendation/config/G.java b/src/com/android/networkrecommendation/config/G.java
new file mode 100644
index 0000000..b256f25
--- /dev/null
+++ b/src/com/android/networkrecommendation/config/G.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2016 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;
+
+/** Application behavior parameters. */
+public final class G {
+
+ /**
+ * Configurations related to general behaviors of the netrec module, not tied to a specific
+ * feature.
+ */
+ public interface Netrec {
+ Flag<Boolean> enableSensitiveLogging = new Flag(false);
+ Flag<String> wideAreaNetworks = new Flag("xfinitywifi,XFINITY");
+ }
+
+ private G() {}
+}
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/config/WideAreaNetworks.java b/src/com/android/networkrecommendation/config/WideAreaNetworks.java
new file mode 100644
index 0000000..1a33559
--- /dev/null
+++ b/src/com/android/networkrecommendation/config/WideAreaNetworks.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.networkrecommendation.config;
+
+import static com.android.networkrecommendation.Constants.TAG;
+
+import android.support.annotation.VisibleForTesting;
+import com.android.networkrecommendation.config.G.Netrec;
+import com.android.networkrecommendation.util.Blog;
+import com.google.common.collect.ImmutableSet;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.List;
+
+/** Provides a list of known wide area networks. */
+public class WideAreaNetworks {
+ private WideAreaNetworks() {}
+
+ private static ImmutableSet<String> sWideAreaNetworks;
+
+ /** Initialize the list of wide area networks from the phenotype flag. */
+ public static void init() {
+ sWideAreaNetworks = parseFlag();
+ }
+
+ /**
+ * @param ssid canonical SSID for a network (with quotes removed)
+ * @return {@code true} if {@code ssid} is in the set of wide area networks.
+ */
+ public static boolean contains(String ssid) {
+ if (sWideAreaNetworks == null) {
+ init();
+ }
+ return sWideAreaNetworks.contains(ssid);
+ }
+
+ @VisibleForTesting
+ static ImmutableSet<String> parseFlag() {
+ List<String> parts = new ArrayList<>();
+ BufferedReader reader = new BufferedReader(new StringReader(Netrec.wideAreaNetworks.get()));
+ try {
+ Csv.parseLine(reader, parts);
+ } catch (IOException ex) {
+ Blog.e(TAG, ex, "Error parsing flag");
+ }
+ return ImmutableSet.copyOf(parts);
+ }
+}
diff --git a/src/com/android/networkrecommendation/notify/WifiNotificationController.java b/src/com/android/networkrecommendation/notify/WifiNotificationController.java
new file mode 100644
index 0000000..c134b47
--- /dev/null
+++ b/src/com/android/networkrecommendation/notify/WifiNotificationController.java
@@ -0,0 +1,590 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.networkrecommendation.notify;
+
+import static com.android.networkrecommendation.Constants.TAG;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.net.NetworkInfo;
+import android.net.NetworkScoreManager;
+import android.net.RecommendationRequest;
+import android.net.RecommendationResult;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.support.annotation.IntDef;
+import android.support.annotation.Nullable;
+import com.android.networkrecommendation.R;
+import com.android.networkrecommendation.SynchronousNetworkRecommendationProvider;
+import com.android.networkrecommendation.util.Blog;
+import com.android.networkrecommendation.util.RoboCompatUtil;
+import com.android.networkrecommendation.util.ScanResultUtil;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/** Takes care of handling the "open wi-fi network available" notification */
+public class WifiNotificationController {
+ /** The unique ID of the Notification given to the NotificationManager. */
+ 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;
+
+ /** Whether the user has set the setting to show the 'available networks' notification. */
+ private boolean mNotificationEnabled;
+
+ /** Whether the user has {@link UserManager#DISALLOW_CONFIG_WIFI} restriction. */
+ private boolean mWifiConfigRestricted;
+
+ /** Observes the user setting to keep {@link #mNotificationEnabled} in sync. */
+ private final NotificationEnabledSettingObserver mNotificationEnabledSettingObserver;
+
+ /**
+ * The {@link System#currentTimeMillis()} must be at least this value for us to show the
+ * notification again.
+ */
+ private long mNotificationRepeatTime;
+
+ /** These are all of the possible states for the open networks available notification. */
+ @IntDef({
+ State.NO_RECOMMENDATION,
+ State.SHOWING_RECOMMENDATION_NOTIFICATION,
+ State.CONNECTING_IN_NOTIFICATION,
+ State.CONNECTING_IN_WIFI_PICKER,
+ State.CONNECTED,
+ State.CONNECT_FAILED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface State {
+ int NO_RECOMMENDATION = 0;
+ int SHOWING_RECOMMENDATION_NOTIFICATION = 1;
+ int CONNECTING_IN_NOTIFICATION = 2;
+ int CONNECTING_IN_WIFI_PICKER = 3;
+ int CONNECTED = 4;
+ int CONNECT_FAILED = 5;
+ }
+
+ /**
+ * The {@link System#currentTimeMillis()} must be at least this value to log that open networks
+ * are available.
+ */
+ private long mOpenNetworksLoggingRepeatTime;
+
+ /** Current state of the notification. */
+ @State private int mState = State.NO_RECOMMENDATION;
+
+ /**
+ * The number of continuous scans that must occur before consider the supplicant in a scanning
+ * state. This allows supplicant to associate with remembered networks that are in the scan
+ * results.
+ */
+ private static final int NUM_SCANS_BEFORE_ACTUALLY_SCANNING = 3;
+
+ /**
+ * The number of scans since the last network state change. When this exceeds {@link
+ * #NUM_SCANS_BEFORE_ACTUALLY_SCANNING}, we consider the supplicant to actually be scanning.
+ * When the network state changes to something other than scanning, we reset this to 0.
+ */
+ private int mNumScansSinceNetworkStateChange;
+
+ /** Time in milliseconds to display the Connecting notification. */
+ private static final int TIME_TO_SHOW_CONNECTING_MILLIS = 10000;
+
+ /** Time in milliseconds to display the Connected notification. */
+ private static final int TIME_TO_SHOW_CONNECTED_MILLIS = 5000;
+
+ /** Time in milliseconds to display the Failed To Connect notification. */
+ private static final int TIME_TO_SHOW_FAILED_MILLIS = 5000;
+
+ /** Try to connect to the recommended WifiConfiguration and also open the wifi picker. */
+ static final String ACTION_CONNECT_TO_RECOMMENDED_NETWORK_AND_OPEN_SETTINGS =
+ "com.android.networkrecommendation.notify.CONNECT_TO_RECOMMENDED_NETWORK_AND_OPEN_SETTINGS";
+
+ /** Try to connect to the recommended WifiConfiguration. */
+ static final String ACTION_CONNECT_TO_RECOMMENDED_NETWORK =
+ "com.android.networkrecommendation.notify.CONNECT_TO_RECOMMENDED_NETWORK";
+
+ /** Open wifi picker to see all available networks. */
+ static final String ACTION_PICK_WIFI_NETWORK =
+ "com.android.networkrecommendation.notify.ACTION_PICK_WIFI_NETWORK";
+
+ /** Open wifi picker to see all networks after connect to the recommended network failed. */
+ static final String ACTION_PICK_WIFI_NETWORK_AFTER_CONNECT_FAILURE =
+ "com.android.networkrecommendation.notify.ACTION_PICK_WIFI_NETWORK_AFTER_CONNECT_FAILURE";
+
+ /** Handles behavior when notification is deleted. */
+ static final String ACTION_NOTIFICATION_DELETED =
+ "com.android.networkrecommendation.notify.NOTIFICATION_DELETED";
+
+ /** Network recommended by {@link NetworkScoreManager#requestRecommendation}. */
+ private WifiConfiguration mRecommendedNetwork;
+
+ /** Whether {@link WifiNotificationController} has been started. */
+ private final AtomicBoolean mStarted;
+
+ private static final String NOTIFICATION_TAG = "WifiNotification";
+
+ private final Context mContext;
+ private final Handler mHandler;
+ private final ContentResolver mContentResolver;
+ private final SynchronousNetworkRecommendationProvider mNetworkRecommendationProvider;
+ private final WifiManager mWifiManager;
+ private final NotificationManager mNotificationManager;
+ private final UserManager mUserManager;
+ private final WifiNotificationHelper mWifiNotificationHelper;
+ private NetworkInfo mNetworkInfo;
+ private NetworkInfo.DetailedState mDetailedState;
+ private volatile int mWifiState;
+
+ public WifiNotificationController(
+ Context context,
+ ContentResolver contentResolver,
+ Handler handler,
+ SynchronousNetworkRecommendationProvider networkRecommendationProvider,
+ WifiManager wifiManager,
+ NotificationManager notificationManager,
+ UserManager userManager,
+ WifiNotificationHelper helper) {
+ mContext = context;
+ mContentResolver = contentResolver;
+ mNetworkRecommendationProvider = networkRecommendationProvider;
+ mWifiManager = wifiManager;
+ mNotificationManager = notificationManager;
+ mUserManager = userManager;
+ mHandler = handler;
+ mWifiNotificationHelper = helper;
+ mStarted = new AtomicBoolean(false);
+
+ // Setting is in seconds
+ mNotificationRepeatDelayMs =
+ TimeUnit.SECONDS.toMillis(
+ Settings.Global.getInt(
+ contentResolver,
+ Settings.Global.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY,
+ 900));
+ mNotificationEnabledSettingObserver = new NotificationEnabledSettingObserver(mHandler);
+ }
+
+ /** Starts {@link WifiNotificationController}. */
+ public void start() {
+ if (!mStarted.compareAndSet(false, true)) {
+ return;
+ }
+
+ mWifiState = mWifiManager.getWifiState();
+ mDetailedState = NetworkInfo.DetailedState.IDLE;
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
+ filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+ filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
+ filter.addAction(RoboCompatUtil.ACTION_USER_RESTRICTIONS_CHANGED);
+ filter.addAction(ACTION_CONNECT_TO_RECOMMENDED_NETWORK_AND_OPEN_SETTINGS);
+ filter.addAction(ACTION_CONNECT_TO_RECOMMENDED_NETWORK);
+ filter.addAction(ACTION_NOTIFICATION_DELETED);
+ filter.addAction(ACTION_PICK_WIFI_NETWORK);
+ filter.addAction(ACTION_PICK_WIFI_NETWORK_AFTER_CONNECT_FAILURE);
+
+ mContext.registerReceiver(
+ mBroadcastReceiver, filter, null /* broadcastPermission */, mHandler);
+ mNotificationEnabledSettingObserver.register();
+
+ handleUserRestrictionsChanged();
+ }
+
+ /** Stops {@link WifiNotificationController}. */
+ public void stop() {
+ if (!mStarted.compareAndSet(true, false)) {
+ return;
+ }
+ mContext.unregisterReceiver(mBroadcastReceiver);
+ mNotificationEnabledSettingObserver.unregister();
+ }
+
+ private final BroadcastReceiver mBroadcastReceiver =
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ try {
+ switch (intent.getAction()) {
+ case WifiManager.WIFI_STATE_CHANGED_ACTION:
+ mWifiState = mWifiManager.getWifiState();
+ resetNotification();
+ break;
+ case WifiManager.NETWORK_STATE_CHANGED_ACTION:
+ handleNetworkStateChange(intent);
+ break;
+ case WifiManager.SCAN_RESULTS_AVAILABLE_ACTION:
+ checkAndSetNotification(mNetworkInfo);
+ break;
+ case RoboCompatUtil.ACTION_USER_RESTRICTIONS_CHANGED:
+ handleUserRestrictionsChanged();
+ break;
+ case ACTION_CONNECT_TO_RECOMMENDED_NETWORK_AND_OPEN_SETTINGS:
+ connectToRecommendedNetwork();
+ openWifiPicker();
+ updateOnConnecting(false /* showNotification*/);
+ break;
+ case ACTION_CONNECT_TO_RECOMMENDED_NETWORK:
+ connectToRecommendedNetwork();
+ updateOnConnecting(true /* showNotification*/);
+ break;
+ case ACTION_NOTIFICATION_DELETED:
+ handleNotificationDeleted();
+ break;
+ case ACTION_PICK_WIFI_NETWORK:
+ openWifiPicker();
+ break;
+ case ACTION_PICK_WIFI_NETWORK_AFTER_CONNECT_FAILURE:
+ openWifiPicker();
+ break;
+ default:
+ Blog.e(
+ TAG,
+ "Unexpected broadcast. [action=%s]",
+ intent.getAction());
+ }
+
+ } 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.");
+ }
+ }
+ };
+
+ private void handleNetworkStateChange(Intent intent) {
+ mNetworkInfo = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
+ NetworkInfo.DetailedState detailedState = mNetworkInfo.getDetailedState();
+ if (detailedState != NetworkInfo.DetailedState.SCANNING
+ && detailedState != mDetailedState) {
+ mDetailedState = detailedState;
+ switch (mDetailedState) {
+ case CONNECTED:
+ updateOnConnect();
+ break;
+ case DISCONNECTED:
+ case CAPTIVE_PORTAL_CHECK:
+ resetNotification();
+ break;
+
+ // TODO: figure out if these are failure cases when connecting
+ case IDLE:
+ case SCANNING:
+ case CONNECTING:
+ case DISCONNECTING:
+ case AUTHENTICATING:
+ case OBTAINING_IPADDR:
+ case SUSPENDED:
+ case FAILED:
+ case BLOCKED:
+ case VERIFYING_POOR_LINK:
+ break;
+ }
+ }
+ }
+
+ private void handleUserRestrictionsChanged() {
+ mWifiConfigRestricted = mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_WIFI);
+ Blog.v(TAG, "handleUserRestrictionsChanged: %b", mWifiConfigRestricted);
+ }
+
+ 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
+ || mWifiConfigRestricted
+ || mWifiState != WifiManager.WIFI_STATE_ENABLED
+ || mState > State.SHOWING_RECOMMENDATION_NOTIFICATION) {
+ return;
+ }
+
+ NetworkInfo.State state = NetworkInfo.State.DISCONNECTED;
+ if (networkInfo != null) {
+ state = networkInfo.getState();
+ }
+
+ if (state == NetworkInfo.State.DISCONNECTED || state == NetworkInfo.State.UNKNOWN) {
+ maybeLogOpenNetworksAvailable();
+ RecommendationResult result = getOpenNetworkRecommendation();
+ if (result != null && result.getWifiConfiguration() != null) {
+ mRecommendedNetwork = result.getWifiConfiguration();
+
+ if (++mNumScansSinceNetworkStateChange >= NUM_SCANS_BEFORE_ACTUALLY_SCANNING) {
+ /*
+ * We have scanned continuously at least
+ * NUM_SCANS_BEFORE_NOTIFICATION times. The user
+ * probably does not have a remembered network in range,
+ * since otherwise supplicant would have tried to
+ * associate and thus resetting this counter.
+ */
+ displayNotification();
+ }
+ return;
+ }
+ }
+
+ // No open networks in range, remove the notification
+ removeNotification();
+ }
+
+ private void maybeLogOpenNetworksAvailable() {
+ long now = System.currentTimeMillis();
+ if (now < mOpenNetworksLoggingRepeatTime) {
+ return;
+ }
+ mOpenNetworksLoggingRepeatTime = now + mNotificationRepeatDelayMs;
+ }
+
+ /**
+ * Uses {@link NetworkScoreManager} to choose a qualified network out of the list of {@link
+ * ScanResult}s.
+ *
+ * @return returns the best qualified open networks, if any.
+ */
+ @Nullable
+ private RecommendationResult getOpenNetworkRecommendation() {
+ List<ScanResult> scanResults = mWifiManager.getScanResults();
+ if (scanResults == null || scanResults.isEmpty()) {
+ return null;
+ }
+
+ ArrayList<ScanResult> openNetworks = new ArrayList<>();
+ List<WifiConfiguration> configuredNetworks = mWifiManager.getConfiguredNetworks();
+ for (ScanResult scanResult : scanResults) {
+ //A capability of [ESS] represents an open access point
+ //that is available for an STA to connect
+ //TODO: potentially handle this within NetworkRecommendationProvider instead.
+ if ("[ESS]".equals(scanResult.capabilities)) {
+ if (isSavedNetwork(scanResult, configuredNetworks)) {
+ continue;
+ }
+ openNetworks.add(scanResult);
+ }
+ }
+
+ Blog.d(TAG, "Sending RecommendationRequest. [num_open_networks=%d]", openNetworks.size());
+ RecommendationRequest request =
+ new RecommendationRequest.Builder()
+ .setScanResults(openNetworks.toArray(new ScanResult[openNetworks.size()]))
+ .build();
+ return mNetworkRecommendationProvider.requestRecommendation(request);
+ }
+
+ /** Returns true if scanResult matches the list of saved networks */
+ private boolean isSavedNetwork(ScanResult scanResult, List<WifiConfiguration> savedNetworks) {
+ if (savedNetworks == null) {
+ return false;
+ }
+ for (int i = 0; i < savedNetworks.size(); i++) {
+ if (ScanResultUtil.doesScanResultMatchWithNetwork(scanResult, savedNetworks.get(i))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** Display's a notification that there are open Wi-Fi networks. */
+ private void displayNotification() {
+ // Since we use auto cancel on the notification, when the
+ // mNetworksAvailableNotificationShown is true, the notification may
+ // have actually been canceled. However, when it is false we know
+ // for sure that it is not being shown (it will not be shown any other
+ // place than here)
+
+ // Not enough time has passed to show the notification again
+ if (mState == State.NO_RECOMMENDATION
+ && System.currentTimeMillis() < mNotificationRepeatTime) {
+ return;
+ }
+ Notification notification =
+ mWifiNotificationHelper.createMainNotification(mRecommendedNetwork);
+ mNotificationRepeatTime = System.currentTimeMillis() + mNotificationRepeatDelayMs;
+ postNotification(notification);
+ if (mState != State.SHOWING_RECOMMENDATION_NOTIFICATION) {
+ mState = State.SHOWING_RECOMMENDATION_NOTIFICATION;
+ }
+ }
+
+ /** Opens activity to allow the user to select a wifi network. */
+ private void openWifiPicker() {
+ // Close notification drawer before opening the picker.
+ mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+ mContext.startActivity(
+ new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ removeNotification();
+ }
+
+ /** Attempts to connect to recommended network the recommended network. */
+ private void connectToRecommendedNetwork() {
+ if (mRecommendedNetwork == null) {
+ return;
+ }
+ mRecommendedNetwork.BSSID = null;
+
+ // Attempts to connect to recommended network.
+ RoboCompatUtil.getInstance().connectToWifi(mWifiManager, mRecommendedNetwork);
+ }
+
+ private void updateOnConnecting(boolean showNotification) {
+ if (showNotification) {
+ // Update notification to connecting status.
+ Notification notification =
+ mWifiNotificationHelper.createConnectingNotification(mRecommendedNetwork);
+ postNotification(notification);
+ mState = State.CONNECTING_IN_NOTIFICATION;
+ } else {
+ mState = State.CONNECTING_IN_WIFI_PICKER;
+ }
+ mHandler.postDelayed(
+ () -> {
+ updateOnFailedToConnect();
+ },
+ TIME_TO_SHOW_CONNECTING_MILLIS);
+ }
+
+ /**
+ * When detailed state changes to CONNECTED, show connected notification or reset notification.
+ */
+ private void updateOnConnect() {
+ if (mState == State.CONNECTING_IN_NOTIFICATION) {
+ Notification notification =
+ mWifiNotificationHelper.createConnectedNotification(mRecommendedNetwork);
+ postNotification(notification);
+ mState = State.CONNECTED;
+ mHandler.postDelayed(
+ () -> {
+ if (mState == State.CONNECTED) {
+ removeNotification();
+ }
+ },
+ TIME_TO_SHOW_CONNECTED_MILLIS);
+ } else if (mState == State.CONNECTING_IN_WIFI_PICKER) {
+ removeNotification();
+ }
+ }
+
+ /**
+ * Displays the Failed To Connect notification after the Connecting notification is shown for
+ * {@link #TIME_TO_SHOW_CONNECTING_MILLIS} duration.
+ */
+ private void updateOnFailedToConnect() {
+ if (mState == State.CONNECTING_IN_NOTIFICATION) {
+ Notification notification = mWifiNotificationHelper.createFailedToConnectNotification();
+ postNotification(notification);
+ mState = State.CONNECT_FAILED;
+ mHandler.postDelayed(
+ () -> {
+ if (mState == State.CONNECT_FAILED) {
+ removeNotification();
+ }
+ },
+ TIME_TO_SHOW_FAILED_MILLIS);
+ } else if (mState == State.CONNECTING_IN_WIFI_PICKER) {
+ removeNotification();
+ }
+ }
+
+ /** Handles behavior when notification is dismissed. */
+ private void handleNotificationDeleted() {
+ mState = State.NO_RECOMMENDATION;
+ mRecommendedNetwork = null;
+ }
+
+ private void postNotification(Notification notification) {
+ mNotificationManager.notify(NOTIFICATION_TAG, NOTIFICATION_ID, notification);
+ }
+
+ /**
+ * Clears variables related to tracking whether a notification has been shown recently and
+ * clears the current notification.
+ */
+ private void resetNotification() {
+ if (mState != State.NO_RECOMMENDATION) {
+ removeNotification();
+ }
+ mRecommendedNetwork = null;
+ mNotificationRepeatTime = 0;
+ mNumScansSinceNetworkStateChange = 0;
+ mOpenNetworksLoggingRepeatTime = 0;
+ }
+
+ private void removeNotification() {
+ mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID);
+ mState = State.NO_RECOMMENDATION;
+ mRecommendedNetwork = null;
+ }
+
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("mNotificationEnabled " + mNotificationEnabled);
+ pw.println("mNotificationRepeatTime " + mNotificationRepeatTime);
+ pw.println("mState " + mState);
+ pw.println("mNumScansSinceNetworkStateChange " + mNumScansSinceNetworkStateChange);
+ }
+
+ private class NotificationEnabledSettingObserver extends ContentObserver {
+ NotificationEnabledSettingObserver(Handler handler) {
+ super(handler);
+ }
+
+ public void register() {
+ mContentResolver.registerContentObserver(
+ Settings.Global.getUriFor(
+ Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON),
+ true,
+ this);
+ mNotificationEnabled = getValue();
+ }
+
+ public void unregister() {
+ mContentResolver.unregisterContentObserver(this);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ super.onChange(selfChange);
+
+ mNotificationEnabled = getValue();
+ resetNotification();
+ }
+
+ private boolean getValue() {
+ return Settings.Global.getInt(
+ mContentResolver,
+ Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
+ 0)
+ == 1;
+ }
+ }
+}
diff --git a/src/com/android/networkrecommendation/notify/WifiNotificationHelper.java b/src/com/android/networkrecommendation/notify/WifiNotificationHelper.java
new file mode 100644
index 0000000..b19f25c
--- /dev/null
+++ b/src/com/android/networkrecommendation/notify/WifiNotificationHelper.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.networkrecommendation.notify;
+
+import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
+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.net.wifi.WifiConfiguration;
+import android.os.Bundle;
+import com.android.networkrecommendation.R;
+import com.android.networkrecommendation.util.NotificationChannelUtil;
+
+/** Helper class that creates notifications for {@link WifiNotificationController}. */
+public class WifiNotificationHelper {
+ private final Context mContext;
+
+ public WifiNotificationHelper(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Creates the main open networks notification with two actions. "Options" link to the Wi-Fi
+ * picker activity, and "Connect" prompts {@link WifiNotificationController} to connect to the
+ * recommended network. Tapping on the content body connects to the recommended network and
+ * opens the wifi picker
+ */
+ public Notification createMainNotification(WifiConfiguration config) {
+ PendingIntent allNetworksIntent =
+ PendingIntent.getBroadcast(
+ mContext,
+ 0,
+ new Intent(WifiNotificationController.ACTION_PICK_WIFI_NETWORK),
+ FLAG_UPDATE_CURRENT);
+ Action allNetworksAction =
+ new Action.Builder(
+ null /* icon */,
+ mContext.getText(R.string.wifi_available_action_all_networks),
+ allNetworksIntent)
+ .build();
+ PendingIntent connectIntent =
+ PendingIntent.getBroadcast(
+ mContext,
+ 0,
+ new Intent(
+ WifiNotificationController.ACTION_CONNECT_TO_RECOMMENDED_NETWORK),
+ FLAG_UPDATE_CURRENT);
+ Action connectAction =
+ new Action.Builder(
+ null /* icon */,
+ mContext.getText(R.string.wifi_available_action_connect),
+ connectIntent)
+ .build();
+ PendingIntent connectAndOpenPickerIntent =
+ PendingIntent.getBroadcast(
+ mContext,
+ 0,
+ new Intent(
+ WifiNotificationController
+ .ACTION_CONNECT_TO_RECOMMENDED_NETWORK_AND_OPEN_SETTINGS),
+ FLAG_UPDATE_CURRENT);
+ return createNotificationBuilder(R.string.wifi_available_title, config.SSID)
+ .setContentIntent(connectAndOpenPickerIntent)
+ .addAction(connectAction)
+ .addAction(allNetworksAction)
+ .build();
+ }
+
+ /**
+ * Creates the notification that indicates the controller is attempting to connect to the
+ * recommended network.
+ */
+ public Notification createConnectingNotification(WifiConfiguration config) {
+ return createNotificationBuilder(R.string.wifi_available_title_connecting, config.SSID)
+ .setProgress(0 /* max */, 0 /* progress */, true /* indeterminate */)
+ .build();
+ }
+
+ /**
+ * Creates the notification that indicates the controller successfully connected to the
+ * recommended network.
+ */
+ 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. Tapping this notification opens the wifi picker.
+ */
+ public Notification createFailedToConnectNotification() {
+ PendingIntent openWifiPickerAfterFailure =
+ PendingIntent.getBroadcast(
+ mContext,
+ 0,
+ new Intent(
+ WifiNotificationController
+ .ACTION_PICK_WIFI_NETWORK_AFTER_CONNECT_FAILURE),
+ FLAG_UPDATE_CURRENT);
+ return createNotificationBuilder(
+ R.string.wifi_available_title_failed,
+ mContext.getString(R.string.wifi_available_content_failed))
+ .setContentIntent(openWifiPickerAfterFailure)
+ .setAutoCancel(true)
+ .build();
+ }
+
+ 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);
+ 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)
+ .setShowWhen(false)
+ .setLocalOnly(true)
+ .addExtras(getOverrideLabelExtras());
+ return NotificationChannelUtil.setChannel(builder, CHANNEL_ID_NETWORK_AVAILABLE);
+ }
+
+ private Bundle getOverrideLabelExtras() {
+ Bundle extras = new Bundle();
+ extras.putString(
+ Notification.EXTRA_SUBSTITUTE_APP_NAME,
+ mContext.getString(R.string.notification_channel_group_name));
+ return extras;
+ }
+}
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..b27bbe6
--- /dev/null
+++ b/src/com/android/networkrecommendation/scoring/util/NetworkUtil.java
@@ -0,0 +1,142 @@
+/*
+ * 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 static com.android.networkrecommendation.util.ScanResultUtil.isScanResultForOpenNetwork;
+
+import android.content.Context;
+import android.net.NetworkKey;
+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.util.Blog;
+import com.android.networkrecommendation.util.ScanResultUtil;
+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);
+ NetworkKey networkKey = ScanResultUtil.createNetworkKey(scanResult);
+ if (networkKey != null) {
+ openKeys.put(networkKey, isScanResultForOpenNetwork(scanResult));
+ }
+ }
+ return openKeys;
+ }
+
+ /** 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..4a8c80b
--- /dev/null
+++ b/src/com/android/networkrecommendation/scoring/util/Util.java
@@ -0,0 +1,71 @@
+/*
+ * 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 com.android.networkrecommendation.Constants.TAG;
+
+import android.content.Context;
+import android.net.NetworkScoreManager;
+import android.net.ScoredNetwork;
+import com.android.networkrecommendation.util.Blog;
+
+/** 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);
+ }
+
+ /** Clear scores in the platform if we're the active scorer. */
+ public static boolean safelyClearScores(Context context) {
+ try {
+ NetworkScoreManager scoreManager =
+ (NetworkScoreManager) context.getSystemService(Context.NETWORK_SCORE_SERVICE);
+ scoreManager.clearScores();
+ return true;
+ } catch (SecurityException e) {
+ Blog.v(
+ TAG,
+ e,
+ "SecurityException trying to clear scores, probably just an unavoidable race condition. "
+ + "Ignoring.");
+ return false;
+ }
+ }
+
+ /** Update scores in the platform if we're the active scorer. */
+ public static boolean safelyUpdateScores(Context context, ScoredNetwork[] networkScores) {
+ try {
+ NetworkScoreManager scoreManager =
+ (NetworkScoreManager) context.getSystemService(Context.NETWORK_SCORE_SERVICE);
+ scoreManager.updateScores(networkScores);
+ return true;
+ } catch (SecurityException e) {
+ Blog.v(
+ TAG,
+ e,
+ "SecurityException trying to update scores, probably just an unavoidable race condition. "
+ + "Ignoring.");
+ return false;
+ }
+ }
+}
diff --git a/src/com/android/networkrecommendation/util/Blog.java b/src/com/android/networkrecommendation/util/Blog.java
new file mode 100644
index 0000000..47eff57
--- /dev/null
+++ b/src/com/android/networkrecommendation/util/Blog.java
@@ -0,0 +1,176 @@
+/*
+ * 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.text.TextUtils;
+import android.util.Base64;
+import android.util.Log;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.IllegalFormatException;
+import java.util.Locale;
+
+/**
+ * Wrapper for {@link Log} which adds the calling class/method to logged items.
+ *
+ * This works by traversing up the call stack and finding the first calling class whose name does
+ * not end with "Log" (allowing clients to add another layer such as AppLog when a constant tag is
+ * desired across the application).
+ */
+public final class Blog {
+ private static final String TAG = "Blog";
+
+ private Blog() {}
+
+ public static void i(String tag, String format, Object... args) {
+ Log.i(tag, buildMessage(format, args));
+ }
+
+ public static void i(String tag, Throwable tr, String format, Object... args) {
+ Log.i(tag, buildMessage(format, args), tr);
+ }
+
+ public static void v(String tag, String format, Object... args) {
+ // Not guarded with Log.isLoggable - these calls should be stripped out by proguard for
+ // release builds.
+ Log.v(tag, buildMessage(format, args));
+ }
+
+ public static void v(String tag, Throwable tr, String format, Object... args) {
+ // Not guarded with Log.isLoggable - these calls should be stripped out by proguard for
+ // release builds.
+ Log.v(tag, buildMessage(format, args), tr);
+ }
+
+ public static void d(String tag, String format, Object... args) {
+ if (Log.isLoggable(tag, Log.DEBUG)) {
+ Log.d(tag, buildMessage(format, args));
+ }
+ }
+
+ public static void d(String tag, Throwable tr, String format, Object... args) {
+ if (Log.isLoggable(tag, Log.DEBUG)) {
+ Log.d(tag, buildMessage(format, args), tr);
+ }
+ }
+
+ public static void w(String tag, Throwable tr, String format, Object... args) {
+ Log.w(tag, buildMessage(format, args), tr);
+ }
+
+ public static void w(String tag, String format, Object... args) {
+ Log.w(tag, buildMessage(format, args));
+ }
+
+ public static void e(String tag, String format, Object... args) {
+ Log.e(tag, buildMessage(format, args));
+ }
+
+ public static void e(String tag, Throwable tr, String format, Object... args) {
+ Log.e(tag, buildMessage(format, args), tr);
+ }
+
+ public static void wtf(String tag, String format, Object... args) {
+ // Ensure we always log a stack trace when calling Log.wtf.
+ Log.wtf(tag, buildMessage(format, args), new WhatATerribleException());
+ }
+
+ public static void wtf(String tag, Throwable tr, String format, Object...args) {
+ Log.wtf(tag, buildMessage(format, args), new WhatATerribleException(tr));
+ }
+
+ /**
+ * Redact personally identifiable information for production users.
+ *
+ * If:
+ * 1) String is null
+ * 2) String is empty
+ * 3) enableSensitiveLogging param passed in is true
+ * Return the String value of the input object.
+ * Else:
+ * Return a SHA-1 hash of the String value of the input object.
+ */
+ public static String pii(Object pii, boolean enableSensitiveLogging) {
+ String val = String.valueOf(pii);
+ if (pii == null || TextUtils.isEmpty(val) || enableSensitiveLogging) {
+ return val;
+ }
+ return "[" + secureHash(val.getBytes()) + "]";
+ }
+
+ /**
+ * Returns a secure hash (using the SHA1 algorithm) of the provided input.
+ *
+ * @return the hash
+ * @param input the bytes for which the secure hash should be computed.
+ */
+ public static String secureHash(byte[] input) {
+ MessageDigest messageDigest;
+ try {
+ messageDigest = MessageDigest.getInstance("SHA-1");
+ } catch (NoSuchAlgorithmException e) {
+ return null;
+ }
+ messageDigest.update(input);
+ byte[] result = messageDigest.digest();
+ return Base64.encodeToString(
+ result, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP);
+ }
+
+ /**
+ * Formats the caller's provided message and prepends useful info like
+ * calling thread ID and method name.
+ */
+ private static String buildMessage(String format, Object... args) {
+ String msg;
+ try {
+ msg = (args == null || args.length == 0) ? format
+ : String.format(Locale.US, format, args);
+ } catch (IllegalFormatException ife) {
+ String formattedArgs = Arrays.toString(args);
+ wtf(TAG, ife, "msg: \"%s\" args: %s", format, formattedArgs);
+ msg = format + " " + formattedArgs;
+ }
+ StackTraceElement[] trace = new Throwable().fillInStackTrace().getStackTrace();
+ String caller = "<unknown>";
+ // Walk up the stack looking for the first caller outside of Blog whose name doesn't end
+ // with "Log". It will be at least two frames up, so start there.
+ for (int i = 2; i < trace.length; i++) {
+ String callingClass = trace[i].getClassName();
+ if (!callingClass.equals(Blog.class.getName())
+ && !callingClass.endsWith("Log")) {
+ callingClass = callingClass.substring(callingClass.lastIndexOf('.') + 1);
+ callingClass = callingClass.substring(callingClass.lastIndexOf('$') + 1);
+ caller = callingClass + "." + trace[i].getMethodName();
+ break;
+ }
+ }
+ return String.format(Locale.US, "[%d] %s: %s",
+ Thread.currentThread().getId(), caller, msg);
+ }
+
+ /** Named exception thrown when calling wtf(). */
+ public static class WhatATerribleException extends RuntimeException {
+ public WhatATerribleException() {
+ super();
+ }
+ public WhatATerribleException(Throwable t) {
+ super(t);
+ }
+ }
+}
diff --git a/src/com/android/networkrecommendation/util/NotificationChannelUtil.java b/src/com/android/networkrecommendation/util/NotificationChannelUtil.java
new file mode 100644
index 0000000..ebe5ff3
--- /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.setChannelId(channelId);
+ }
+}
diff --git a/src/com/android/networkrecommendation/util/OCompatFactory.java b/src/com/android/networkrecommendation/util/OCompatFactory.java
new file mode 100644
index 0000000..ef949cf
--- /dev/null
+++ b/src/com/android/networkrecommendation/util/OCompatFactory.java
@@ -0,0 +1,43 @@
+/*
+ * 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.net.NetworkKey;
+import android.net.RssiCurve;
+import android.net.ScoredNetwork;
+import android.os.Bundle;
+
+/**
+ * Encapsulates the constructors for O-only objects to make unit testing possible, since those APIs
+ * are not supported by Robolectric yet. Once Robolectric supports O, we can remove this class.
+ */
+public class OCompatFactory {
+
+ /** Create a ScoredNetwork with the provided parameters. */
+ public static ScoredNetwork createScoredNetworkOCompat(
+ NetworkKey networkToScore,
+ RssiCurve rssiCurve,
+ boolean meteredHint,
+ Bundle attributesBundle) {
+ return new ScoredNetwork(networkToScore, rssiCurve, meteredHint, attributesBundle);
+ }
+
+ /** Create an RssiCurve with the provided parameters. */
+ public static RssiCurve createRssiCurveOCompat(
+ int start, int bucketWidth, byte[] rssiBuckets, int activeNetworkRssiBoost) {
+ return new RssiCurve(start, bucketWidth, rssiBuckets, activeNetworkRssiBoost);
+ }
+}
diff --git a/src/com/android/networkrecommendation/util/RoboCompatUtil.java b/src/com/android/networkrecommendation/util/RoboCompatUtil.java
new file mode 100644
index 0000000..7aa0d7b
--- /dev/null
+++ b/src/com/android/networkrecommendation/util/RoboCompatUtil.java
@@ -0,0 +1,99 @@
+/*
+ * 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.content.res.Resources.Theme;
+import android.graphics.drawable.Drawable;
+import android.net.NetworkBadging;
+import android.net.RssiCurve;
+import android.net.ScoredNetwork;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+import android.os.UserManager;
+import android.support.annotation.VisibleForTesting;
+
+/**
+ * This class provides access to @SystemApi methods that were added in Android O and not yet
+ * available in using the --config robo_experimental configuration when testing.
+ */
+public class RoboCompatUtil {
+
+ /**
+ * {@link UserManager#ACTION_USER_RESTRICTIONS_CHANGED}. TODO: remove when string is available
+ * in experimental.
+ */
+ public static final String ACTION_USER_RESTRICTIONS_CHANGED =
+ "android.os.action.USER_RESTRICTIONS_CHANGED";
+
+ private static RoboCompatUtil mRoboCompatUtil;
+
+ private RoboCompatUtil() {}
+
+ /** Get a shared instance of this utility. */
+ public static synchronized RoboCompatUtil getInstance() {
+ if (mRoboCompatUtil == null) {
+ mRoboCompatUtil = new RoboCompatUtil();
+ }
+ return mRoboCompatUtil;
+ }
+
+ @VisibleForTesting
+ public static void setInstanceForTesting(RoboCompatUtil roboCompatUtil) {
+ mRoboCompatUtil = roboCompatUtil;
+ }
+
+ /** Wraps WifiManager.connect. */
+ public void connectToWifi(WifiManager wifiManager, WifiConfiguration wifiConfiguration) {
+ wifiManager.connect(wifiConfiguration, null /* actionListener */);
+ }
+
+ /** Wraps WifiConfiguration.hasNoInternetAccess. */
+ @SuppressWarnings("unchecked")
+ public boolean hasNoInternetAccess(WifiConfiguration wifiConfiguration) {
+ return wifiConfiguration.hasNoInternetAccess();
+ }
+
+ /** Wraps WifiConfiguration.isNoInternetAccessExpected. */
+ public boolean isNoInternetAccessExpected(WifiConfiguration wifiConfiguration) {
+ return wifiConfiguration.isNoInternetAccessExpected();
+ }
+
+ /** Wraps WifiConfiguration.useExternalScores. */
+ public boolean useExternalScores(WifiConfiguration wifiConfiguration) {
+ return wifiConfiguration.useExternalScores;
+ }
+
+ /** Wraps WifiConfiguration.isPasspoint. */
+ public boolean isPasspoint(WifiConfiguration wifiConfiguration) {
+ return wifiConfiguration.isPasspoint();
+ }
+
+ /** Wraps NetworkBadging.getWifiIcon. */
+ public Drawable getWifiIcon(int signalLevel, int badging, Theme theme) {
+ return NetworkBadging.getWifiIcon(signalLevel, badging, theme);
+ }
+
+ /** Wraps RssiCurve.activeNetworkRssiBoost. */
+ public int activeNetworkRssiBoost(RssiCurve curve) {
+ return curve.activeNetworkRssiBoost;
+ }
+
+ /** Wraps ScoredNetwork.attributes. */
+ public Bundle attributes(ScoredNetwork scoredNetwork) {
+ return scoredNetwork.attributes;
+ }
+}
diff --git a/src/com/android/networkrecommendation/ScanResultUtil.java b/src/com/android/networkrecommendation/util/ScanResultUtil.java
similarity index 64%
rename from src/com/android/networkrecommendation/ScanResultUtil.java
rename to src/com/android/networkrecommendation/util/ScanResultUtil.java
index 08b71d0..4587e91 100644
--- a/src/com/android/networkrecommendation/ScanResultUtil.java
+++ b/src/com/android/networkrecommendation/util/ScanResultUtil.java
@@ -13,39 +13,44 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.networkrecommendation.util;
-package com.android.networkrecommendation;
+import static com.android.networkrecommendation.Constants.TAG;
+import static com.android.networkrecommendation.util.SsidUtil.quoteSsid;
+import android.net.NetworkKey;
+import android.net.WifiKey;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
-import android.support.annotation.VisibleForTesting;
+import android.support.annotation.Nullable;
import android.text.TextUtils;
+import com.android.networkrecommendation.config.G;
/**
- * Scan result utility for any {@link ScanResult} related operations.
- * TODO(b/34125341): Delete this class once exposed as a SystemApi
+ * Scan result utility for any {@link ScanResult} related operations. TODO(b/34125341): Delete this
+ * class once exposed as a SystemApi
*/
public class ScanResultUtil {
/**
- * Helper method to check if the provided |scanResult| corresponds to a PSK network or not.
- * This checks if the provided capabilities string contains PSK encryption type or not.
+ * Helper method to check if the provided |scanResult| corresponds to a PSK network or not. This
+ * checks if the provided capabilities string contains PSK encryption type or not.
*/
public static boolean isScanResultForPskNetwork(ScanResult scanResult) {
return scanResult.capabilities.contains("PSK");
}
/**
- * Helper method to check if the provided |scanResult| corresponds to a EAP network or not.
- * This checks if the provided capabilities string contains EAP encryption type or not.
+ * Helper method to check if the provided |scanResult| corresponds to a EAP network or not. This
+ * checks if the provided capabilities string contains EAP encryption type or not.
*/
public static boolean isScanResultForEapNetwork(ScanResult scanResult) {
return scanResult.capabilities.contains("EAP");
}
/**
- * Helper method to check if the provided |scanResult| corresponds to a WEP network or not.
- * This checks if the provided capabilities string contains WEP encryption type or not.
+ * Helper method to check if the provided |scanResult| corresponds to a WEP network or not. This
+ * checks if the provided capabilities string contains WEP encryption type or not.
*/
public static boolean isScanResultForWepNetwork(ScanResult scanResult) {
return scanResult.capabilities.contains("WEP");
@@ -57,17 +62,43 @@
* encryption types or not.
*/
public static boolean isScanResultForOpenNetwork(ScanResult scanResult) {
- return !(isScanResultForWepNetwork(scanResult) || isScanResultForPskNetwork(scanResult)
+ return !(isScanResultForWepNetwork(scanResult)
+ || isScanResultForPskNetwork(scanResult)
|| isScanResultForEapNetwork(scanResult));
}
+ /** Create a {@link NetworkKey} from a ScanResult, properly quoting the SSID. */
+ @Nullable
+ public static final NetworkKey createNetworkKey(ScanResult scanResult) {
+ WifiKey wifiKey = createWifiKey(scanResult);
+ if (wifiKey == null) {
+ return null;
+ }
+ return new NetworkKey(wifiKey);
+ }
+
/**
* Helper method to quote the SSID in Scan result to use for comparing/filling SSID stored in
* WifiConfiguration object.
*/
- @VisibleForTesting
- public static String createQuotedSSID(String ssid) {
- return "\"" + ssid + "\"";
+ @Nullable
+ public static WifiKey createWifiKey(ScanResult result) {
+ if (result == null) {
+ Blog.e(TAG, "Couldn't create WifiKey, provided scan result is null.");
+ return null;
+ }
+ try {
+ return new WifiKey(quoteSsid(result.SSID), result.BSSID);
+ } catch (IllegalArgumentException | NullPointerException e) {
+ // Expect IllegalArgumentException only in Android O.
+ Blog.e(
+ TAG,
+ e,
+ "Couldn't make a wifi key from %s/%s",
+ Blog.pii(result.SSID, G.Netrec.enableSensitiveLogging.get()),
+ Blog.pii(result.BSSID, G.Netrec.enableSensitiveLogging.get()));
+ return null;
+ }
}
/** @return {@code true} if the result is for a 2.4GHz network. */
@@ -91,13 +122,13 @@
}
/**
- * Checks if the provided |scanResult| match with the provided |config|. Essentially checks
- * if the network config and scan result have the same SSID and encryption type.
+ * Checks if the provided |scanResult| match with the provided |config|. Essentially checks if
+ * the network config and scan result have the same SSID and encryption type.
*/
public static boolean doesScanResultMatchWithNetwork(
ScanResult scanResult, WifiConfiguration config) {
// Add the double quotes to the scan result SSID for comparison with the network configs.
- String configSSID = createQuotedSSID(scanResult.SSID);
+ String configSSID = quoteSsid(scanResult.SSID);
if (TextUtils.equals(config.SSID, configSSID)) {
if (ScanResultUtil.isScanResultForPskNetwork(scanResult)
&& WifiConfigurationUtil.isConfigForPskNetwork(config)) {
diff --git a/src/com/android/networkrecommendation/util/SsidUtil.java b/src/com/android/networkrecommendation/util/SsidUtil.java
new file mode 100644
index 0000000..b15ac5f
--- /dev/null
+++ b/src/com/android/networkrecommendation/util/SsidUtil.java
@@ -0,0 +1,117 @@
+/*
+ * 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 static com.android.networkrecommendation.Constants.TAG;
+
+import android.net.WifiKey;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+import com.android.networkrecommendation.config.G;
+
+/** Utility methods for Wifi Network SSID and BSSID manipulation. */
+public final class SsidUtil {
+
+ // A special BSSID used to indicate a wildcard/ignore.
+ // The MAC address this refers to is reserved by IANA
+ // http://www.iana.org/assignments/ethernet-numbers/ethernet-numbers.xhtml
+ public static final String BSSID_IGNORE = "00:00:5E:00:00:00";
+
+ /** Quote an SSID if it hasn't already been quoted. */
+ @Nullable
+ public static String quoteSsid(@Nullable String ssid) {
+ if (ssid == null) {
+ return null;
+ }
+ if (isValidQuotedSsid(ssid)) {
+ return ssid;
+ }
+ return "\"" + ssid + "\"";
+ }
+
+ /** Strip initial and final quotations marks from an SSID. */
+ public static String unquoteSsid(@Nullable String ssid) {
+ if (ssid == null) {
+ return null;
+ }
+ return ssid.replaceAll("^\"", "").replaceAll("\"$", "");
+ }
+
+ /**
+ * Create a WifiKey for the given SSID/BSSID. Returns null if the key could not be created
+ * (ssid/bssid are not valid patterns).
+ */
+ @Nullable
+ public static WifiKey createWifiKey(String ssid, String bssid) {
+ try {
+ return new WifiKey(quoteSsid(ssid), bssid);
+ } catch (IllegalArgumentException | NullPointerException e) {
+ // Expect IllegalArgumentException only in Android O.
+ Blog.e(
+ TAG,
+ e,
+ "Couldn't make a wifi key from %s/%s",
+ Blog.pii(ssid, G.Netrec.enableSensitiveLogging.get()),
+ Blog.pii(bssid, G.Netrec.enableSensitiveLogging.get()));
+ return null;
+ }
+ }
+
+ /**
+ * Returns true if the given string will be accepted as an SSID by WifiKey, especially meaning
+ * it is quoted.
+ */
+ public static boolean isValidQuotedSsid(@Nullable String ssid) {
+ return ssid != null && ssid.startsWith("\"") && ssid.endsWith("\"");
+ }
+
+ /** Thows IllegalArgumentException if the given string cannot be used for an SSID in WifiKey. */
+ public static void checkIsValidQuotedSsid(String ssid) {
+ if (!isValidQuotedSsid(ssid)) {
+ throw new IllegalArgumentException("SSID " + ssid + " expected to be quoted");
+ }
+ }
+
+ /**
+ * Returns true if the canonical version of two SSIDs (ignoring wrapping quotations) is equal.
+ */
+ public static boolean areEqual(@Nullable String ssid1, @Nullable String ssid2) {
+ String quotedSsid1 = quoteSsid(ssid1);
+ String quotedSsid2 = quoteSsid(ssid2);
+ return TextUtils.equals(quotedSsid1, quotedSsid2);
+ }
+
+ /**
+ * Returns a string version of the SSID for logging which is typically redacted.
+ *
+ * <p>The ID will only be returned verbatim if the enableSensitiveLogging flag is set.
+ */
+ public static String getRedactedId(String ssid) {
+ return Blog.pii(String.format("%s", ssid), G.Netrec.enableSensitiveLogging.get());
+ }
+
+ /**
+ * 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 enableSensitiveLogging flag is set.
+ */
+ public static String getRedactedId(String ssid, String bssid) {
+ return Blog.pii(String.format("%s/%s", ssid, bssid), G.Netrec.enableSensitiveLogging.get());
+ }
+
+ // Can't instantiate.
+ private SsidUtil() {}
+}
diff --git a/src/com/android/networkrecommendation/WifiConfigurationUtil.java b/src/com/android/networkrecommendation/util/WifiConfigurationUtil.java
similarity index 98%
rename from src/com/android/networkrecommendation/WifiConfigurationUtil.java
rename to src/com/android/networkrecommendation/util/WifiConfigurationUtil.java
index d653a4f..3616cd7 100644
--- a/src/com/android/networkrecommendation/WifiConfigurationUtil.java
+++ b/src/com/android/networkrecommendation/util/WifiConfigurationUtil.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.networkrecommendation;
+package com.android.networkrecommendation.util;
import android.net.wifi.WifiConfiguration;
diff --git a/src/com/android/networkrecommendation/wakeup/WifiWakeupController.java b/src/com/android/networkrecommendation/wakeup/WifiWakeupController.java
new file mode 100644
index 0000000..4c9b4c1
--- /dev/null
+++ b/src/com/android/networkrecommendation/wakeup/WifiWakeupController.java
@@ -0,0 +1,404 @@
+/*
+ * 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.wakeup;
+
+import static com.android.networkrecommendation.Constants.TAG;
+
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.support.annotation.VisibleForTesting;
+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;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Handles enabling Wi-Fi for the Wi-Fi Wakeup feature.
+ *
+ * <p>This class enables Wi-Fi when the user is near a network that would would autojoined if Wi-Fi
+ * were enabled. When a user disables Wi-Fi, Wi-Fi Wakeup will not enable Wi-Fi until the user's
+ * context has changed. For saved networks, this context change is defined by the user leaving the
+ * range of the saved SSIDs that were in range when the user disabled Wi-Fi.
+ *
+ * @hide
+ */
+public class WifiWakeupController {
+ /** Number of scans to ensure that a previously in range AP is now out of range. */
+ private static final int NUM_SCANS_TO_CONFIRM_AP_LOSS = 3;
+
+ private final Context mContext;
+ private final ContentResolver mContentResolver;
+ private final WifiManager mWifiManager;
+ private final PowerManager mPowerManager;
+ private final UserManager mUserManager;
+ private final WifiWakeupNetworkSelector mWifiWakeupNetworkSelector;
+ private final Handler mHandler;
+ private final WifiWakeupHelper mWifiWakeupHelper;
+ private final AtomicBoolean mStarted;
+ @VisibleForTesting final ContentObserver mContentObserver;
+
+ private final Map<String, WifiConfiguration> mSavedNetworks = new ArrayMap<>();
+ private final Set<String> mSavedSsidsInLastScan = new ArraySet<>();
+ private final Set<String> mSavedSsids = new ArraySet<>();
+ private final Map<String, Integer> mSavedSsidsOnDisable = new ArrayMap<>();
+ private final SavedNetworkCounts mSavedNetworkCounts = new SavedNetworkCounts();
+ private int mWifiState;
+ private int mWifiApState;
+ private boolean mWifiWakeupEnabled;
+ private boolean mAirplaneModeEnabled;
+ private boolean mAutopilotEnabledWifi;
+ private boolean mPowerSaverModeOn;
+ private boolean mWifiConfigRestricted;
+
+ public WifiWakeupController(
+ Context context,
+ ContentResolver contentResolver,
+ Handler handler,
+ WifiManager wifiManager,
+ PowerManager powerManager,
+ UserManager userManager,
+ WifiWakeupNetworkSelector wifiWakeupNetworkSelector,
+ WifiWakeupHelper wifiWakeupHelper) {
+ mContext = context;
+ mContentResolver = contentResolver;
+ mHandler = handler;
+ mWifiWakeupHelper = wifiWakeupHelper;
+ mStarted = new AtomicBoolean(false);
+ mWifiManager = wifiManager;
+ mPowerManager = powerManager;
+ mUserManager = userManager;
+ mWifiWakeupNetworkSelector = wifiWakeupNetworkSelector;
+ mContentObserver =
+ new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ mWifiWakeupEnabled =
+ Settings.Global.getInt(
+ mContentResolver,
+ Settings.Global.WIFI_WAKEUP_ENABLED,
+ 0)
+ == 1;
+ mAirplaneModeEnabled =
+ Settings.Global.getInt(
+ mContentResolver,
+ Settings.Global.AIRPLANE_MODE_ON,
+ 0)
+ == 1;
+ Blog.d(
+ TAG,
+ "onChange: [mWifiWakeupEnabled=%b,mAirplaneModeEnabled=%b]",
+ mWifiWakeupEnabled,
+ mAirplaneModeEnabled);
+ }
+ };
+ }
+
+ private final BroadcastReceiver mBroadcastReceiver =
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ 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();
+ } else if (RoboCompatUtil.ACTION_USER_RESTRICTIONS_CHANGED.equals(
+ intent.getAction())) {
+ handleUserRestrictionsChanged();
+ }
+ } 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.");
+ }
+ }
+ };
+
+ /** Starts {@link WifiWakeupController}. */
+ public void start() {
+ if (!mStarted.compareAndSet(false, true)) {
+ return;
+ }
+ Blog.d(TAG, "Starting WifiWakeupController.");
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
+ filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
+ filter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
+ filter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
+ filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
+ filter.addAction(RoboCompatUtil.ACTION_USER_RESTRICTIONS_CHANGED);
+ // TODO(b/33695273): conditionally register this receiver based on wifi enabled setting
+ mContext.registerReceiver(mBroadcastReceiver, filter, null, mHandler);
+ mContentResolver.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.WIFI_WAKEUP_ENABLED),
+ true,
+ mContentObserver);
+ mContentResolver.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON),
+ true,
+ mContentObserver);
+ mContentObserver.onChange(true);
+ handlePowerSaverModeChanged();
+ handleUserRestrictionsChanged();
+ handleWifiApStateChanged();
+ handleConfiguredNetworksChanged();
+ handleWifiStateChanged(true);
+ handleScanResultsAvailable();
+ }
+
+ /** Stops {@link WifiWakeupController}. */
+ public void stop() {
+ if (!mStarted.compareAndSet(true, false)) {
+ return;
+ }
+ Blog.d(TAG, "Stopping WifiWakeupController.");
+ mContext.unregisterReceiver(mBroadcastReceiver);
+ mContentResolver.unregisterContentObserver(mContentObserver);
+ }
+
+ private void handlePowerSaverModeChanged() {
+ mPowerSaverModeOn = mPowerManager.isPowerSaveMode();
+ Blog.v(TAG, "handlePowerSaverModeChanged: %b", mPowerSaverModeOn);
+ }
+
+ private void handleWifiApStateChanged() {
+ mWifiApState = mWifiManager.getWifiApState();
+ Blog.v(TAG, "handleWifiApStateChanged: %d", mWifiApState);
+ }
+
+ private void handleUserRestrictionsChanged() {
+ mWifiConfigRestricted = mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_WIFI);
+ Blog.v(TAG, "handleUserRestrictionsChanged: %b", mWifiConfigRestricted);
+ }
+
+ private void handleConfiguredNetworksChanged() {
+ List<WifiConfiguration> wifiConfigurations = mWifiManager.getConfiguredNetworks();
+ if (wifiConfigurations == null) {
+ return;
+ }
+ Blog.v(TAG, "handleConfiguredNetworksChanged: %d", wifiConfigurations.size());
+
+ mSavedNetworkCounts.clear();
+ mSavedNetworkCounts.total = wifiConfigurations.size();
+ mSavedNetworks.clear();
+ mSavedSsids.clear();
+ for (int i = 0; i < wifiConfigurations.size(); i++) {
+ WifiConfiguration wifiConfiguration = wifiConfigurations.get(i);
+ if (wifiConfiguration.status != WifiConfiguration.Status.ENABLED
+ && wifiConfiguration.status != WifiConfiguration.Status.CURRENT) {
+ continue; // Ignore networks that are not connected or enabled.
+ }
+ mSavedNetworkCounts.enabled++;
+ if (RoboCompatUtil.getInstance().hasNoInternetAccess(wifiConfiguration)) {
+ mSavedNetworkCounts.noInternetAccess++;
+ continue; // Ignore networks that do not have verified internet access.
+ }
+ if (RoboCompatUtil.getInstance().isNoInternetAccessExpected(wifiConfiguration)) {
+ mSavedNetworkCounts.noInternetAccessExpected++;
+ continue; // Ignore networks that are expected not to have internet access.
+ }
+ String ssid = WifiConfigurationUtil.removeDoubleQuotes(wifiConfiguration);
+ if (TextUtils.isEmpty(ssid)) {
+ continue;
+ }
+ if (WideAreaNetworks.contains(ssid)) {
+ mSavedNetworkCounts.blacklisted++;
+ continue; // Ignore wide area networks.
+ }
+ mSavedNetworks.put(ssid, wifiConfiguration);
+ mSavedSsids.add(ssid);
+
+ if (WifiConfigurationUtil.isConfigForOpenNetwork(wifiConfiguration)) {
+ mSavedNetworkCounts.open++;
+ }
+ if (RoboCompatUtil.getInstance().useExternalScores(wifiConfiguration)) {
+ mSavedNetworkCounts.useExternalScores++;
+ }
+ }
+ mSavedSsidsInLastScan.retainAll(mSavedSsids);
+ }
+
+ private void handleWifiStateChanged(boolean calledOnStart) {
+ mWifiState = mWifiManager.getWifiState();
+ Blog.v(TAG, "handleWifiStateChanged: %d", mWifiState);
+
+ switch (mWifiState) {
+ case WifiManager.WIFI_STATE_ENABLED:
+ mSavedSsidsOnDisable.clear();
+ if (!mAutopilotEnabledWifi) {}
+ break;
+ case WifiManager.WIFI_STATE_DISABLED:
+ 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);
+
+ mAutopilotEnabledWifi = false;
+ break;
+ default: // Only handle ENABLED and DISABLED states
+ }
+ }
+
+ 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 || mWifiConfigRestricted) {
+ return;
+ }
+ List<ScanResult> scanResults = mWifiManager.getScanResults();
+ if (scanResults == null) {
+ return;
+ }
+ Blog.v(TAG, "handleScanResultsAvailable: %d", scanResults.size());
+
+ mSavedSsidsInLastScan.clear();
+ for (int i = 0; i < scanResults.size(); i++) {
+ String ssid = scanResults.get(i).SSID;
+ if (mSavedSsids.contains(ssid)) {
+ mSavedSsidsInLastScan.add(ssid);
+ }
+ }
+
+ if (mAirplaneModeEnabled
+ || mWifiState != WifiManager.WIFI_STATE_DISABLED
+ || mWifiApState != WifiManager.WIFI_AP_STATE_DISABLED
+ || mPowerSaverModeOn) {
+ return;
+ }
+
+ // Update mSavedSsidsOnDisable to remove ssids that the user has moved away from.
+ for (Map.Entry<String, Integer> entry : mSavedSsidsOnDisable.entrySet()) {
+ if (mSavedSsidsInLastScan.contains(entry.getKey())) {
+ mSavedSsidsOnDisable.put(entry.getKey(), NUM_SCANS_TO_CONFIRM_AP_LOSS);
+ } else {
+ if (entry.getValue() > 1) {
+ mSavedSsidsOnDisable.put(entry.getKey(), entry.getValue() - 1);
+ } else {
+ mSavedSsidsOnDisable.remove(entry.getKey());
+ }
+ }
+ }
+
+ if (!mSavedSsidsOnDisable.isEmpty()) {
+ Blog.d(
+ TAG,
+ "Scan results contain ssids from the disabled set: %s",
+ mSavedSsidsOnDisable);
+ return;
+ }
+
+ if (mSavedSsidsInLastScan.isEmpty()) {
+ Blog.v(TAG, "Scan results do not contain any saved ssids.");
+ return;
+ }
+
+ WifiConfiguration selectedNetwork =
+ mWifiWakeupNetworkSelector.selectNetwork(mSavedNetworks, scanResults);
+ if (selectedNetwork != null) {
+ Blog.d(
+ TAG,
+ "Enabling wifi for ssid: %s",
+ Blog.pii(selectedNetwork.SSID, G.Netrec.enableSensitiveLogging.get()));
+
+ mAutopilotEnabledWifi = true;
+ mWifiManager.setWifiEnabled(true /* enabled */);
+ mWifiWakeupHelper.startWifiSession(selectedNetwork);
+ }
+ }
+
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("mStarted " + mStarted.get());
+ pw.println("mWifiWakeupEnabled: " + mWifiWakeupEnabled);
+ pw.println("mSavedSsids: " + mSavedSsids);
+ pw.println("mSavedSsidsInLastScan: " + mSavedSsidsInLastScan);
+ pw.println("mSavedSsidsOnDisable: " + mSavedSsidsOnDisable);
+ }
+
+ /** Class to track counts for saved networks for logging. */
+ private static class SavedNetworkCounts {
+ int total;
+ int open;
+ int enabled;
+ int noInternetAccess;
+ int noInternetAccessExpected;
+ int useExternalScores;
+ int blacklisted;
+
+ void clear() {
+ total = 0;
+ open = 0;
+ enabled = 0;
+ noInternetAccess = 0;
+ noInternetAccessExpected = 0;
+ useExternalScores = 0;
+ blacklisted = 0;
+ }
+ }
+}
diff --git a/src/com/android/networkrecommendation/wakeup/WifiWakeupHelper.java b/src/com/android/networkrecommendation/wakeup/WifiWakeupHelper.java
new file mode 100644
index 0000000..8b4da55
--- /dev/null
+++ b/src/com/android/networkrecommendation/wakeup/WifiWakeupHelper.java
@@ -0,0 +1,231 @@
+/*
+ * 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.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;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+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;
+
+/**
+ * Helper class for logging Wi-Fi Wakeup sessions and showing showing notifications for {@link
+ * WifiWakeupController}.
+ */
+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 ACTION_WIFI_SETTINGS =
+ "com.android.networkrecommendation.wakeup.ACTION_WIFI_SETTINGS";
+
+ @VisibleForTesting
+ static final String ACTION_DISMISS_WIFI_ENABLED_NOTIFICATION =
+ "com.android.networkrecommendation.wakeup.ACTION_DISMISS_WIFI_ENABLED_NOTIFICATION";
+
+ private static final long NETWORK_CONNECTED_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(30);
+ private static final IntentFilter INTENT_FILTER = new IntentFilter();
+
+ static {
+ INTENT_FILTER.addAction(ACTION_DISMISS_WIFI_ENABLED_NOTIFICATION);
+ INTENT_FILTER.addAction(ACTION_WIFI_SETTINGS);
+ INTENT_FILTER.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+ }
+
+ private final Context mContext;
+ private final Resources mResources;
+ private final NotificationManager mNotificationManager;
+ private final Handler mHandler;
+ private final WifiManager mWifiManager;
+
+ /** Whether the wakeup notification is currently displayed. */
+ private boolean mNotificationShown;
+ /** True when the device is still connected to the first connected ssid since wakeup. */
+ private boolean mWifiSessionStarted;
+ /** The first connected ssid after wakeup enabled wifi. */
+ private String mConnectedSsid;
+
+ private final BroadcastReceiver mBroadcastReceiver =
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ try {
+ if (ACTION_WIFI_SETTINGS.equals(intent.getAction())) {
+ mContext.startActivity(
+ new Intent(Settings.ACTION_WIFI_SETTINGS)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ } else if (ACTION_DISMISS_WIFI_ENABLED_NOTIFICATION.equals(
+ intent.getAction())) {
+ cancelNotificationIfNeeded();
+ } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(
+ intent.getAction())) {
+ networkStateChanged();
+ }
+ } 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.");
+ }
+ }
+ };
+
+ public WifiWakeupHelper(
+ Context context,
+ Resources resources,
+ Handler handler,
+ NotificationManager notificationManager,
+ WifiManager wifiManager) {
+ mContext = context;
+ mResources = resources;
+ mNotificationManager = notificationManager;
+ mHandler = handler;
+ mWifiManager = wifiManager;
+ mWifiSessionStarted = false;
+ mNotificationShown = false;
+ mConnectedSsid = null;
+ }
+
+ /**
+ * Start tracking a wifi wakeup session. Optionally show a notification that Wi-Fi has been
+ * enabled by Wi-Fi Wakeup if one has not been displayed for this {@link WifiConfiguration}.
+ *
+ * @param wifiConfiguration the {@link WifiConfiguration} that triggered Wi-Fi to wakeup
+ */
+ public void startWifiSession(@NonNull WifiConfiguration wifiConfiguration) {
+ mContext.registerReceiver(
+ mBroadcastReceiver, INTENT_FILTER, null /* broadcastPermission*/, mHandler);
+ mWifiSessionStarted = true;
+ mHandler.postDelayed(
+ () -> {
+ if (mWifiSessionStarted && mConnectedSsid == null) {
+ endWifiSession();
+ }
+ },
+ NETWORK_CONNECTED_TIMEOUT_MILLIS);
+
+ Set<String> hashedSsidSet = Preferences.ssidsForWakeupShown.get();
+ String hashedSsid = HashUtil.getSsidHash(wifiConfiguration.SSID);
+ if (hashedSsidSet.isEmpty()) {
+ hashedSsidSet = new ArraySet<>();
+ } else if (hashedSsidSet.contains(hashedSsid)) {
+ Blog.i(
+ TAG,
+ "Already showed Wi-Fi Enabled notification for ssid: %s",
+ Blog.pii(wifiConfiguration.SSID, G.Netrec.enableSensitiveLogging.get()));
+ return;
+ }
+ hashedSsidSet.add(hashedSsid);
+ Preferences.ssidsForWakeupShown.put(hashedSsidSet);
+
+ String title = mResources.getString(R.string.wifi_wakeup_enabled_notification_title);
+ String summary =
+ mResources.getString(
+ R.string.wifi_wakeup_enabled_notification_context, wifiConfiguration.SSID);
+ PendingIntent savedNetworkSettingsPendingIntent =
+ PendingIntent.getBroadcast(
+ mContext,
+ 0,
+ new Intent(ACTION_WIFI_SETTINGS),
+ PendingIntent.FLAG_UPDATE_CURRENT);
+ PendingIntent deletePendingIntent =
+ PendingIntent.getBroadcast(
+ mContext,
+ 0,
+ new Intent(ACTION_DISMISS_WIFI_ENABLED_NOTIFICATION),
+ PendingIntent.FLAG_UPDATE_CURRENT);
+ Bundle extras = new Bundle();
+ extras.putString(
+ Notification.EXTRA_SUBSTITUTE_APP_NAME,
+ mResources.getString(R.string.notification_channel_group_name));
+ int smallIcon = R.drawable.ic_signal_wifi_statusbar_not_connected;
+ Notification.Builder notificationBuilder =
+ new Notification.Builder(mContext)
+ .setContentTitle(title)
+ .setSmallIcon(smallIcon)
+ .setColor(mContext.getColor(R.color.color_tint))
+ .setStyle(new Notification.BigTextStyle().bigText(summary))
+ .setAutoCancel(true)
+ .setShowWhen(false)
+ .setDeleteIntent(deletePendingIntent)
+ .setPriority(Notification.PRIORITY_LOW)
+ .setVisibility(Notification.VISIBILITY_PUBLIC)
+ .setCategory(Notification.CATEGORY_STATUS)
+ .setContentIntent(savedNetworkSettingsPendingIntent)
+ .setLocalOnly(true)
+ .addExtras(extras);
+ NotificationChannelUtil.setChannel(notificationBuilder, CHANNEL_ID_WAKEUP);
+ mNotificationManager.notify(TAG, NOTIFICATION_ID, notificationBuilder.build());
+ mNotificationShown = true;
+ }
+
+ private void networkStateChanged() {
+ if (!mWifiManager.isWifiEnabled()) {
+ endWifiSession();
+ return;
+ }
+
+ WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
+ String ssid = wifiInfo == null ? null : wifiInfo.getSSID();
+ if (mConnectedSsid == null) {
+ if (!TextUtils.isEmpty(ssid)) {
+ mConnectedSsid = ssid;
+ }
+ } else if (!TextUtils.equals(ssid, mConnectedSsid)) {
+ endWifiSession();
+ }
+ }
+
+ private void endWifiSession() {
+ if (mWifiSessionStarted) {
+ mWifiSessionStarted = false;
+ cancelNotificationIfNeeded();
+ mConnectedSsid = null;
+ mContext.unregisterReceiver(mBroadcastReceiver);
+ }
+ }
+
+ private void cancelNotificationIfNeeded() {
+ if (mNotificationShown) {
+ mNotificationShown = false;
+ mNotificationManager.cancel(TAG, NOTIFICATION_ID);
+ }
+ }
+}
diff --git a/src/com/android/networkrecommendation/wakeup/WifiWakeupNetworkSelector.java b/src/com/android/networkrecommendation/wakeup/WifiWakeupNetworkSelector.java
new file mode 100644
index 0000000..61ed2cb
--- /dev/null
+++ b/src/com/android/networkrecommendation/wakeup/WifiWakeupNetworkSelector.java
@@ -0,0 +1,139 @@
+/*
+ * 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.wakeup;
+
+import android.content.res.Resources;
+import android.net.RecommendationRequest;
+import android.net.RecommendationResult;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
+import android.support.annotation.Nullable;
+import android.util.ArraySet;
+import com.android.networkrecommendation.R;
+import com.android.networkrecommendation.SynchronousNetworkRecommendationProvider;
+import com.android.networkrecommendation.util.RoboCompatUtil;
+import com.android.networkrecommendation.util.ScanResultUtil;
+import com.android.networkrecommendation.util.WifiConfigurationUtil;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/** This class determines which network the framework would connect to if Wi-Fi was enabled. */
+public class WifiWakeupNetworkSelector {
+ private final int mThresholdQualifiedRssi24;
+ private final int mThresholdQualifiedRssi5;
+ private final int mRssiScoreSlope;
+ private final int mRssiScoreOffset;
+ private final int mPasspointSecurityAward;
+ private final int mSecurityAward;
+ private final int mBand5GHzAward;
+ private final int mThresholdSaturatedRssi24;
+ private final SynchronousNetworkRecommendationProvider mNetworkRecommendationProvider;
+
+ public WifiWakeupNetworkSelector(
+ Resources resources,
+ SynchronousNetworkRecommendationProvider networkRecommendationProvider) {
+ mThresholdQualifiedRssi24 =
+ resources.getInteger(R.integer.config_netrec_wifi_score_low_rssi_threshold_24GHz);
+ mThresholdQualifiedRssi5 =
+ resources.getInteger(R.integer.config_netrec_wifi_score_low_rssi_threshold_5GHz);
+ mRssiScoreSlope = resources.getInteger(R.integer.config_netrec_RSSI_SCORE_SLOPE);
+ mRssiScoreOffset = resources.getInteger(R.integer.config_netrec_RSSI_SCORE_OFFSET);
+ mPasspointSecurityAward =
+ resources.getInteger(R.integer.config_netrec_PASSPOINT_SECURITY_AWARD);
+ mSecurityAward = resources.getInteger(R.integer.config_netrec_SECURITY_AWARD);
+ mBand5GHzAward = resources.getInteger(R.integer.config_netrec_5GHz_preference_boost_factor);
+ mThresholdSaturatedRssi24 =
+ resources.getInteger(R.integer.config_netrec_wifi_score_good_rssi_threshold_24GHz);
+ mNetworkRecommendationProvider = networkRecommendationProvider;
+ }
+
+ /** Returns the network that the framework would most likely connect to if Wi-Fi was enabled. */
+ @Nullable
+ public WifiConfiguration selectNetwork(
+ Map<String, WifiConfiguration> savedNetworks, List<ScanResult> scanResults) {
+ Set<WifiConfiguration> openOrExternalConfigs = new ArraySet<>();
+ List<ScanResult> openOrExternalScanResults = new ArrayList<>();
+ WifiConfiguration candidateWifiConfiguration = null;
+ ScanResult candidateScanResult = null;
+ int candidateScore = -1;
+ for (int i = 0; i < scanResults.size(); i++) {
+ ScanResult scanResult = scanResults.get(i);
+ WifiConfiguration wifiConfiguration = savedNetworks.get(scanResult.SSID);
+ if (wifiConfiguration == null) {
+ continue;
+ }
+ if ((ScanResultUtil.is5GHz(scanResult) && scanResult.level < mThresholdQualifiedRssi5)
+ || (ScanResultUtil.is24GHz(scanResult)
+ && scanResult.level < mThresholdQualifiedRssi24)) {
+ continue;
+ }
+ if (!ScanResultUtil.doesScanResultMatchWithNetwork(scanResult, wifiConfiguration)) {
+ continue;
+ }
+ if (WifiConfigurationUtil.isConfigForOpenNetwork(wifiConfiguration)
+ || RoboCompatUtil.getInstance().useExternalScores(wifiConfiguration)) {
+ // All open and externally scored networks should defer to network recommendations.
+ openOrExternalConfigs.add(wifiConfiguration);
+ openOrExternalScanResults.add(scanResult);
+ continue;
+ }
+ int score = calculateScore(scanResult, wifiConfiguration);
+ if (candidateScanResult == null
+ || calculateScore(scanResult, wifiConfiguration) > candidateScore) {
+ candidateScanResult = scanResult;
+ candidateWifiConfiguration = wifiConfiguration;
+ candidateScore = score;
+ }
+ }
+ if (candidateWifiConfiguration == null && !openOrExternalConfigs.isEmpty()) {
+ // TODO(netrec): Add connectableConfigs after next SystemApi drop
+ RecommendationRequest request =
+ new RecommendationRequest.Builder()
+ .setScanResults(openOrExternalScanResults.toArray(new ScanResult[0]))
+ .build();
+ RecommendationResult result =
+ mNetworkRecommendationProvider.requestRecommendation(request);
+ return result.getWifiConfiguration();
+ }
+ return candidateWifiConfiguration;
+ }
+
+ private int calculateScore(ScanResult scanResult, WifiConfiguration wifiConfiguration) {
+ int score = 0;
+ // Calculate the RSSI score.
+ int rssi =
+ scanResult.level <= mThresholdSaturatedRssi24
+ ? scanResult.level
+ : mThresholdSaturatedRssi24;
+ score += (rssi + mRssiScoreOffset) * mRssiScoreSlope;
+
+ // 5GHz band bonus.
+ if (ScanResultUtil.is5GHz(scanResult)) {
+ score += mBand5GHzAward;
+ }
+
+ // Security award.
+ if (RoboCompatUtil.getInstance().isPasspoint(wifiConfiguration)) {
+ score += mPasspointSecurityAward;
+ } else if (!WifiConfigurationUtil.isConfigForOpenNetwork(wifiConfiguration)) {
+ score += mSecurityAward;
+ }
+
+ return score;
+ }
+}
diff --git a/tests/Android.mk b/tests/Android.mk
deleted file mode 100644
index b5759f3..0000000
--- a/tests/Android.mk
+++ /dev/null
@@ -1,19 +0,0 @@
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
- android-support-test \
- guava \
- jsr305 \
- mockito-target
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := NetworkRecommendationTests
-LOCAL_CERTIFICATE := platform
-
-LOCAL_INSTRUMENTATION_FOR := NetworkRecommendation
-
-include $(BUILD_PACKAGE)
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
deleted file mode 100644
index 3ebaaf3..0000000
--- a/tests/AndroidManifest.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.networkrecommendation.tests">
-
- <uses-permission android:name="android.permission.BIND_NETWORK_RECOMMENDATION_SERVICE"/>
- <uses-permission android:name="android.permission.REQUEST_NETWORK_SCORES"/>
-
- <application>
- <uses-library android:name="android.test.runner" />
- </application>
-
- <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.networkrecommendation"
- android:label="Tests for NetworkRecommendation">
- </instrumentation>
-</manifest>
diff --git a/tests/src/com/android/networkrecommendation/DefaultNetworkRecommendationProviderTest.java b/tests/src/com/android/networkrecommendation/DefaultNetworkRecommendationProviderTest.java
deleted file mode 100644
index 5b78310..0000000
--- a/tests/src/com/android/networkrecommendation/DefaultNetworkRecommendationProviderTest.java
+++ /dev/null
@@ -1,376 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.networkrecommendation;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import android.net.NetworkKey;
-import android.net.NetworkRecommendationProvider;
-import android.net.NetworkScoreManager;
-import android.net.RecommendationRequest;
-import android.net.RecommendationResult;
-import android.net.RssiCurve;
-import android.net.ScoredNetwork;
-import android.net.WifiKey;
-import android.net.wifi.ScanResult;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiSsid;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.SystemClock;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
-
-/**
- * Tests the recommendation provider directly, to test internals of the provider rather than the
- * service's API.
- */
-@RunWith(AndroidJUnit4.class)
-public class DefaultNetworkRecommendationProviderTest {
-
- private static final String GOOD_METERED_NETWORK_STRING_UNQUOTED = "Metered";
- private static final String GOOD_METERED_NETWORK_STRING = "\"Metered\",aa:bb:cc:dd:ee:ff" +
- "|10,-128,-128,-128,-128,-128,-128,-128,-128,20,20,20,20,-128|1|0|4K";
- private static final RssiCurve GOOD_METERED_NETWORK_CURVE = new RssiCurve(
- DefaultNetworkRecommendationProvider.CONSTANT_CURVE_START, 10 /* bucketWidth */,
- new byte[]{-128, -128, -128, -128, -128, -128, -128, -128, 20, 20, 20, 20, -128},
- 0 /* defaultActiveNetworkBoost */);
- private static final ScoredNetwork GOOD_METERED_NETWORK = new ScoredNetwork(
- new NetworkKey(new WifiKey("\"Metered\"", "aa:bb:cc:dd:ee:ff")),
- GOOD_METERED_NETWORK_CURVE, true /* meteredHint */, new Bundle());
-
- private static final String GOOD_CAPTIVE_NETWORK_STRING_UNQUOTED = "Captive";
- private static final String GOOD_CAPTIVE_NETWORK_STRING =
- "\"Captive\",ff:ee:dd:cc:bb:aa"
- + "|18,-128,-128,-128,-128,-128,-128,21,21,21,-128|0|1|HD";
- private static final RssiCurve GOOD_CAPTIVE_NETWORK_CURVE = new RssiCurve(
- DefaultNetworkRecommendationProvider.CONSTANT_CURVE_START, 18 /* bucketWidth */,
- new byte[]{-128, -128, -128, -128, -128, -128, 21, 21, 21, -128},
- 0 /* defaultActiveNetworkBoost */);
- private static final ScoredNetwork GOOD_CAPTIVE_NETWORK;
- static {
- Bundle attributes = new Bundle();
- attributes.putBoolean(ScoredNetwork.ATTRIBUTES_KEY_HAS_CAPTIVE_PORTAL, true);
- GOOD_CAPTIVE_NETWORK = new ScoredNetwork(
- new NetworkKey(new WifiKey("\"Captive\"", "ff:ee:dd:cc:bb:aa")),
- GOOD_CAPTIVE_NETWORK_CURVE, false /* meteredHint */, attributes);
- }
-
- private static final String ANY_NETWORK_STRING_UNQUOTED = "AnySsid";
- private static final String ANY_NETWORK_STRING =
- "\"AnySsid\",00:00:00:00:00:00"
- + "|18,-128,-128,-128,-128,-128,-128,22,22,22,-128|0|0|NONE";
- private static final RssiCurve ANY_NETWORK_CURVE = new RssiCurve(
- DefaultNetworkRecommendationProvider.CONSTANT_CURVE_START, 18 /* bucketWidth */,
- new byte[]{-128, -128, -128, -128, -128, -128, 22, 22, 22, -128},
- 0 /* defaultActiveNetworkBoost */);
- private static final ScoredNetwork ANY_NETWORK = new ScoredNetwork(
- new NetworkKey(new WifiKey("\"AnySsid\"", "ee:ee:ee:ee:ee:ee")),
- ANY_NETWORK_CURVE, false /* meteredHint */, new Bundle());
-
- private static final String ANY_NETWORK_SPECIFIC_STRING_UNQUOTED = "AnySsid";
- private static final String ANY_NETWORK_SPECIFIC_STRING =
- "\"AnySsid\",ee:ee:ee:ee:ee:ee"
- + "|18,-128,-128,-128,-128,-128,-128,23,23,23,-128|0|0|NONE";
- private static final RssiCurve ANY_NETWORK_SPECIFIC_CURVE = new RssiCurve(
- DefaultNetworkRecommendationProvider.CONSTANT_CURVE_START, 18 /* bucketWidth */,
- new byte[]{-128, -128, -128, -128, -128, -128, 23, 23, 23, -128},
- 0 /* defaultActiveNetworkBoost */);
- private static final ScoredNetwork ANY_NETWORK_SPECIFIC = new ScoredNetwork(
- new NetworkKey(new WifiKey("\"AnySsid\"", "ee:ee:ee:ee:ee:ee")),
- ANY_NETWORK_SPECIFIC_CURVE, false /* meteredHint */, new Bundle());
-
- private static final String BAD_NETWORK_STRING_UNQUOTED = "Bad";
- private static final String BAD_NETWORK_STRING =
- "\"Bad\",aa:bb:cc:dd:ee:ff"
- + "|10,-128,-128,-128,-128,-128,-128,-128,-128,-128,-128,-128,-128,-128"
- + "|1|0|SD";
- private static final RssiCurve BAD_NETWORK_CURVE =
- new RssiCurve(
- DefaultNetworkRecommendationProvider.CONSTANT_CURVE_START,
- 10 /* bucketWidth */,
- new byte[] {-128, -128, -128, -128, -128, -128,
- -128, -128, -128, -128, -128, -128, -128},
- 0 /* defaultActiveNetworkBoost */);
- private static final ScoredNetwork BAD_NETWORK =
- new ScoredNetwork(
- new NetworkKey(new WifiKey("\"Bad\"", "aa:bb:cc:dd:ee:ff")),
- BAD_NETWORK_CURVE,
- true /* meteredHint */,
- new Bundle());
-
- @Mock
- private NetworkRecommendationProvider.ResultCallback mCallback;
-
- @Mock
- private NetworkScoreManager mNetworkScoreManager;
-
- private DefaultNetworkRecommendationProvider.ScoreStorage mStorage;
- private DefaultNetworkRecommendationProvider mProvider;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mStorage = new DefaultNetworkRecommendationProvider.ScoreStorage();
- mProvider = new DefaultNetworkRecommendationProvider(
- new Handler(Looper.getMainLooper()), mNetworkScoreManager, mStorage);
- }
-
- @Test
- public void basicRecommendation() throws Exception {
-
- ScanResult[] scanResults = new ScanResult[6];
- for (int i = 0; i < 3; i++) {
- scanResults[i] = TestUtil.createMockScanResult(i);
- }
-
- // For now we add directly to storage, but when we start calling
- // networkScoreManager.updateScores, we'll have to adjust this test.
- mProvider.addScoreForTest(GOOD_METERED_NETWORK);
- {
- ScanResult scanResult = new ScanResult();
- scanResult.level = 115;
- scanResult.SSID = GOOD_METERED_NETWORK_STRING_UNQUOTED;
- scanResult.wifiSsid = WifiSsid.createFromAsciiEncoded(
- GOOD_METERED_NETWORK_STRING_UNQUOTED);
- scanResult.BSSID = GOOD_METERED_NETWORK.networkKey.wifiKey.bssid;
- scanResult.capabilities = "[ESS]";
- scanResult.timestamp = SystemClock.elapsedRealtime() * 1000;
- scanResults[3] = scanResult;
- }
-
- for (int i = 4; i < 6; i++) {
- scanResults[i] = TestUtil.createMockScanResult(i);
- }
-
- RecommendationRequest request = new RecommendationRequest.Builder()
- .setScanResults(scanResults)
- .build();
-
- RecommendationResult result = verifyAndCaptureResult(request);
- assertEquals(GOOD_METERED_NETWORK.networkKey.wifiKey.ssid,
- result.getWifiConfiguration().SSID);
- }
-
- @Test
- public void recommendation_noScans_returnsCurrentConfig() throws Exception {
- ScanResult[] scanResults = new ScanResult[0];
-
- WifiConfiguration expectedConfig = new WifiConfiguration();
- expectedConfig.SSID = "ssid";
- expectedConfig.BSSID = "bssid";
- RecommendationRequest request = new RecommendationRequest.Builder()
- .setScanResults(scanResults)
- .setDefaultWifiConfig(expectedConfig)
- .build();
-
- RecommendationResult result = verifyAndCaptureResult(request);
- assertEquals(expectedConfig, result.getWifiConfiguration());
- }
-
- @Test
- public void recommendation_noScans_noCurrentConfig_returnsEmpty() throws Exception {
- ScanResult[] scanResults = new ScanResult[0];
-
- RecommendationRequest request = new RecommendationRequest.Builder()
- .setScanResults(scanResults)
- .build();
-
- RecommendationResult result = verifyAndCaptureResult(request);
- assertNull(result.getWifiConfiguration());
- }
-
- @Test
- public void scoreNetworks() throws Exception {
- NetworkKey[] keys =
- new NetworkKey[]{GOOD_METERED_NETWORK.networkKey, GOOD_CAPTIVE_NETWORK.networkKey};
- mProvider.onRequestScores(keys);
-
- verify(mNetworkScoreManager).updateScores(Mockito.any());
- }
-
- @Test
- public void scoreNetworks_empty() throws Exception {
- NetworkKey[] keys = new NetworkKey[]{};
- mProvider.onRequestScores(keys);
-
- verify(mNetworkScoreManager, times(0)).updateScores(Mockito.any());
- }
-
- @Test
- public void dumpAddScores_goodMetered() {
- String[] args = {"netrec", "addScore", GOOD_METERED_NETWORK_STRING};
- mProvider.dump(null /* fd */, new PrintWriter(new StringWriter()), args);
-
- ScoredNetwork[] scoredNetworks = verifyAndCaptureScoredNetworks();
- assertEquals(1, scoredNetworks.length);
- ScoredNetwork score = scoredNetworks[0];
-
- assertEquals(GOOD_METERED_NETWORK.networkKey.wifiKey.ssid, score.networkKey.wifiKey.ssid);
- assertEquals(GOOD_METERED_NETWORK.networkKey.wifiKey.bssid, score.networkKey.wifiKey.bssid);
-
- assertEquals(GOOD_METERED_NETWORK.meteredHint, score.meteredHint);
- assertEquals(
- GOOD_METERED_NETWORK.attributes.getBoolean(
- ScoredNetwork.ATTRIBUTES_KEY_HAS_CAPTIVE_PORTAL),
- score.attributes.getBoolean(ScoredNetwork.ATTRIBUTES_KEY_HAS_CAPTIVE_PORTAL));
-
- assertEquals("Network curve does not match", GOOD_METERED_NETWORK_CURVE, score.rssiCurve);
- assertEquals(
- "Badge curve does not match",
- DefaultNetworkRecommendationProvider.BADGE_CURVE_4K,
- (RssiCurve) score.attributes.getParcelable(
- ScoredNetwork.ATTRIBUTES_KEY_BADGING_CURVE));
- }
-
- @Test
- public void dumpAddScores_bad() {
- String[] args = {"netrec", "addScore", BAD_NETWORK_STRING};
- mProvider.dump(null /* fd */, new PrintWriter(new StringWriter()), args);
-
- ScoredNetwork[] scoredNetworks = verifyAndCaptureScoredNetworks();
- assertEquals(1, scoredNetworks.length);
- ScoredNetwork score = scoredNetworks[0];
-
- assertEquals(BAD_NETWORK.networkKey.wifiKey.ssid, score.networkKey.wifiKey.ssid);
- assertEquals(BAD_NETWORK.networkKey.wifiKey.bssid, score.networkKey.wifiKey.bssid);
-
- assertEquals(BAD_NETWORK.meteredHint, score.meteredHint);
- assertEquals(
- BAD_NETWORK.attributes.getBoolean(ScoredNetwork.ATTRIBUTES_KEY_HAS_CAPTIVE_PORTAL),
- score.attributes.getBoolean(ScoredNetwork.ATTRIBUTES_KEY_HAS_CAPTIVE_PORTAL));
-
- assertEquals("Network curve does not match", BAD_NETWORK_CURVE, score.rssiCurve);
- assertEquals(
- "Badge curve does not match",
- DefaultNetworkRecommendationProvider.BADGE_CURVE_SD,
- (RssiCurve) score.attributes.getParcelable(
- ScoredNetwork.ATTRIBUTES_KEY_BADGING_CURVE));
- }
-
- @Test
- public void dumpAddScores_goodCaptivePortal() {
- String[] args = {"addScore", GOOD_CAPTIVE_NETWORK_STRING};
- mProvider.dump(null /* fd */, new PrintWriter(new StringWriter()), args);
-
- ScoredNetwork[] scoredNetworks = verifyAndCaptureScoredNetworks();
- assertEquals(1, scoredNetworks.length);
- ScoredNetwork score = scoredNetworks[0];
-
- assertEquals(GOOD_CAPTIVE_NETWORK.networkKey.wifiKey.ssid, score.networkKey.wifiKey.ssid);
- assertEquals(GOOD_CAPTIVE_NETWORK.networkKey.wifiKey.bssid, score.networkKey.wifiKey.bssid);
-
- assertEquals(GOOD_CAPTIVE_NETWORK.meteredHint, score.meteredHint);
-
- assertEquals(
- GOOD_CAPTIVE_NETWORK.attributes.getBoolean(
- ScoredNetwork.ATTRIBUTES_KEY_HAS_CAPTIVE_PORTAL),
- score.attributes.getBoolean(ScoredNetwork.ATTRIBUTES_KEY_HAS_CAPTIVE_PORTAL));
- assertEquals("Network curve does not match.", GOOD_CAPTIVE_NETWORK_CURVE, score.rssiCurve);
- assertEquals(
- "Badge curve does not match",
- DefaultNetworkRecommendationProvider.BADGE_CURVE_HD,
- (RssiCurve) score.attributes.getParcelable(
- ScoredNetwork.ATTRIBUTES_KEY_BADGING_CURVE));
- }
-
- @Test
- public void dumpAddScores_anySsid() {
- String[] args = {"addScore", ANY_NETWORK_STRING};
- mProvider.dump(null /* fd */, new PrintWriter(new StringWriter()), args);
-
- // We don't update the platform with the any bssid score, but we do store it.
- verify(mNetworkScoreManager, times(0)).updateScores(Mockito.any());
-
- // We do store and serve the score, though:
- ScoredNetwork score = mStorage.get(ANY_NETWORK.networkKey);
- assertNotNull(score);
-
- assertEquals(ANY_NETWORK.networkKey, score.networkKey);
- assertEquals(ANY_NETWORK.meteredHint, score.meteredHint);
- assertEquals(
- ANY_NETWORK.attributes.getBoolean(
- ScoredNetwork.ATTRIBUTES_KEY_HAS_CAPTIVE_PORTAL),
- score.attributes.getBoolean(ScoredNetwork.ATTRIBUTES_KEY_HAS_CAPTIVE_PORTAL));
- assertEquals("Network curve does not match", ANY_NETWORK_CURVE, score.rssiCurve);
- assertNull(
- "Badge curve should not be set.",
- (RssiCurve) score.attributes.getParcelable(
- ScoredNetwork.ATTRIBUTES_KEY_BADGING_CURVE));
- }
-
- @Test
- public void dumpAddScores_anySsid_useMoreSpecific() {
- mProvider.dump(null /* fd */, new PrintWriter(new StringWriter()),
- new String[] {"addScore", ANY_NETWORK_STRING});
- verify(mNetworkScoreManager, times(0)).updateScores(Mockito.any());
-
- mProvider.dump(null /* fd */, new PrintWriter(new StringWriter()),
- new String[] {"addScore", ANY_NETWORK_SPECIFIC_STRING});
- verify(mNetworkScoreManager).updateScores(Mockito.any());
-
- // We don't update the platform with the any bssid score, but we do store it.
- ScoredNetwork score = mStorage.get(ANY_NETWORK.networkKey);
- assertNotNull(score);
-
- assertEquals(ANY_NETWORK_SPECIFIC.networkKey, score.networkKey);
- assertEquals(ANY_NETWORK_SPECIFIC.meteredHint, score.meteredHint);
- assertEquals(
- ANY_NETWORK_SPECIFIC.attributes.getBoolean(
- ScoredNetwork.ATTRIBUTES_KEY_HAS_CAPTIVE_PORTAL),
- score.attributes.getBoolean(ScoredNetwork.ATTRIBUTES_KEY_HAS_CAPTIVE_PORTAL));
- assertEquals("Network curve does not match", ANY_NETWORK_SPECIFIC_CURVE, score.rssiCurve);
- assertNull(
- "Badge curve should not be set.",
- (RssiCurve) score.attributes.getParcelable(
- ScoredNetwork.ATTRIBUTES_KEY_BADGING_CURVE));
-
- }
-
- private RecommendationResult verifyAndCaptureResult(
- RecommendationRequest request) {
- mProvider.onRequestRecommendation(request, mCallback);
-
- ArgumentCaptor<RecommendationResult> resultCaptor =
- ArgumentCaptor.forClass(RecommendationResult.class);
- verify(mCallback).onResult(resultCaptor.capture());
-
- return resultCaptor.getValue();
- }
-
- private ScoredNetwork[] verifyAndCaptureScoredNetworks() {
- ArgumentCaptor<ScoredNetwork[]> resultCaptor = ArgumentCaptor.forClass(
- ScoredNetwork[].class);
- verify(mNetworkScoreManager).updateScores(resultCaptor.capture());
- return resultCaptor.getValue();
- }
-}
diff --git a/tests/src/com/android/networkrecommendation/DefaultNetworkRecommendationServiceTest.java b/tests/src/com/android/networkrecommendation/DefaultNetworkRecommendationServiceTest.java
deleted file mode 100644
index 75aff2b..0000000
--- a/tests/src/com/android/networkrecommendation/DefaultNetworkRecommendationServiceTest.java
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.networkrecommendation;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-import android.content.Intent;
-import android.net.INetworkRecommendationProvider;
-import android.net.NetworkKey;
-import android.net.NetworkRecommendationProvider;
-import android.net.NetworkScoreManager;
-import android.net.RecommendationRequest;
-import android.net.RecommendationResult;
-import android.net.WifiKey;
-import android.net.wifi.ScanResult;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.IRemoteCallback;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.rule.ServiceTestRule;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-@RunWith(AndroidJUnit4.class)
-public class DefaultNetworkRecommendationServiceTest {
-
- private static final int RESULT_LATCH_TIMEOUT_MILLIS = 2000;
- private static final int WAIT_FOR_BIND_TIMEOUT_MILLIS = 2000;
- private static final int SEQUENCE_ID = 11;
-
- @Rule
- public final ServiceTestRule mServiceRule = new ServiceTestRule();
-
- private INetworkRecommendationProvider bind() throws TimeoutException {
- Intent bindIntent = new Intent(InstrumentationRegistry.getTargetContext(),
- DefaultNetworkRecommendationService.class);
- bindIntent.setAction(NetworkScoreManager.ACTION_RECOMMEND_NETWORKS);
-
- // https://code.google.com/p/android/issues/detail?id=200071
- // bindService can occasionally returns null.
- IBinder binder = null;
- boolean interrupted = false;
- try {
- long startTime = SystemClock.elapsedRealtime();
- long currentTime = startTime;
- while (currentTime < startTime + WAIT_FOR_BIND_TIMEOUT_MILLIS) {
- binder = mServiceRule.bindService(bindIntent);
- if (binder != null) {
- return INetworkRecommendationProvider.Stub.asInterface(binder);
- }
- try {
- Thread.sleep(100);
- } catch (InterruptedException e) {
- interrupted = true;
- currentTime = SystemClock.elapsedRealtime();
- }
- }
- throw new TimeoutException("Unable to bind to service.");
- } finally {
- if (interrupted) {
- Thread.currentThread().interrupt();
- }
- }
- }
-
- /**
- * Assert that when we make a request we get a response with the proper sequence.
- * <p />
- * We do not assert the config given to us, because we're simply verifying the behavior of
- * this service interacting with the provider. For recommendation tests, see
- * {@link DefaultNetworkRecommendationProviderTest}.
- */
- @Test
- public void requestRecommendation() throws Exception {
- INetworkRecommendationProvider service = bind();
-
- ScanResult[] scanResults = new ScanResult[5];
- for (int i = 0; i < 5; i++) {
- scanResults[i] = TestUtil.createMockScanResult(i);
- }
-
- RecommendationRequest request = new RecommendationRequest.Builder()
- .setScanResults(scanResults)
- .build();
-
- Result result = requestRecommendation(service, request, SEQUENCE_ID);
- synchronized (result) {
- assertEquals(result.sequence, SEQUENCE_ID);
- }
- }
-
- @Test
- public void scoreNetworks() throws Exception {
- INetworkRecommendationProvider service = bind();
- service.requestScores(new NetworkKey[]{new NetworkKey(new WifiKey("\"ProperlyQuoted\"",
- "aa:bb:cc:dd:ee:ff"))});
- }
-
- @Test
- public void scoreNetworks_empty() throws Exception {
- INetworkRecommendationProvider service = bind();
- service.requestScores(new NetworkKey[]{});
- }
-
- @Test
- public void scoreNetworks_invalid() throws Exception {
- INetworkRecommendationProvider service = bind();
- try {
- service.requestScores(new NetworkKey[]{
- new NetworkKey(new WifiKey("ImproperlyQuoted", "aa:bb:cc:dd:ee:ff"))});
- fail("An invalid SSID should throw an exception.");
- } catch (IllegalArgumentException e) {
- // Expected.
- }
- }
-
- /**
- * Make a network recommendation request. Be sure to synchronize on the result to access its
- * values properly.
- */
- private Result requestRecommendation(INetworkRecommendationProvider service,
- RecommendationRequest request, int seqId) throws RemoteException, InterruptedException {
- final Result result = new Result();
- final CountDownLatch latch = new CountDownLatch(1);
- IRemoteCallback callback = new IRemoteCallback.Stub() {
- @Override
- public void sendResult(Bundle data) throws RemoteException {
- synchronized (result) {
- result.sequence = data.getInt(NetworkRecommendationProvider.EXTRA_SEQUENCE);
- result.recommendation = data.getParcelable(
- NetworkRecommendationProvider.EXTRA_RECOMMENDATION_RESULT);
- latch.countDown();
- }
- }
- };
-
- service.requestRecommendation(request, callback, seqId);
- latch.await(RESULT_LATCH_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
- return result;
- }
-
- private static class Result {
- int sequence;
- RecommendationResult recommendation;
- }
-}
diff --git a/tests/src/com/android/networkrecommendation/TestUtil.java b/tests/src/com/android/networkrecommendation/TestUtil.java
deleted file mode 100644
index 77d7aa7..0000000
--- a/tests/src/com/android/networkrecommendation/TestUtil.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.networkrecommendation;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.net.NetworkInfo;
-import android.net.wifi.ScanResult;
-import android.net.wifi.WifiManager;
-import android.net.wifi.WifiSsid;
-import android.os.SystemClock;
-
-/**
- * Utils for wifi tests.
- */
-public class TestUtil {
-
- /** Create a scan result with some basic properties. */
- public static ScanResult createMockScanResult(int i) {
- ScanResult scanResult = new ScanResult();
- scanResult.level = i;
- scanResult.SSID = "ssid-" + i;
- scanResult.wifiSsid = WifiSsid.createFromAsciiEncoded("ssid-" + i);
- scanResult.BSSID = "aa:bb:cc:dd:ee:0" + i;
- scanResult.capabilities = "[ESS]";
- scanResult.timestamp = SystemClock.elapsedRealtime() * 1000;
- return scanResult;
- }
-
- /** Send {@link WifiManager#NETWORK_STATE_CHANGED_ACTION} broadcast. */
- public static void sendNetworkStateChanged(BroadcastReceiver broadcastReceiver,
- Context context, NetworkInfo.DetailedState detailedState) {
- Intent intent = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION);
- NetworkInfo networkInfo = new NetworkInfo(0, 0, "", "");
- networkInfo.setDetailedState(detailedState, "", "");
- intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, networkInfo);
- broadcastReceiver.onReceive(context, intent);
- }
-
- /** Send {@link WifiManager#WIFI_AP_STATE_CHANGED_ACTION} broadcast. */
- public static void sendWifiApStateChanged(BroadcastReceiver broadcastReceiver,
- Context context) {
- Intent intent = new Intent(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
- broadcastReceiver.onReceive(context, intent);
- }
-
- /** Send {@link WifiManager#SCAN_RESULTS_AVAILABLE_ACTION} broadcast. */
- public static void sendScanResultsAvailable(BroadcastReceiver broadcastReceiver,
- Context context) {
- Intent intent = new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
- broadcastReceiver.onReceive(context, intent);
- }
-
- /** Send {@link WifiManager#CONFIGURED_NETWORKS_CHANGED_ACTION} broadcast. */
- public static void sendConfiguredNetworksChanged(BroadcastReceiver broadcastReceiver,
- Context context) {
- Intent intent = new Intent(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
- broadcastReceiver.onReceive(context, intent);
- }
-
- /** Send {@link WifiManager#WIFI_STATE_CHANGED_ACTION} broadcast. */
- public static void sendWifiStateChanged(BroadcastReceiver broadcastReceiver, Context context) {
- Intent intent = new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION);
- broadcastReceiver.onReceive(context, intent);
- }
-}
diff --git a/tests/src/com/android/networkrecommendation/WifiNotificationControllerTest.java b/tests/src/com/android/networkrecommendation/WifiNotificationControllerTest.java
deleted file mode 100644
index d902e68..0000000
--- a/tests/src/com/android/networkrecommendation/WifiNotificationControllerTest.java
+++ /dev/null
@@ -1,266 +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;
-
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.graphics.Bitmap;
-import android.net.NetworkInfo;
-import android.net.RecommendationRequest;
-import android.net.RecommendationResult;
-import android.net.wifi.ScanResult;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiManager;
-import android.net.wifi.WifiManager.ActionListener;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Unit tests for {@link com.android.networkrecommendation.WifiNotificationController}.
- */
-@RunWith(AndroidJUnit4.class)
-public class WifiNotificationControllerTest {
- @Mock private Context mContext;
- @Mock private WifiManager mWifiManager;
- @Mock private NotificationManager mNotificationManager;
- @Mock private WifiNotificationHelper mWifiNotificationHelper;
- @Mock private SynchronousNetworkRecommendationProvider mNetworkRecommendationProvider;
- private ContentResolver mContentResolver;
- private Handler mHandler;
- private WifiNotificationController mWifiNotificationController;
- private int mNotificationsEnabledOriginalValue;
-
- /**
- * Internal BroadcastReceiver that WifiNotificationController uses to listen for broadcasts
- * this is initialized by calling startServiceAndLoadDriver
- */
- private BroadcastReceiver mBroadcastReceiver;
-
- /** Initialize objects before each test run. */
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
-
- // Needed for the NotificationEnabledSettingObserver.
- mContentResolver = InstrumentationRegistry.getTargetContext().getContentResolver();
- mNotificationsEnabledOriginalValue =
- Settings.Global.getInt(mContentResolver,
- Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0);
- Settings.Global.putInt(mContentResolver,
- Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 1);
- mHandler = new Handler(Looper.getMainLooper());
-
- mWifiNotificationController = new WifiNotificationController(
- mContext, mContentResolver, mHandler, mNetworkRecommendationProvider,
- mWifiManager, mNotificationManager, mWifiNotificationHelper);
- mWifiNotificationController.start();
-
- ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
- ArgumentCaptor.forClass(BroadcastReceiver.class);
- verify(mContext)
- .registerReceiver(broadcastReceiverCaptor.capture(), any(IntentFilter.class),
- anyString(), any(Handler.class));
- mBroadcastReceiver = broadcastReceiverCaptor.getValue();
- }
-
- @After
- public void tearDown() throws Exception {
- Settings.Global.putInt(mContentResolver,
- Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
- mNotificationsEnabledOriginalValue);
- }
-
- private void setOpenAccessPoints(int numAccessPoints) {
- List<ScanResult> scanResults = new ArrayList<>();
- for (int i = 0; i < numAccessPoints; i++) {
- ScanResult scanResult = createScanResult("testSSID" + i, "00:00:00:00:00:00");
- scanResults.add(scanResult);
- }
- when(mWifiManager.getScanResults()).thenReturn(scanResults);
- }
-
- private ScanResult createScanResult(String ssid, String bssid) {
- ScanResult scanResult = new ScanResult();
- scanResult.capabilities = "[ESS]";
- scanResult.SSID = ssid;
- scanResult.BSSID = bssid;
- return scanResult;
- }
-
- private WifiConfiguration createFakeConfig() {
- WifiConfiguration config = new WifiConfiguration();
- config.SSID = "\"TestSsid\"";
- config.BSSID = "00:00:00:00:00:00";
- config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
- return config;
- }
-
- private void createFakeBitmap() {
- when(mWifiNotificationHelper.createNotificationBadgeBitmap(any(), any()))
- .thenReturn(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888));
- }
-
- /** Verifies that a notification is displayed (and retracted) given system events. */
- @Test
- public void verifyNotificationDisplayedWhenNetworkRecommended() throws Exception {
- when(mWifiManager.getWifiState()).thenReturn(WifiManager.WIFI_STATE_ENABLED);
-
- TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext);
- TestUtil.sendNetworkStateChanged(mBroadcastReceiver, mContext,
- NetworkInfo.DetailedState.DISCONNECTED);
- setOpenAccessPoints(3);
- createFakeBitmap();
-
- when(mNetworkRecommendationProvider.requestRecommendation(any(RecommendationRequest.class)))
- .thenReturn(
- RecommendationResult.createConnectRecommendation(createFakeConfig()));
-
- // The notification should not be displayed after only two scan results.
- TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
- TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
- verify(mNotificationManager, never())
- .notify(any(String.class), anyInt(), any(Notification.class));
-
- // Changing to and from "SCANNING" state should not affect the counter.
- TestUtil.sendNetworkStateChanged(mBroadcastReceiver, mContext,
- NetworkInfo.DetailedState.SCANNING);
- TestUtil.sendNetworkStateChanged(mBroadcastReceiver, mContext,
- NetworkInfo.DetailedState.DISCONNECTED);
- verify(mNotificationManager, never())
- .notify(any(String.class), anyInt(), any(Notification.class));
- // The third scan result notification will trigger the notification.
- TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
- verify(mWifiNotificationHelper).createMainNotification(any(WifiConfiguration.class),
- any(Bitmap.class));
- verify(mNotificationManager)
- .notify(any(String.class), anyInt(), any(Notification.class));
- verify(mNotificationManager, never())
- .cancelAsUser(any(String.class), anyInt(), any(UserHandle.class));
- }
-
- /** Verifies that a notification is not displayed for bad networks. */
- @Test
- public void verifyNotificationNotDisplayedWhenNoNetworkRecommended() throws Exception {
- when(mWifiManager.getWifiState()).thenReturn(WifiManager.WIFI_STATE_ENABLED);
-
- TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext);
- TestUtil.sendNetworkStateChanged(mBroadcastReceiver, mContext,
- NetworkInfo.DetailedState.DISCONNECTED);
- setOpenAccessPoints(3);
- createFakeBitmap();
-
- // Recommendation result with no WifiConfiguration returned.
- when(mNetworkRecommendationProvider.requestRecommendation(any(RecommendationRequest.class)))
- .thenReturn(RecommendationResult.createDoNotConnectRecommendation());
-
- TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
- TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
- TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
- TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
- verify(mNotificationManager, never())
- .notify(any(String.class), anyInt(), any(Notification.class));
-
- // DoNotConnect Recommendation result.
- when(mNetworkRecommendationProvider.requestRecommendation(any(RecommendationRequest.class)))
- .thenReturn(RecommendationResult.createDoNotConnectRecommendation());
- TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
- verify(mNotificationManager, never())
- .notify(any(String.class), anyInt(), any(Notification.class));
- }
-
- /**
- * Verifies the notifications flow (Connect -> connecting -> connected) when user clicks
- * on Connect button.
- */
- @Test
- public void verifyNotificationsFlowOnConnectToNetwork() {
- when(mWifiManager.getWifiState()).thenReturn(WifiManager.WIFI_STATE_ENABLED);
-
- TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext);
- TestUtil.sendNetworkStateChanged(mBroadcastReceiver, mContext,
- NetworkInfo.DetailedState.DISCONNECTED);
- setOpenAccessPoints(3);
- createFakeBitmap();
- when(mNetworkRecommendationProvider.requestRecommendation(any(RecommendationRequest.class)))
- .thenReturn(
- RecommendationResult.createConnectRecommendation(createFakeConfig()));
-
- TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
- TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
- TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
- verify(mWifiNotificationHelper).createMainNotification(any(WifiConfiguration.class),
- any(Bitmap.class));
- verify(mNotificationManager)
- .notify(any(String.class), anyInt(), any(Notification.class));
-
- // Send connect intent, should attempt to connect to Wi-Fi
- Intent intent = new Intent(
- WifiNotificationController.ACTION_CONNECT_TO_RECOMMENDED_NETWORK);
- mBroadcastReceiver.onReceive(mContext, intent);
- verify(mWifiManager).connect(any(WifiConfiguration.class), any(ActionListener.class));
- verify(mWifiNotificationHelper).createConnectingNotification(any(WifiConfiguration.class),
- any(Bitmap.class));
-
- // Show connecting notification.
- verify(mNotificationManager, times(2))
- .notify(any(String.class), anyInt(), any(Notification.class));
- // Verify callback to dismiss connecting notification exists.
- assertTrue(mHandler.hasCallbacks(
- mWifiNotificationController.mShowFailedToConnectNotificationRunnable));
-
- // Verify show connected notification.
- TestUtil.sendNetworkStateChanged(mBroadcastReceiver, mContext,
- NetworkInfo.DetailedState.CONNECTED);
- verify(mWifiNotificationHelper).createConnectedNotification(any(WifiConfiguration.class),
- any(Bitmap.class));
- verify(mNotificationManager, times(3))
- .notify(any(String.class), anyInt(), any(Notification.class));
-
- // Verify callback to dismiss connected notification exists.
- assertTrue(mHandler.hasCallbacks(
- mWifiNotificationController.mDismissNotificationRunnable));
- }
-}
diff --git a/tests/src/com/android/networkrecommendation/WifiWakeupControllerTest.java b/tests/src/com/android/networkrecommendation/WifiWakeupControllerTest.java
deleted file mode 100644
index 3869a71..0000000
--- a/tests/src/com/android/networkrecommendation/WifiWakeupControllerTest.java
+++ /dev/null
@@ -1,438 +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;
-
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyList;
-import static org.mockito.Matchers.anyMap;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
-
-import android.app.NotificationManager;
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.IntentFilter;
-import android.net.wifi.ScanResult;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiManager;
-import android.net.wifi.WifiSsid;
-import android.os.Handler;
-import android.os.Looper;
-import android.provider.Settings;
-import android.support.test.runner.AndroidJUnit4;
-
-import com.google.android.collect.Lists;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-
-/**
- * Unit tests for {@link WifiWakeupController}.
- */
-@RunWith(AndroidJUnit4.class)
-public class WifiWakeupControllerTest {
- private static final ScanResult OPEN_SCAN_RESULT = buildScanResult("ssid");
- private static final ScanResult SAVED_SCAN_RESULT = buildScanResult("ssid1");
- private static final ScanResult SAVED_SCAN_RESULT2 = buildScanResult("ssid2");
- private static final ScanResult SAVED_SCAN_RESULT_EXTERNAL = buildScanResult("ssid3");
-
- private static final WifiConfiguration SAVED_WIFI_CONFIGURATION = new WifiConfiguration();
- private static final WifiConfiguration SAVED_WIFI_CONFIGURATION2 = new WifiConfiguration();
- private static final WifiConfiguration SAVED_WIFI_CONFIGURATION_EXTERNAL =
- new WifiConfiguration();
-
- static {
- SAVED_WIFI_CONFIGURATION.SSID = "\"" + SAVED_SCAN_RESULT.SSID + "\"";
- SAVED_WIFI_CONFIGURATION.status = WifiConfiguration.Status.CURRENT;
- SAVED_WIFI_CONFIGURATION.validatedInternetAccess = true;
- SAVED_WIFI_CONFIGURATION2.SSID = "\"" + SAVED_SCAN_RESULT.SSID + "\"";
- SAVED_WIFI_CONFIGURATION2.status = WifiConfiguration.Status.ENABLED;
- SAVED_WIFI_CONFIGURATION2.validatedInternetAccess = true;
- SAVED_WIFI_CONFIGURATION_EXTERNAL.SSID = "\"" + SAVED_SCAN_RESULT_EXTERNAL.SSID + "\"";
- SAVED_WIFI_CONFIGURATION_EXTERNAL.useExternalScores = true;
- SAVED_WIFI_CONFIGURATION_EXTERNAL.status = WifiConfiguration.Status.ENABLED;
- SAVED_WIFI_CONFIGURATION_EXTERNAL.validatedInternetAccess = true;
- }
-
- private static ScanResult buildScanResult(String ssid) {
- ScanResult scanResult = new ScanResult(WifiSsid.createFromAsciiEncoded(ssid),
- "00:00:00:00:00:00", 0L, -1, null, "", 0, 0, 0);
- scanResult.informationElements = new ScanResult.InformationElement[0];
- return scanResult;
- }
-
- @Mock private Context mContext;
- @Mock private NotificationManager mNotificationManager;
- @Mock private ContentResolver mContentResolver;
- @Mock private WifiWakeupNetworkSelector mWifiWakeupNetworkSelector;
- @Mock private WifiWakeupNotificationHelper mWifiWakeupNotificationHelper;
- @Mock private WifiManager mWifiManager;
- @Captor private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor;
-
- private WifiWakeupController mWifiWakeupController;
- private BroadcastReceiver mBroadcastReceiver;
-
- private int mWifiWakeupEnabledOriginalValue;
- private int mAirplaneModeOriginalValue;
-
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
-
- mWifiWakeupEnabledOriginalValue =
- Settings.Global.getInt(mContentResolver, Settings.Global.WIFI_WAKEUP_ENABLED, 0);
- mAirplaneModeOriginalValue =
- Settings.Global.getInt(mContentResolver, Settings.Global.AIRPLANE_MODE_ON, 0);
- Settings.Global.putInt(mContentResolver, Settings.Global.WIFI_WAKEUP_ENABLED, 1);
- Settings.Global.putInt(mContentResolver, Settings.Global.AIRPLANE_MODE_ON, 0);
- when(mWifiManager.getWifiApState()).thenReturn(WifiManager.WIFI_AP_STATE_DISABLED);
-
- mWifiWakeupController = new WifiWakeupController(mContext, mContentResolver,
- Looper.getMainLooper(), mWifiManager, mWifiWakeupNetworkSelector,
- mWifiWakeupNotificationHelper);
- mWifiWakeupController.start();
-
- verify(mContext).registerReceiver(mBroadcastReceiverCaptor.capture(),
- any(IntentFilter.class), anyString(), any(Handler.class));
- mBroadcastReceiver = mBroadcastReceiverCaptor.getValue();
- }
-
- @After
- public void tearDown() {
- Settings.Global.putInt(mContentResolver, Settings.Global.WIFI_WAKEUP_ENABLED,
- mWifiWakeupEnabledOriginalValue);
- Settings.Global.putInt(mContentResolver, Settings.Global.AIRPLANE_MODE_ON,
- mAirplaneModeOriginalValue);
- }
-
- /**
- * When the NetworkRecommendationService associated with this WifiWakeupController is unbound,
- * this WifiWakeupController should no longer function.
- */
- @Test
- public void wifiWakeupControllerStopped() {
- mWifiWakeupController.stop();
-
- verify(mContext).unregisterReceiver(mBroadcastReceiver);
- }
-
- /**
- * 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.
- */
- @Test
- public void wifiEnabled_userDisabledWifiNearSavedNetwork_thenLeaves_thenMovesBack() {
- when(mWifiManager.getConfiguredNetworks()).thenReturn(
- Lists.newArrayList(SAVED_WIFI_CONFIGURATION));
- when(mWifiManager.getScanResults()).thenReturn(
- Lists.newArrayList(SAVED_SCAN_RESULT),
- Lists.newArrayList(OPEN_SCAN_RESULT),
- Lists.newArrayList(OPEN_SCAN_RESULT),
- Lists.newArrayList(OPEN_SCAN_RESULT),
- Lists.newArrayList(SAVED_SCAN_RESULT));
- when(mWifiWakeupNetworkSelector.selectNetwork(anyMap(), anyList()))
- .thenReturn(null, SAVED_WIFI_CONFIGURATION);
- when(mWifiManager.getWifiState())
- .thenReturn(WifiManager.WIFI_STATE_ENABLED, WifiManager.WIFI_STATE_DISABLED);
-
- TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext);
- TestUtil.sendConfiguredNetworksChanged(mBroadcastReceiver, mContext);
- TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
- TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext);
- TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
- TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
- TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-
- verify(mWifiManager, never()).setWifiEnabled(true);
-
- TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-
- verify(mWifiManager).setWifiEnabled(true);
- verify(mWifiWakeupNotificationHelper)
- .maybeShowWifiEnabledNotification(SAVED_WIFI_CONFIGURATION);
- }
-
- /**
- * When Wi-Fi is disabled and a saved network is in the scan list, and then another scan result
- * comes in 3x with only a different saved network, Wi-Fi should be enabled.
- */
- @Test
- public void wifiEnabled_userDisabledWifiNearSavedNetwork_thenMovesToAnotherSavedNetwork() {
- when(mWifiManager.getConfiguredNetworks()).thenReturn(
- Lists.newArrayList(SAVED_WIFI_CONFIGURATION, SAVED_WIFI_CONFIGURATION2));
- when(mWifiManager.getScanResults()).thenReturn(
- Lists.newArrayList(SAVED_SCAN_RESULT),
- Lists.newArrayList(SAVED_SCAN_RESULT2));
- when(mWifiWakeupNetworkSelector.selectNetwork(anyMap(), anyList()))
- .thenReturn(SAVED_WIFI_CONFIGURATION2);
- when(mWifiManager.getWifiState())
- .thenReturn(WifiManager.WIFI_STATE_ENABLED, WifiManager.WIFI_STATE_DISABLED);
-
- TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext);
- TestUtil.sendConfiguredNetworksChanged(mBroadcastReceiver, mContext);
- TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
- TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext);
-
- verify(mWifiManager, never()).setWifiEnabled(true);
-
- TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
- TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
- TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-
- verify(mWifiManager).setWifiEnabled(true);
- verify(mWifiWakeupNotificationHelper)
- .maybeShowWifiEnabledNotification(SAVED_WIFI_CONFIGURATION2);
- }
-
- /**
- * When a user disables Wi-Fi when there is a saved network marked with
- * {@link WifiConfiguration#useExternalScores} in the scan list, Wi-Fi should not be enabled if
- * the {@link WifiWakeupNetworkSelector} does not return an ssid.
- *
- * When {@link WifiWakeupNetworkSelector} does return an ssid, Wi-Fi should
- * be enabled (in absence of a scan list without the saved network) because networks marked
- * with external scores are not tracked by {@link WifiWakeupController}.
- */
- @Test
- public void wifiEnabled_userDisablesWifiNearExternallyScoredNetwork_thenNetworkIsSelected() {
- when(mWifiManager.getConfiguredNetworks()).thenReturn(
- Lists.newArrayList(SAVED_WIFI_CONFIGURATION, SAVED_WIFI_CONFIGURATION_EXTERNAL));
- when(mWifiManager.getScanResults())
- .thenReturn(Lists.newArrayList(SAVED_SCAN_RESULT_EXTERNAL));
- when(mWifiWakeupNetworkSelector.selectNetwork(anyMap(), anyList()))
- .thenReturn(null, SAVED_WIFI_CONFIGURATION_EXTERNAL);
- when(mWifiManager.getWifiState()).thenReturn(WifiManager.WIFI_STATE_DISABLED);
-
- TestUtil.sendConfiguredNetworksChanged(mBroadcastReceiver, mContext);
- TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext);
- TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-
- verify(mWifiManager, never()).setWifiEnabled(true);
-
- TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-
- verify(mWifiManager).setWifiEnabled(true);
- verify(mWifiWakeupNotificationHelper)
- .maybeShowWifiEnabledNotification(SAVED_WIFI_CONFIGURATION_EXTERNAL);
- }
-
- /**
- * When Wi-Fi is enabled and a saved network is in the scan list, Wi-Fi should not be enabled.
- */
- @Test
- public void wifiNotEnabled_wifiAlreadyEnabled() {
- when(mWifiManager.getConfiguredNetworks()).thenReturn(
- Lists.newArrayList(SAVED_WIFI_CONFIGURATION, SAVED_WIFI_CONFIGURATION_EXTERNAL));
- when(mWifiManager.getScanResults())
- .thenReturn(Lists.newArrayList(SAVED_SCAN_RESULT));
- when(mWifiManager.getWifiState()).thenReturn(WifiManager.WIFI_STATE_ENABLED);
-
- TestUtil.sendConfiguredNetworksChanged(mBroadcastReceiver, mContext);
- TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext);
- TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-
- verifyZeroInteractions(mWifiWakeupNetworkSelector);
- 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.
- */
- @Test
- public void wifiNotEnabled_userNearSavedNetworkButNotSelected() {
- when(mWifiManager.getConfiguredNetworks()).thenReturn(
- Lists.newArrayList(SAVED_WIFI_CONFIGURATION, SAVED_WIFI_CONFIGURATION_EXTERNAL));
- when(mWifiManager.getScanResults())
- .thenReturn(Lists.newArrayList(SAVED_SCAN_RESULT));
- when(mWifiWakeupNetworkSelector.selectNetwork(anyMap(), anyList())).thenReturn(null);
- when(mWifiManager.getWifiState()).thenReturn(WifiManager.WIFI_STATE_DISABLED);
-
- TestUtil.sendConfiguredNetworksChanged(mBroadcastReceiver, mContext);
- TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext);
- TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-
- verify(mWifiManager, never()).setWifiEnabled(true);
- }
-
- /**
- * If Wi-Fi is disabled and a saved network is in the scan list, Wi-Fi should not be enabled
- * if the user has not enabled the wifi wakeup feature.
- */
- @Test
- public void wifiNotEnabled_userDisablesWifiWakeupFeature() {
- Settings.Global.putInt(mContentResolver, Settings.Global.WIFI_WAKEUP_ENABLED, 0);
- when(mWifiManager.getConfiguredNetworks()).thenReturn(
- Lists.newArrayList(SAVED_WIFI_CONFIGURATION, SAVED_WIFI_CONFIGURATION_EXTERNAL));
- when(mWifiManager.getScanResults())
- .thenReturn(Lists.newArrayList(SAVED_SCAN_RESULT));
- when(mWifiManager.getWifiState()).thenReturn(WifiManager.WIFI_STATE_DISABLED);
-
- mWifiWakeupController.mContentObserver.onChange(true);
- TestUtil.sendConfiguredNetworksChanged(mBroadcastReceiver, mContext);
- TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext);
- TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-
- verifyZeroInteractions(mWifiWakeupNetworkSelector);
- verify(mWifiManager, never()).setWifiEnabled(true);
- }
-
- /**
- * If Wi-Fi is disabled and a saved network is in the scan list, Wi-Fi should not be enabled if
- * the user is in airplane mode.
- */
- @Test
- public void wifiNotEnabled_userIsInAirplaneMode() {
- Settings.Global.putInt(mContentResolver, Settings.Global.AIRPLANE_MODE_ON, 1);
- when(mWifiManager.getConfiguredNetworks()).thenReturn(
- Lists.newArrayList(SAVED_WIFI_CONFIGURATION, SAVED_WIFI_CONFIGURATION_EXTERNAL));
- when(mWifiManager.getScanResults())
- .thenReturn(Lists.newArrayList(SAVED_SCAN_RESULT));
- when(mWifiManager.getWifiState()).thenReturn(WifiManager.WIFI_STATE_DISABLED);
-
- mWifiWakeupController.mContentObserver.onChange(true);
- TestUtil.sendConfiguredNetworksChanged(mBroadcastReceiver, mContext);
- TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext);
- TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-
- verifyZeroInteractions(mWifiWakeupNetworkSelector);
- verify(mWifiManager, never()).setWifiEnabled(true);
- }
-
- /**
- * If Wi-Fi is disabled and a saved network is in the scan list, Wi-Fi should not be enabled if
- * the wifi AP state is not disabled.
- */
- @Test
- public void wifiNotEnabled_wifiApStateIsNotDisabled() {
- when(mWifiManager.getConfiguredNetworks()).thenReturn(
- Lists.newArrayList(SAVED_WIFI_CONFIGURATION, SAVED_WIFI_CONFIGURATION_EXTERNAL));
- when(mWifiManager.getScanResults())
- .thenReturn(Lists.newArrayList(SAVED_SCAN_RESULT));
- when(mWifiManager.getWifiState()).thenReturn(WifiManager.WIFI_STATE_DISABLED);
- when(mWifiManager.getWifiApState()).thenReturn(WifiManager.WIFI_AP_STATE_ENABLED);
-
- mWifiWakeupController.mContentObserver.onChange(true);
- TestUtil.sendConfiguredNetworksChanged(mBroadcastReceiver, mContext);
- TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext);
- TestUtil.sendWifiApStateChanged(mBroadcastReceiver, mContext);
- TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-
- verifyZeroInteractions(mWifiWakeupNetworkSelector);
- verify(mWifiManager, never()).setWifiEnabled(true);
- }
-
- /**
- * If Wi-Fi is disabled when a saved network is the scan list, Wi-Fi should not be enabled no
- * matter how many scans are performed that include the saved network.
- */
- @Test
- public void wifiNotEnabled_userDisablesWifiNearSavedNetwork_thenDoesNotLeave() {
- when(mWifiManager.getConfiguredNetworks()).thenReturn(
- Lists.newArrayList(SAVED_WIFI_CONFIGURATION, SAVED_WIFI_CONFIGURATION_EXTERNAL));
- when(mWifiManager.getScanResults())
- .thenReturn(Lists.newArrayList(SAVED_SCAN_RESULT));
-
- TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext);
- TestUtil.sendConfiguredNetworksChanged(mBroadcastReceiver, mContext);
- TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
- TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext);
- TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
- TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
- TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-
- verifyZeroInteractions(mWifiWakeupNetworkSelector);
- verify(mWifiManager, never()).setWifiEnabled(true);
- }
-
- /**
- * If Wi-Fi is disabled when a saved network is in the scan list, and then that saved network
- * is removed, Wi-Fi is not enabled even if the user leaves range of that network and returns.
- */
- @Test
- public void wifiNotEnabled_userDisablesWifiNearSavedNetwork_thenRemovesNetwork_thenStays() {
- when(mWifiManager.getConfiguredNetworks()).thenReturn(
- Lists.newArrayList(SAVED_WIFI_CONFIGURATION),
- Lists.<WifiConfiguration>newArrayList());
- when(mWifiManager.getScanResults())
- .thenReturn(Lists.newArrayList(SAVED_SCAN_RESULT))
- .thenReturn(Lists.<ScanResult>newArrayList())
- .thenReturn(Lists.newArrayList(SAVED_SCAN_RESULT));
- when(mWifiWakeupNetworkSelector.selectNetwork(anyMap(), anyList())).thenReturn(null);
- when(mWifiManager.getWifiState())
- .thenReturn(WifiManager.WIFI_STATE_ENABLED, WifiManager.WIFI_STATE_DISABLED);
-
- TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext);
- TestUtil.sendConfiguredNetworksChanged(mBroadcastReceiver, mContext);
- TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
- TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext);
- TestUtil.sendConfiguredNetworksChanged(mBroadcastReceiver, mContext);
- TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
- TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-
- verify(mWifiManager, never()).setWifiEnabled(true);
- }
-
- /**
- * If Wi-Fi is disabled when 2 saved networks are in the scan list, and then a scan result
- * comes in with only 1 saved network 3x, Wi-Fi should not be enabled.
- */
- @Test
- public void wifiNotEnabled_userDisablesWifiNear2SavedNetworks_thenLeavesRangeOfOneOfThem() {
- when(mWifiManager.getConfiguredNetworks()).thenReturn(
- Lists.newArrayList(SAVED_WIFI_CONFIGURATION, SAVED_WIFI_CONFIGURATION2));
- when(mWifiManager.getScanResults()).thenReturn(
- Lists.newArrayList(SAVED_SCAN_RESULT,
- SAVED_SCAN_RESULT2),
- Lists.newArrayList(SAVED_SCAN_RESULT));
- when(mWifiManager.getWifiState())
- .thenReturn(WifiManager.WIFI_STATE_ENABLED, WifiManager.WIFI_STATE_DISABLED);
-
- TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext);
- TestUtil.sendConfiguredNetworksChanged(mBroadcastReceiver, mContext);
- TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
- TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext);
- TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
- TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
- TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-
- verifyZeroInteractions(mWifiWakeupNetworkSelector);
- verify(mWifiManager, never()).setWifiEnabled(true);
- }
-
- /** Test dump() does not crash. */
- @Test
- public void testDump() {
- StringWriter stringWriter = new StringWriter();
- mWifiWakeupController.dump(
- new FileDescriptor(), new PrintWriter(stringWriter), new String[0]);
- }
-}
diff --git a/tests/src/com/android/networkrecommendation/WifiWakeupNetworkSelectorTest.java b/tests/src/com/android/networkrecommendation/WifiWakeupNetworkSelectorTest.java
deleted file mode 100644
index c54803c..0000000
--- a/tests/src/com/android/networkrecommendation/WifiWakeupNetworkSelectorTest.java
+++ /dev/null
@@ -1,143 +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;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.mockito.Mockito.when;
-
-import android.content.res.Resources;
-import android.net.wifi.ScanResult;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiSsid;
-import android.support.test.runner.AndroidJUnit4;
-import android.util.ArrayMap;
-
-import com.google.android.collect.Lists;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.List;
-
-/**
- * Unit tests for {@link WifiWakeupNetworkSelector}
- */
-@RunWith(AndroidJUnit4.class)
-public class WifiWakeupNetworkSelectorTest {
- private static final String SSID = "ssid";
- private static final String SSID2 = "ssid2";
-
- private static final WifiConfiguration WIFI_CONFIGURATION_PSK = new WifiConfiguration();
- private static final WifiConfiguration WIFI_CONFIGURATION_NONE = new WifiConfiguration();
- private static final ArrayMap<String, WifiConfiguration> SAVED_WIFI_CONFIGURATION_MAP =
- new ArrayMap<>();
-
- static {
- WIFI_CONFIGURATION_PSK.SSID = "\"" + SSID + "\"";
- WIFI_CONFIGURATION_PSK.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
- SAVED_WIFI_CONFIGURATION_MAP.put(SSID, WIFI_CONFIGURATION_PSK);
-
- WIFI_CONFIGURATION_NONE.SSID = "\"" + SSID2 + "\"";
- WIFI_CONFIGURATION_NONE.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
- SAVED_WIFI_CONFIGURATION_MAP.put(SSID2, WIFI_CONFIGURATION_NONE);
- }
-
- private static ScanResult buildScanResult(String ssid, int level, int frequency, String caps) {
- return new ScanResult(WifiSsid.createFromAsciiEncoded(ssid),
- "00:00:00:00:00:00", 0L, -1, null, caps, level, frequency, 0);
- }
-
- private static final int MIN_QUALIFIED_24 = -73;
- private static final int MIN_QUALIFIED_5 = -70;
- private static final int FREQUENCY_24 = 2450;
- private static final int FREQUENCY_5 = 5000;
- private static final String CAPABILITIES_NONE = "";
- private static final String CAPABILITIES_PSK = "PSK";
-
- @Mock private Resources mResources;
-
- private WifiWakeupNetworkSelector mWifiWakeupNetworkSelector;
-
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
-
- when(mResources.getInteger(
- R.integer.config_netrec_wifi_score_low_rssi_threshold_24GHz))
- .thenReturn(MIN_QUALIFIED_24);
- when(mResources.getInteger(
- R.integer.config_netrec_wifi_score_low_rssi_threshold_5GHz))
- .thenReturn(MIN_QUALIFIED_5);
- when(mResources.getInteger(
- R.integer.config_netrec_5GHz_preference_boost_factor))
- .thenReturn(100);
-
- mWifiWakeupNetworkSelector = new WifiWakeupNetworkSelector(mResources);
- }
-
- @Test
- public void testSelectNetwork_noSavedNetworksInScanResults() {
- List<ScanResult> scanResults = Lists.newArrayList(
- buildScanResult("blah", MIN_QUALIFIED_5 + 1, FREQUENCY_5, CAPABILITIES_NONE),
- buildScanResult("blahtoo", MIN_QUALIFIED_24 + 1, FREQUENCY_24, CAPABILITIES_NONE));
-
- WifiConfiguration selectedNetwork = mWifiWakeupNetworkSelector
- .selectNetwork(SAVED_WIFI_CONFIGURATION_MAP, scanResults);
-
- assertNull(selectedNetwork);
- }
-
- @Test
- public void testSelectNetwork_noQualifiedSavedNetworks() {
- List<ScanResult> scanResults = Lists.newArrayList(
- buildScanResult(SSID2, MIN_QUALIFIED_5 - 1, FREQUENCY_5, CAPABILITIES_NONE),
- buildScanResult(SSID2, MIN_QUALIFIED_24 - 1, FREQUENCY_24, CAPABILITIES_NONE));
-
- WifiConfiguration selectedNetwork = mWifiWakeupNetworkSelector
- .selectNetwork(SAVED_WIFI_CONFIGURATION_MAP, scanResults);
-
- assertNull(selectedNetwork);
- }
-
- @Test
- public void testSelectNetwork_noMatchingScanResults() {
- List<ScanResult> scanResults = Lists.newArrayList(
- buildScanResult(SSID, MIN_QUALIFIED_5 + 1, FREQUENCY_5, CAPABILITIES_NONE),
- buildScanResult(SSID, MIN_QUALIFIED_24 + 1, FREQUENCY_24, CAPABILITIES_NONE));
-
- WifiConfiguration selectedNetwork = mWifiWakeupNetworkSelector
- .selectNetwork(SAVED_WIFI_CONFIGURATION_MAP, scanResults);
-
- assertNull(selectedNetwork);
- }
-
- @Test
- public void testSelectNetwork_secureNetworkOverUnsecure() {
- List<ScanResult> scanResults = Lists.newArrayList(
- buildScanResult(SSID, MIN_QUALIFIED_5 + 1, FREQUENCY_5, CAPABILITIES_PSK),
- buildScanResult(SSID2, MIN_QUALIFIED_5 + 1, FREQUENCY_5, CAPABILITIES_NONE));
-
- WifiConfiguration selectedNetwork = mWifiWakeupNetworkSelector
- .selectNetwork(SAVED_WIFI_CONFIGURATION_MAP, scanResults);
-
- assertEquals(WIFI_CONFIGURATION_PSK, selectedNetwork);
- }
-}
diff --git a/tests/src/com/android/networkrecommendation/WifiWakeupNotificationHelperTest.java b/tests/src/com/android/networkrecommendation/WifiWakeupNotificationHelperTest.java
deleted file mode 100644
index 2091335..0000000
--- a/tests/src/com/android/networkrecommendation/WifiWakeupNotificationHelperTest.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.networkrecommendation;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.content.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.net.wifi.WifiSsid;
-import android.os.Handler;
-import android.os.Looper;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.Set;
-
-/**
- * Unit tests for {@link WifiWakeupNetworkSelector}
- */
-@RunWith(AndroidJUnit4.class)
-public class WifiWakeupNotificationHelperTest {
- private static final String SSID = "ssid";
- private static final WifiConfiguration WIFI_CONFIGURATION = new WifiConfiguration();
-
- static {
- WIFI_CONFIGURATION.SSID = "\"" + SSID + "\"";
- }
-
- private Context mContext;
- @Mock private NotificationManager mNotificationManager;
- @Mock private WifiManager mWifiManager;
- private SharedPreferences mSharedPreferences;
- private String mSharedPreferenceName;
-
- private WifiWakeupNotificationHelper mWifiWakeupNotificationHelper;
-
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
-
- mContext = InstrumentationRegistry.getTargetContext();
- mSharedPreferenceName = "WifiWakeupNotificationHelperTest";
- mSharedPreferences = mContext.getSharedPreferences(mSharedPreferenceName,
- Context.MODE_PRIVATE);
-
- mWifiWakeupNotificationHelper = new WifiWakeupNotificationHelper(mContext,
- mContext.getResources(), new Handler(Looper.getMainLooper()), mNotificationManager,
- mWifiManager, mSharedPreferences);
- }
-
- @After
- public void tearDown() throws Exception {
- mContext.deleteSharedPreferences(mSharedPreferenceName);
- }
-
- @Test
- public void notificationShowsOncePerSsid() {
- mWifiWakeupNotificationHelper.maybeShowWifiEnabledNotification(WIFI_CONFIGURATION);
- mWifiWakeupNotificationHelper.maybeShowWifiEnabledNotification(WIFI_CONFIGURATION);
-
- verify(mNotificationManager, times(1))
- .notify(anyString(), anyInt(), any(Notification.class));
- Set<String> ssidSet = mSharedPreferences.getStringSet(
- WifiWakeupNotificationHelper.KEY_SHOWN_SSIDS, null);
- assertEquals(1, ssidSet.size());
- assertTrue(ssidSet.contains(WIFI_CONFIGURATION.SSID));
- }
-
- @Test
- public void notificationCanceledWhenNeverConnected() {
- mWifiWakeupNotificationHelper.maybeShowWifiEnabledNotification(WIFI_CONFIGURATION);
-
- mWifiWakeupNotificationHelper.mCancelNotification.run();
-
- verify(mNotificationManager).cancel(anyString(), anyInt());
- }
-
- @Test
- public void notificationCanceledWhenWifiDisabled() {
- mWifiWakeupNotificationHelper.maybeShowWifiEnabledNotification(WIFI_CONFIGURATION);
-
- when(mWifiManager.isWifiEnabled()).thenReturn(false);
-
- mWifiWakeupNotificationHelper.mBroadcastReceiver.onReceive(mContext,
- new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION));
-
- verify(mNotificationManager).cancel(anyString(), anyInt());
- }
-
- @Test
- public void notificationCanceledWhenSsidChanged() {
- WifiInfo firstWifiInfo = new WifiInfo();
- firstWifiInfo.setSSID(WifiSsid.createFromAsciiEncoded(SSID));
- WifiInfo secondWifiInfo = new WifiInfo();
- firstWifiInfo.setSSID(WifiSsid.createFromAsciiEncoded("blah"));
-
- mWifiWakeupNotificationHelper.maybeShowWifiEnabledNotification(WIFI_CONFIGURATION);
-
- when(mWifiManager.isWifiEnabled()).thenReturn(true);
- when(mWifiManager.getConnectionInfo()).thenReturn(firstWifiInfo, secondWifiInfo);
-
- mWifiWakeupNotificationHelper.mBroadcastReceiver.onReceive(mContext,
- new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION));
-
- verify(mNotificationManager, never()).cancel(anyString(), anyInt());
-
- mWifiWakeupNotificationHelper.mBroadcastReceiver.onReceive(mContext,
- new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION));
-
- verify(mNotificationManager).cancel(anyString(), anyInt());
- }
-}