From 3ae41f6a9cd5e508baf751d0f8a8507a88cf16aa Mon Sep 17 00:00:00 2001 From: Joe LaPenna Date: Thu, 2 Feb 2017 17:57:09 -0800 Subject: Move code around for better organization. Bug: 34944625 Test: adb shell am instrument -w com.android.networkrecommendation.tests/android.support.test.runner.AndroidJUnitRunner Change-Id: If50a041009c1015319b1fad3dd30256e3c235a53 --- .../DefaultNetworkRecommendationProvider.java | 14 +- .../DefaultNetworkRecommendationService.java | 91 ---- .../android/networkrecommendation/ImageUtils.java | 62 --- .../NetworkRecommendationService.java | 97 +++++ .../networkrecommendation/ScanResultUtil.java | 121 ------ .../SynchronousNetworkRecommendationProvider.java | 2 +- .../WifiConfigurationUtil.java | 89 ---- .../WifiNotificationController.java | 456 -------------------- .../WifiNotificationHelper.java | 235 ----------- .../WifiWakeupController.java | 279 ------------- .../WifiWakeupNetworkSelector.java | 112 ----- .../WifiWakeupNotificationHelper.java | 189 --------- .../notify/WifiNotificationController.java | 461 +++++++++++++++++++++ .../notify/WifiNotificationHelper.java | 240 +++++++++++ .../networkrecommendation/util/ImageUtils.java | 62 +++ .../networkrecommendation/util/ScanResultUtil.java | 121 ++++++ .../util/WifiConfigurationUtil.java | 89 ++++ .../wakeup/WifiWakeupController.java | 282 +++++++++++++ .../wakeup/WifiWakeupNetworkSelector.java | 116 ++++++ .../wakeup/WifiWakeupNotificationHelper.java | 191 +++++++++ 20 files changed, 1668 insertions(+), 1641 deletions(-) delete mode 100644 src/com/android/networkrecommendation/DefaultNetworkRecommendationService.java delete mode 100644 src/com/android/networkrecommendation/ImageUtils.java create mode 100644 src/com/android/networkrecommendation/NetworkRecommendationService.java delete mode 100644 src/com/android/networkrecommendation/ScanResultUtil.java delete mode 100644 src/com/android/networkrecommendation/WifiConfigurationUtil.java delete mode 100644 src/com/android/networkrecommendation/WifiNotificationController.java delete mode 100644 src/com/android/networkrecommendation/WifiNotificationHelper.java delete mode 100644 src/com/android/networkrecommendation/WifiWakeupController.java delete mode 100644 src/com/android/networkrecommendation/WifiWakeupNetworkSelector.java delete mode 100644 src/com/android/networkrecommendation/WifiWakeupNotificationHelper.java create mode 100644 src/com/android/networkrecommendation/notify/WifiNotificationController.java create mode 100644 src/com/android/networkrecommendation/notify/WifiNotificationHelper.java create mode 100644 src/com/android/networkrecommendation/util/ImageUtils.java create mode 100644 src/com/android/networkrecommendation/util/ScanResultUtil.java create mode 100644 src/com/android/networkrecommendation/util/WifiConfigurationUtil.java create mode 100644 src/com/android/networkrecommendation/wakeup/WifiWakeupController.java create mode 100644 src/com/android/networkrecommendation/wakeup/WifiWakeupNetworkSelector.java create mode 100644 src/com/android/networkrecommendation/wakeup/WifiWakeupNotificationHelper.java (limited to 'src/com') diff --git a/src/com/android/networkrecommendation/DefaultNetworkRecommendationProvider.java b/src/com/android/networkrecommendation/DefaultNetworkRecommendationProvider.java index 355d2b8..0a585f4 100644 --- a/src/com/android/networkrecommendation/DefaultNetworkRecommendationProvider.java +++ b/src/com/android/networkrecommendation/DefaultNetworkRecommendationProvider.java @@ -33,6 +33,8 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; +import com.android.networkrecommendation.util.ScanResultUtil; + import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; @@ -49,13 +51,13 @@ import javax.annotation.concurrent.GuardedBy; *

This recommender is not yet recommended for non-development devices. * *

To debug: - * $ adb shell dumpsys activity service DefaultNetworkRecommendationService + * $ adb shell dumpsys activity service NetworkRecommendationService * *

Clear stored scores: - * $ adb shell dumpsys activity service DefaultNetworkRecommendationService clear + * $ adb shell dumpsys activity service NetworkRecommendationService clear * *

Score a network: - * $ adb shell dumpsys activity service DefaultNetworkRecommendationService addScore $SCORE + * $ adb shell dumpsys activity service NetworkRecommendationService addScore $SCORE * *

SCORE: "Quoted SSID",bssid|$RSSI_CURVE|metered|captivePortal|BADGE * @@ -67,17 +69,17 @@ import javax.annotation.concurrent.GuardedBy; * *

All commands should be executed on one line, no spaces between each line of the command.. *

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' * *

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' * *

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' */ 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/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/NetworkRecommendationService.java b/src/com/android/networkrecommendation/NetworkRecommendationService.java new file mode 100644 index 0000000..6583762 --- /dev/null +++ b/src/com/android/networkrecommendation/NetworkRecommendationService.java @@ -0,0 +1,97 @@ +/* + * 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 com.android.networkrecommendation.notify.WifiNotificationController; +import com.android.networkrecommendation.notify.WifiNotificationHelper; +import com.android.networkrecommendation.wakeup.WifiWakeupController; +import com.android.networkrecommendation.wakeup.WifiWakeupNetworkSelector; +import com.android.networkrecommendation.wakeup.WifiWakeupNotificationHelper; + +import java.io.FileDescriptor; +import java.io.PrintWriter; + +/** + * Provides network recommendations for the platform. + */ +public class NetworkRecommendationService 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/ScanResultUtil.java b/src/com/android/networkrecommendation/ScanResultUtil.java deleted file mode 100644 index 08b71d0..0000000 --- a/src/com/android/networkrecommendation/ScanResultUtil.java +++ /dev/null @@ -1,121 +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.net.wifi.ScanResult; -import android.net.wifi.WifiConfiguration; -import android.support.annotation.VisibleForTesting; -import android.text.TextUtils; - -/** - * 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. - */ - 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. - */ - 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. - */ - public static boolean isScanResultForWepNetwork(ScanResult scanResult) { - return scanResult.capabilities.contains("WEP"); - } - - /** - * Helper method to check if the provided |scanResult| corresponds to an open network or not. - * This checks if the provided capabilities string does not contain either of WEP, PSK or EAP - * encryption types or not. - */ - public static boolean isScanResultForOpenNetwork(ScanResult scanResult) { - return !(isScanResultForWepNetwork(scanResult) || isScanResultForPskNetwork(scanResult) - || isScanResultForEapNetwork(scanResult)); - } - - /** - * 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 + "\""; - } - - /** @return {@code true} if the result is for a 2.4GHz network. */ - public static boolean is24GHz(ScanResult result) { - return is24GHz(result.frequency); - } - - /** @return {@code true} if the frequency is for a 2.4GHz network. */ - public static boolean is24GHz(int freq) { - return freq > 2400 && freq < 2500; - } - - /** @return {@code true} if the result is for a 5GHz network. */ - public static boolean is5GHz(ScanResult result) { - return is5GHz(result.frequency); - } - - /** @return {@code true} if the frequency is for a 5GHz network. */ - public static boolean is5GHz(int freq) { - return freq > 4900 && freq < 5900; - } - - /** - * 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); - if (TextUtils.equals(config.SSID, configSSID)) { - if (ScanResultUtil.isScanResultForPskNetwork(scanResult) - && WifiConfigurationUtil.isConfigForPskNetwork(config)) { - return true; - } - if (ScanResultUtil.isScanResultForEapNetwork(scanResult) - && WifiConfigurationUtil.isConfigForEapNetwork(config)) { - return true; - } - if (ScanResultUtil.isScanResultForWepNetwork(scanResult) - && WifiConfigurationUtil.isConfigForWepNetwork(config)) { - return true; - } - if (ScanResultUtil.isScanResultForOpenNetwork(scanResult) - && WifiConfigurationUtil.isConfigForOpenNetwork(config)) { - return true; - } - } - return false; - } -} 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 @@ import android.net.ScoredNetwork; /** * 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/WifiConfigurationUtil.java b/src/com/android/networkrecommendation/WifiConfigurationUtil.java deleted file mode 100644 index d653a4f..0000000 --- a/src/com/android/networkrecommendation/WifiConfigurationUtil.java +++ /dev/null @@ -1,89 +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.net.wifi.WifiConfiguration; - -/** - * WifiConfiguration utility for any {@link WifiConfiguration} related operations.. - * TODO(b/34125341): Delete this class once exposed as a SystemApi - */ -public class WifiConfigurationUtil { - /** - * Checks if the provided |wepKeys| array contains any non-null value; - */ - public static boolean hasAnyValidWepKey(String[] wepKeys) { - for (int i = 0; i < wepKeys.length; i++) { - if (wepKeys[i] != null) { - return true; - } - } - return false; - } - - /** - * Helper method to check if the provided |config| corresponds to a PSK network or not. - */ - public static boolean isConfigForPskNetwork(WifiConfiguration config) { - return config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK); - } - - /** - * Helper method to check if the provided |config| corresponds to a EAP network or not. - */ - public static boolean isConfigForEapNetwork(WifiConfiguration config) { - return (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP) - || config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X)); - } - - /** - * Helper method to check if the provided |config| corresponds to a WEP network or not. - */ - public static boolean isConfigForWepNetwork(WifiConfiguration config) { - return (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE) - && hasAnyValidWepKey(config.wepKeys)); - } - - /** - * Helper method to check if the provided |config| corresponds to an open network or not. - */ - public static boolean isConfigForOpenNetwork(WifiConfiguration config) { - return !(isConfigForWepNetwork(config) || isConfigForPskNetwork(config) - || isConfigForEapNetwork(config)); - } - - /** @return a ssid that can be shown to the user. */ - public static String getPrintableSsid(WifiConfiguration config) { - if (config.SSID == null) return ""; - final int length = config.SSID.length(); - if (length > 2 && (config.SSID.charAt(0) == '"') && config.SSID.charAt(length - 1) == '"') { - return config.SSID.substring(1, length - 1); - } - return config.SSID; - } - - /** Removes " from the ssid in a wifi configuration (to match against a ScanResult). */ - public static String removeDoubleQuotes(WifiConfiguration config) { - if (config.SSID == null) return null; - final int length = config.SSID.length(); - if ((length > 1) && (config.SSID.charAt(0) == '"') - && (config.SSID.charAt(length - 1) == '"')) { - return config.SSID.substring(1, length - 1); - } - return config.SSID; - } -} 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 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 scanResults) { - if (scanResults == null || scanResults.isEmpty()) { - return null; - } - ArrayList 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 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 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. - * - *

- * 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 mSavedNetworks = new ArrayMap<>(); - private final Set mSavedSsidsInLastScan = new ArraySet<>(); - private final Set mSavedSsids = new ArraySet<>(); - private final Map 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 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 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 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 savedNetworks, - List 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 4a90486..0000000 --- a/src/com/android/networkrecommendation/WifiWakeupNotificationHelper.java +++ /dev/null @@ -1,189 +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 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, - new Intent(Settings.ACTION_CONFIGURE_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/notify/WifiNotificationController.java b/src/com/android/networkrecommendation/notify/WifiNotificationController.java new file mode 100644 index 0000000..a7afcdd --- /dev/null +++ b/src/com/android/networkrecommendation/notify/WifiNotificationController.java @@ -0,0 +1,461 @@ +/* + * 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 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 com.android.networkrecommendation.R; +import com.android.networkrecommendation.SynchronousNetworkRecommendationProvider; + +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; + + public 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 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 scanResults) { + if (scanResults == null || scanResults.isEmpty()) { + return null; + } + ArrayList 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; + } + + /** Dump debugging information. */ + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("mNotificationEnabled " + mNotificationEnabled); + pw.println("mNotificationRepeatTime " + mNotificationRepeatTime); + pw.println("mNotificationShown " + mNotificationShown); + pw.println("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..f37d0c1 --- /dev/null +++ b/src/com/android/networkrecommendation/notify/WifiNotificationHelper.java @@ -0,0 +1,240 @@ +/* + * 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 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 com.android.networkrecommendation.R; +import com.android.networkrecommendation.SynchronousNetworkRecommendationProvider; +import com.android.networkrecommendation.util.ImageUtils; +import com.android.networkrecommendation.util.WifiConfigurationUtil; + +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 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 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/util/ImageUtils.java b/src/com/android/networkrecommendation/util/ImageUtils.java new file mode 100644 index 0000000..e870563 --- /dev/null +++ b/src/com/android/networkrecommendation/util/ImageUtils.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.networkrecommendation.util; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; + +/** Helper for image manipulation */ +public class 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/util/ScanResultUtil.java b/src/com/android/networkrecommendation/util/ScanResultUtil.java new file mode 100644 index 0000000..c7b4e25 --- /dev/null +++ b/src/com/android/networkrecommendation/util/ScanResultUtil.java @@ -0,0 +1,121 @@ +/* + * 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.wifi.ScanResult; +import android.net.wifi.WifiConfiguration; +import android.support.annotation.VisibleForTesting; +import android.text.TextUtils; + +/** + * 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. + */ + 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. + */ + 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. + */ + public static boolean isScanResultForWepNetwork(ScanResult scanResult) { + return scanResult.capabilities.contains("WEP"); + } + + /** + * Helper method to check if the provided |scanResult| corresponds to an open network or not. + * This checks if the provided capabilities string does not contain either of WEP, PSK or EAP + * encryption types or not. + */ + public static boolean isScanResultForOpenNetwork(ScanResult scanResult) { + return !(isScanResultForWepNetwork(scanResult) || isScanResultForPskNetwork(scanResult) + || isScanResultForEapNetwork(scanResult)); + } + + /** + * 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 + "\""; + } + + /** @return {@code true} if the result is for a 2.4GHz network. */ + public static boolean is24GHz(ScanResult result) { + return is24GHz(result.frequency); + } + + /** @return {@code true} if the frequency is for a 2.4GHz network. */ + public static boolean is24GHz(int freq) { + return freq > 2400 && freq < 2500; + } + + /** @return {@code true} if the result is for a 5GHz network. */ + public static boolean is5GHz(ScanResult result) { + return is5GHz(result.frequency); + } + + /** @return {@code true} if the frequency is for a 5GHz network. */ + public static boolean is5GHz(int freq) { + return freq > 4900 && freq < 5900; + } + + /** + * 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); + if (TextUtils.equals(config.SSID, configSSID)) { + if (ScanResultUtil.isScanResultForPskNetwork(scanResult) + && WifiConfigurationUtil.isConfigForPskNetwork(config)) { + return true; + } + if (ScanResultUtil.isScanResultForEapNetwork(scanResult) + && WifiConfigurationUtil.isConfigForEapNetwork(config)) { + return true; + } + if (ScanResultUtil.isScanResultForWepNetwork(scanResult) + && WifiConfigurationUtil.isConfigForWepNetwork(config)) { + return true; + } + if (ScanResultUtil.isScanResultForOpenNetwork(scanResult) + && WifiConfigurationUtil.isConfigForOpenNetwork(config)) { + return true; + } + } + return false; + } +} diff --git a/src/com/android/networkrecommendation/util/WifiConfigurationUtil.java b/src/com/android/networkrecommendation/util/WifiConfigurationUtil.java new file mode 100644 index 0000000..3616cd7 --- /dev/null +++ b/src/com/android/networkrecommendation/util/WifiConfigurationUtil.java @@ -0,0 +1,89 @@ +/* + * 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.wifi.WifiConfiguration; + +/** + * WifiConfiguration utility for any {@link WifiConfiguration} related operations.. + * TODO(b/34125341): Delete this class once exposed as a SystemApi + */ +public class WifiConfigurationUtil { + /** + * Checks if the provided |wepKeys| array contains any non-null value; + */ + public static boolean hasAnyValidWepKey(String[] wepKeys) { + for (int i = 0; i < wepKeys.length; i++) { + if (wepKeys[i] != null) { + return true; + } + } + return false; + } + + /** + * Helper method to check if the provided |config| corresponds to a PSK network or not. + */ + public static boolean isConfigForPskNetwork(WifiConfiguration config) { + return config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK); + } + + /** + * Helper method to check if the provided |config| corresponds to a EAP network or not. + */ + public static boolean isConfigForEapNetwork(WifiConfiguration config) { + return (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP) + || config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X)); + } + + /** + * Helper method to check if the provided |config| corresponds to a WEP network or not. + */ + public static boolean isConfigForWepNetwork(WifiConfiguration config) { + return (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE) + && hasAnyValidWepKey(config.wepKeys)); + } + + /** + * Helper method to check if the provided |config| corresponds to an open network or not. + */ + public static boolean isConfigForOpenNetwork(WifiConfiguration config) { + return !(isConfigForWepNetwork(config) || isConfigForPskNetwork(config) + || isConfigForEapNetwork(config)); + } + + /** @return a ssid that can be shown to the user. */ + public static String getPrintableSsid(WifiConfiguration config) { + if (config.SSID == null) return ""; + final int length = config.SSID.length(); + if (length > 2 && (config.SSID.charAt(0) == '"') && config.SSID.charAt(length - 1) == '"') { + return config.SSID.substring(1, length - 1); + } + return config.SSID; + } + + /** Removes " from the ssid in a wifi configuration (to match against a ScanResult). */ + public static String removeDoubleQuotes(WifiConfiguration config) { + if (config.SSID == null) return null; + final int length = config.SSID.length(); + if ((length > 1) && (config.SSID.charAt(0) == '"') + && (config.SSID.charAt(length - 1) == '"')) { + return config.SSID.substring(1, length - 1); + } + return config.SSID; + } +} diff --git a/src/com/android/networkrecommendation/wakeup/WifiWakeupController.java b/src/com/android/networkrecommendation/wakeup/WifiWakeupController.java new file mode 100644 index 0000000..a62bfbd --- /dev/null +++ b/src/com/android/networkrecommendation/wakeup/WifiWakeupController.java @@ -0,0 +1,282 @@ +/* + * 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.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 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. + * + *

+ * 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 mSavedNetworks = new ArrayMap<>(); + private final Set mSavedSsidsInLastScan = new ArraySet<>(); + private final Set mSavedSsids = new ArraySet<>(); + private final Map mSavedSsidsOnDisable = new ArrayMap<>(); + private int mWifiState; + private int mWifiApState; + private boolean mWifiWakeupEnabled; + private boolean mAirplaneModeEnabled; + + public 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 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 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 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); + } + } + + /** Dump debugging information. */ + 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); + } +} diff --git a/src/com/android/networkrecommendation/wakeup/WifiWakeupNetworkSelector.java b/src/com/android/networkrecommendation/wakeup/WifiWakeupNetworkSelector.java new file mode 100644 index 0000000..7e3e884 --- /dev/null +++ b/src/com/android/networkrecommendation/wakeup/WifiWakeupNetworkSelector.java @@ -0,0 +1,116 @@ +/* + * 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.wifi.ScanResult; +import android.net.wifi.WifiConfiguration; + +import com.android.networkrecommendation.R; +import com.android.networkrecommendation.util.ScanResultUtil; +import com.android.networkrecommendation.util.WifiConfigurationUtil; + +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 savedNetworks, + List 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/wakeup/WifiWakeupNotificationHelper.java b/src/com/android/networkrecommendation/wakeup/WifiWakeupNotificationHelper.java new file mode 100644 index 0000000..2659752 --- /dev/null +++ b/src/com/android/networkrecommendation/wakeup/WifiWakeupNotificationHelper.java @@ -0,0 +1,191 @@ +/* + * 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.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 com.android.networkrecommendation.R; + +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 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, + new Intent(Settings.ACTION_CONFIGURE_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(); + } + } + } +} -- cgit v1.2.3