Loading core/java/android/provider/Settings.java +33 −19 Original line number Diff line number Diff line Loading @@ -7427,6 +7427,20 @@ public final class Settings { @SystemApi public static final String WEBVIEW_MULTIPROCESS = "webview_multiprocess"; /** * The maximum number of notifications shown in 24 hours when switching networks. * @hide */ public static final String NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT = "network_switch_notification_daily_limit"; /** * The minimum time in milliseconds between notifications when switching networks. * @hide */ public static final String NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS = "network_switch_notification_rate_limit_millis"; /** * Whether Wifi display is enabled/disabled * 0=disabled. 1=enabled. Loading services/core/java/com/android/server/ConnectivityService.java +8 −1 Original line number Diff line number Diff line Loading @@ -838,7 +838,14 @@ public class ConnectivityService extends IConnectivityManager.Stub mKeepaliveTracker = new KeepaliveTracker(mHandler); mNotifier = new NetworkNotificationManager(mContext, mTelephonyManager, mContext.getSystemService(NotificationManager.class)); mLingerMonitor = new LingerMonitor(mContext, mNotifier); final int dailyLimit = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT, LingerMonitor.DEFAULT_NOTIFICATION_DAILY_LIMIT); final long rateLimit = Settings.Global.getLong(mContext.getContentResolver(), Settings.Global.NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS, LingerMonitor.DEFAULT_NOTIFICATION_RATE_LIMIT_MILLIS); mLingerMonitor = new LingerMonitor(mContext, mNotifier, dailyLimit, rateLimit); } private NetworkRequest createInternetRequestForTransport(int transportType) { Loading services/core/java/com/android/server/connectivity/LingerMonitor.java +82 −40 Original line number Diff line number Diff line Loading @@ -17,15 +17,14 @@ package com.android.server.connectivity; import android.app.PendingIntent; import android.net.ConnectivityManager; import android.net.NetworkCapabilities; import android.net.Uri; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.net.NetworkCapabilities; import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; import android.text.TextUtils; import android.text.format.DateUtils; import android.util.Log; import android.util.SparseArray; import android.util.SparseIntArray; Loading @@ -33,6 +32,8 @@ import android.util.SparseBooleanArray; import java.util.Arrays; import java.util.HashMap; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.MessageUtils; import com.android.server.connectivity.NetworkNotificationManager; import com.android.server.connectivity.NetworkNotificationManager.NotificationType; Loading @@ -52,19 +53,30 @@ public class LingerMonitor { private static final boolean VDBG = false; private static final String TAG = LingerMonitor.class.getSimpleName(); private static final HashMap<String, Integer> sTransportNames = makeTransportToNameMap(); private static final Intent CELLULAR_SETTINGS = new Intent().setComponent(new ComponentName( public static final int DEFAULT_NOTIFICATION_DAILY_LIMIT = 3; public static final long DEFAULT_NOTIFICATION_RATE_LIMIT_MILLIS = DateUtils.MINUTE_IN_MILLIS; private static final HashMap<String, Integer> TRANSPORT_NAMES = makeTransportToNameMap(); @VisibleForTesting public static final Intent CELLULAR_SETTINGS = new Intent().setComponent(new ComponentName( "com.android.settings", "com.android.settings.Settings$DataUsageSummaryActivity")); private static final int NOTIFY_TYPE_NONE = 0; private static final int NOTIFY_TYPE_NOTIFICATION = 1; private static final int NOTIFY_TYPE_TOAST = 2; @VisibleForTesting public static final int NOTIFY_TYPE_NONE = 0; public static final int NOTIFY_TYPE_NOTIFICATION = 1; public static final int NOTIFY_TYPE_TOAST = 2; private static SparseArray<String> sNotifyTypeNames = MessageUtils.findMessageNames( new Class[] { LingerMonitor.class }, new String[]{ "NOTIFY_TYPE_" }); private final Context mContext; private final NetworkNotificationManager mNotifier; private final int mDailyLimit; private final long mRateLimitMillis; private long mFirstNotificationMillis; private long mLastNotificationMillis; private int mNotificationCounter; /** Current notifications. Maps the netId we switched away from to the netId we switched to. */ private final SparseIntArray mNotifications = new SparseIntArray(); Loading @@ -72,9 +84,12 @@ public class LingerMonitor { /** Whether we ever notified that we switched away from a particular network. */ private final SparseBooleanArray mEverNotified = new SparseBooleanArray(); public LingerMonitor(Context context, NetworkNotificationManager notifier) { public LingerMonitor(Context context, NetworkNotificationManager notifier, int dailyLimit, long rateLimitMillis) { mContext = context; mNotifier = notifier; mDailyLimit = dailyLimit; mRateLimitMillis = rateLimitMillis; } private static HashMap<String, Integer> makeTransportToNameMap() { Loading Loading @@ -106,10 +121,11 @@ public class LingerMonitor { return mEverNotified.get(nai.network.netId, false); } private boolean isNotificationEnabled(NetworkAgentInfo fromNai, NetworkAgentInfo toNai) { @VisibleForTesting public boolean isNotificationEnabled(NetworkAgentInfo fromNai, NetworkAgentInfo toNai) { // TODO: Evaluate moving to CarrierConfigManager. String[] notifySwitches = mContext.getResources().getStringArray( com.android.internal.R.array.config_networkNotifySwitches); String[] notifySwitches = mContext.getResources().getStringArray(R.array.config_networkNotifySwitches); if (VDBG) { Log.d(TAG, "Notify on network switches: " + Arrays.toString(notifySwitches)); Loading @@ -122,8 +138,8 @@ public class LingerMonitor { Log.e(TAG, "Invalid network switch notification configuration: " + notifySwitch); continue; } int fromTransport = sTransportNames.get("TRANSPORT_" + transports[0]); int toTransport = sTransportNames.get("TRANSPORT_" + transports[1]); int fromTransport = TRANSPORT_NAMES.get("TRANSPORT_" + transports[0]); int toTransport = TRANSPORT_NAMES.get("TRANSPORT_" + transports[1]); if (hasTransport(fromNai, fromTransport) && hasTransport(toNai, toTransport)) { return true; } Loading @@ -133,12 +149,14 @@ public class LingerMonitor { } private void showNotification(NetworkAgentInfo fromNai, NetworkAgentInfo toNai) { PendingIntent pendingIntent = PendingIntent.getActivityAsUser( mContext, 0, CELLULAR_SETTINGS, PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT); mNotifier.showNotification(fromNai.network.netId, NotificationType.NETWORK_SWITCH, fromNai, toNai, pendingIntent, true); fromNai, toNai, createNotificationIntent(), true); } @VisibleForTesting protected PendingIntent createNotificationIntent() { return PendingIntent.getActivityAsUser(mContext, 0, CELLULAR_SETTINGS, PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT); } // Removes any notification that was put up as a result of switching to nai. Loading @@ -153,42 +171,38 @@ public class LingerMonitor { // Notify the user of a network switch using a notification or a toast. private void notify(NetworkAgentInfo fromNai, NetworkAgentInfo toNai, boolean forceToast) { boolean notify = false; int notifyType = mContext.getResources().getInteger( com.android.internal.R.integer.config_networkNotifySwitchType); int notifyType = mContext.getResources().getInteger(R.integer.config_networkNotifySwitchType); if (notifyType == NOTIFY_TYPE_NOTIFICATION && forceToast) { notifyType = NOTIFY_TYPE_TOAST; } if (VDBG) { Log.d(TAG, "Notify type: " + sNotifyTypeNames.get(notifyType, "" + notifyType)); } switch (notifyType) { case NOTIFY_TYPE_NONE: break; return; case NOTIFY_TYPE_NOTIFICATION: showNotification(fromNai, toNai); notify = true; break; case NOTIFY_TYPE_TOAST: mNotifier.showToast(fromNai, toNai); notify = true; break; default: Log.e(TAG, "Unknown notify type " + notifyType); return; } if (VDBG) { Log.d(TAG, "Notify type: " + sNotifyTypeNames.get(notifyType, "" + notifyType)); } if (notify) { if (DBG) { Log.d(TAG, "Notifying switch from=" + fromNai.name() + " to=" + toNai.name() + " type=" + sNotifyTypeNames.get(notifyType, "unknown(" + notifyType + ")")); } mNotifications.put(fromNai.network.netId, toNai.network.netId); mEverNotified.put(fromNai.network.netId, true); } } // The default network changed from fromNai to toNai due to a change in score. public void noteLingerDefaultNetwork(NetworkAgentInfo fromNai, NetworkAgentInfo toNai) { Loading Loading @@ -248,10 +262,13 @@ public class LingerMonitor { // unvalidated. if (fromNai.lastValidated) return; if (isNotificationEnabled(fromNai, toNai)) { if (!isNotificationEnabled(fromNai, toNai)) return; final long now = SystemClock.elapsedRealtime(); if (isRateLimited(now) || isAboveDailyLimit(now)) return; notify(fromNai, toNai, forceToast); } } public void noteDisconnect(NetworkAgentInfo nai) { mNotifications.delete(nai.network.netId); Loading @@ -259,4 +276,29 @@ public class LingerMonitor { maybeStopNotifying(nai); // No need to cancel notifications on nai: NetworkMonitor does that on disconnect. } private boolean isRateLimited(long now) { final long millisSinceLast = now - mLastNotificationMillis; if (millisSinceLast < mRateLimitMillis) { return true; } mLastNotificationMillis = now; return false; } private boolean isAboveDailyLimit(long now) { if (mFirstNotificationMillis == 0) { mFirstNotificationMillis = now; } final long millisSinceFirst = now - mFirstNotificationMillis; if (millisSinceFirst > DateUtils.DAY_IN_MILLIS) { mNotificationCounter = 0; mFirstNotificationMillis = 0; } if (mNotificationCounter >= mDailyLimit) { return true; } mNotificationCounter++; return false; } } services/tests/servicestests/src/com/android/server/connectivity/LingerMonitorTest.java 0 → 100644 +349 −0 Original line number Diff line number Diff line /* * 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.server.connectivity; import android.app.PendingIntent; import android.content.Context; import android.content.res.Resources; import android.net.ConnectivityManager; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.net.NetworkMisc; import android.text.format.DateUtils; import com.android.internal.R; import com.android.server.ConnectivityService; import com.android.server.connectivity.NetworkNotificationManager; import com.android.server.connectivity.NetworkNotificationManager.NotificationType; import junit.framework.TestCase; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyBoolean; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.mockito.Mockito.reset; public class LingerMonitorTest extends TestCase { static final String CELLULAR = "CELLULAR"; static final String WIFI = "WIFI"; static final long LOW_RATE_LIMIT = DateUtils.MINUTE_IN_MILLIS; static final long HIGH_RATE_LIMIT = 0; static final int LOW_DAILY_LIMIT = 2; static final int HIGH_DAILY_LIMIT = 1000; LingerMonitor mMonitor; @Mock ConnectivityService mConnService; @Mock Context mCtx; @Mock NetworkMisc mMisc; @Mock NetworkNotificationManager mNotifier; @Mock Resources mResources; public void setUp() { MockitoAnnotations.initMocks(this); when(mCtx.getResources()).thenReturn(mResources); when(mCtx.getPackageName()).thenReturn("com.android.server.connectivity"); when(mConnService.createNetworkMonitor(any(), any(), any(), any())).thenReturn(null); mMonitor = new TestableLingerMonitor(mCtx, mNotifier, HIGH_DAILY_LIMIT, HIGH_RATE_LIMIT); } public void testTransitions() { setNotificationSwitch(transition(WIFI, CELLULAR)); NetworkAgentInfo nai1 = wifiNai(100); NetworkAgentInfo nai2 = cellNai(101); assertTrue(mMonitor.isNotificationEnabled(nai1, nai2)); assertFalse(mMonitor.isNotificationEnabled(nai2, nai1)); } public void testNotificationOnLinger() { setNotificationSwitch(transition(WIFI, CELLULAR)); setNotificationType(LingerMonitor.NOTIFY_TYPE_NOTIFICATION); NetworkAgentInfo from = wifiNai(100); NetworkAgentInfo to = cellNai(101); mMonitor.noteLingerDefaultNetwork(from, to); verifyNotification(from, to); } public void testToastOnLinger() { setNotificationSwitch(transition(WIFI, CELLULAR)); setNotificationType(LingerMonitor.NOTIFY_TYPE_TOAST); NetworkAgentInfo from = wifiNai(100); NetworkAgentInfo to = cellNai(101); mMonitor.noteLingerDefaultNetwork(from, to); verifyToast(from, to); } public void testNotificationClearedAfterDisconnect() { setNotificationSwitch(transition(WIFI, CELLULAR)); setNotificationType(LingerMonitor.NOTIFY_TYPE_NOTIFICATION); NetworkAgentInfo from = wifiNai(100); NetworkAgentInfo to = cellNai(101); mMonitor.noteLingerDefaultNetwork(from, to); verifyNotification(from, to); mMonitor.noteDisconnect(to); verify(mNotifier, times(1)).clearNotification(100); } public void testNotificationClearedAfterSwitchingBack() { setNotificationSwitch(transition(WIFI, CELLULAR)); setNotificationType(LingerMonitor.NOTIFY_TYPE_NOTIFICATION); NetworkAgentInfo from = wifiNai(100); NetworkAgentInfo to = cellNai(101); mMonitor.noteLingerDefaultNetwork(from, to); verifyNotification(from, to); mMonitor.noteLingerDefaultNetwork(to, from); verify(mNotifier, times(1)).clearNotification(100); } public void testUniqueToast() { setNotificationSwitch(transition(WIFI, CELLULAR)); setNotificationType(LingerMonitor.NOTIFY_TYPE_TOAST); NetworkAgentInfo from = wifiNai(100); NetworkAgentInfo to = cellNai(101); mMonitor.noteLingerDefaultNetwork(from, to); verifyToast(from, to); mMonitor.noteLingerDefaultNetwork(to, from); verify(mNotifier, times(1)).clearNotification(100); reset(mNotifier); mMonitor.noteLingerDefaultNetwork(from, to); verifyNoNotifications(); } public void testMultipleNotifications() { setNotificationSwitch(transition(WIFI, CELLULAR)); setNotificationType(LingerMonitor.NOTIFY_TYPE_NOTIFICATION); NetworkAgentInfo wifi1 = wifiNai(100); NetworkAgentInfo wifi2 = wifiNai(101); NetworkAgentInfo cell = cellNai(102); mMonitor.noteLingerDefaultNetwork(wifi1, cell); verifyNotification(wifi1, cell); mMonitor.noteLingerDefaultNetwork(cell, wifi2); verify(mNotifier, times(1)).clearNotification(100); reset(mNotifier); mMonitor.noteLingerDefaultNetwork(wifi2, cell); verifyNotification(wifi2, cell); } public void testRateLimiting() throws InterruptedException { mMonitor = new TestableLingerMonitor(mCtx, mNotifier, HIGH_DAILY_LIMIT, LOW_RATE_LIMIT); setNotificationSwitch(transition(WIFI, CELLULAR)); setNotificationType(LingerMonitor.NOTIFY_TYPE_NOTIFICATION); NetworkAgentInfo wifi1 = wifiNai(100); NetworkAgentInfo wifi2 = wifiNai(101); NetworkAgentInfo wifi3 = wifiNai(102); NetworkAgentInfo cell = cellNai(103); mMonitor.noteLingerDefaultNetwork(wifi1, cell); verifyNotification(wifi1, cell); reset(mNotifier); Thread.sleep(50); mMonitor.noteLingerDefaultNetwork(cell, wifi2); mMonitor.noteLingerDefaultNetwork(wifi2, cell); verifyNoNotifications(); Thread.sleep(50); mMonitor.noteLingerDefaultNetwork(cell, wifi3); mMonitor.noteLingerDefaultNetwork(wifi3, cell); verifyNoNotifications(); } public void testDailyLimiting() throws InterruptedException { mMonitor = new TestableLingerMonitor(mCtx, mNotifier, LOW_DAILY_LIMIT, HIGH_RATE_LIMIT); setNotificationSwitch(transition(WIFI, CELLULAR)); setNotificationType(LingerMonitor.NOTIFY_TYPE_NOTIFICATION); NetworkAgentInfo wifi1 = wifiNai(100); NetworkAgentInfo wifi2 = wifiNai(101); NetworkAgentInfo wifi3 = wifiNai(102); NetworkAgentInfo cell = cellNai(103); mMonitor.noteLingerDefaultNetwork(wifi1, cell); verifyNotification(wifi1, cell); reset(mNotifier); Thread.sleep(50); mMonitor.noteLingerDefaultNetwork(cell, wifi2); mMonitor.noteLingerDefaultNetwork(wifi2, cell); verifyNotification(wifi2, cell); reset(mNotifier); Thread.sleep(50); mMonitor.noteLingerDefaultNetwork(cell, wifi3); mMonitor.noteLingerDefaultNetwork(wifi3, cell); verifyNoNotifications(); } public void testUniqueNotification() { setNotificationSwitch(transition(WIFI, CELLULAR)); setNotificationType(LingerMonitor.NOTIFY_TYPE_NOTIFICATION); NetworkAgentInfo from = wifiNai(100); NetworkAgentInfo to = cellNai(101); mMonitor.noteLingerDefaultNetwork(from, to); verifyNotification(from, to); mMonitor.noteLingerDefaultNetwork(to, from); verify(mNotifier, times(1)).clearNotification(100); mMonitor.noteLingerDefaultNetwork(from, to); verifyNotification(from, to); } public void testIgnoreNeverValidatedNetworks() { setNotificationType(LingerMonitor.NOTIFY_TYPE_TOAST); setNotificationSwitch(transition(WIFI, CELLULAR)); NetworkAgentInfo from = wifiNai(100); NetworkAgentInfo to = cellNai(101); from.everValidated = false; mMonitor.noteLingerDefaultNetwork(from, to); verifyNoNotifications(); } public void testIgnoreCurrentlyValidatedNetworks() { setNotificationType(LingerMonitor.NOTIFY_TYPE_TOAST); setNotificationSwitch(transition(WIFI, CELLULAR)); NetworkAgentInfo from = wifiNai(100); NetworkAgentInfo to = cellNai(101); from.lastValidated = true; mMonitor.noteLingerDefaultNetwork(from, to); verifyNoNotifications(); } public void testNoNotificationType() { setNotificationType(LingerMonitor.NOTIFY_TYPE_TOAST); setNotificationSwitch(); NetworkAgentInfo from = wifiNai(100); NetworkAgentInfo to = cellNai(101); mMonitor.noteLingerDefaultNetwork(from, to); verifyNoNotifications(); } public void testNoTransitionToNotify() { setNotificationType(LingerMonitor.NOTIFY_TYPE_NONE); setNotificationSwitch(transition(WIFI, CELLULAR)); NetworkAgentInfo from = wifiNai(100); NetworkAgentInfo to = cellNai(101); mMonitor.noteLingerDefaultNetwork(from, to); verifyNoNotifications(); } public void testDifferentTransitionToNotify() { setNotificationType(LingerMonitor.NOTIFY_TYPE_TOAST); setNotificationSwitch(transition(CELLULAR, WIFI)); NetworkAgentInfo from = wifiNai(100); NetworkAgentInfo to = cellNai(101); mMonitor.noteLingerDefaultNetwork(from, to); verifyNoNotifications(); } void setNotificationSwitch(String... transitions) { when(mResources.getStringArray(R.array.config_networkNotifySwitches)) .thenReturn(transitions); } String transition(String from, String to) { return from + "-" + to; } void setNotificationType(int type) { when(mResources.getInteger(R.integer.config_networkNotifySwitchType)).thenReturn(type); } void verifyNoToast() { verify(mNotifier, never()).showToast(any(), any()); } void verifyNoNotification() { verify(mNotifier, never()) .showNotification(anyInt(), any(), any(), any(), any(), anyBoolean()); } void verifyNoNotifications() { verifyNoToast(); verifyNoNotification(); } void verifyToast(NetworkAgentInfo from, NetworkAgentInfo to) { verifyNoNotification(); verify(mNotifier, times(1)).showToast(from, to); } void verifyNotification(NetworkAgentInfo from, NetworkAgentInfo to) { verifyNoToast(); verify(mNotifier, times(1)).showNotification(eq(from.network.netId), eq(NotificationType.NETWORK_SWITCH), eq(from), eq(to), any(), eq(true)); } NetworkAgentInfo nai(int netId, int transport, int networkType, String networkTypeName) { NetworkInfo info = new NetworkInfo(networkType, 0, networkTypeName, ""); NetworkCapabilities caps = new NetworkCapabilities(); caps.addCapability(0); caps.addTransportType(transport); NetworkAgentInfo nai = new NetworkAgentInfo(null, null, new Network(netId), info, null, caps, 50, mCtx, null, mMisc, null, mConnService); nai.everValidated = true; return nai; } NetworkAgentInfo wifiNai(int netId) { return nai(netId, NetworkCapabilities.TRANSPORT_WIFI, ConnectivityManager.TYPE_WIFI, WIFI); } NetworkAgentInfo cellNai(int netId) { return nai(netId, NetworkCapabilities.TRANSPORT_CELLULAR, ConnectivityManager.TYPE_MOBILE, CELLULAR); } public static class TestableLingerMonitor extends LingerMonitor { public TestableLingerMonitor(Context c, NetworkNotificationManager n, int l, long r) { super(c, n, l, r); } @Override protected PendingIntent createNotificationIntent() { return null; } } } Loading
core/java/android/provider/Settings.java +33 −19 Original line number Diff line number Diff line Loading @@ -7427,6 +7427,20 @@ public final class Settings { @SystemApi public static final String WEBVIEW_MULTIPROCESS = "webview_multiprocess"; /** * The maximum number of notifications shown in 24 hours when switching networks. * @hide */ public static final String NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT = "network_switch_notification_daily_limit"; /** * The minimum time in milliseconds between notifications when switching networks. * @hide */ public static final String NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS = "network_switch_notification_rate_limit_millis"; /** * Whether Wifi display is enabled/disabled * 0=disabled. 1=enabled. Loading
services/core/java/com/android/server/ConnectivityService.java +8 −1 Original line number Diff line number Diff line Loading @@ -838,7 +838,14 @@ public class ConnectivityService extends IConnectivityManager.Stub mKeepaliveTracker = new KeepaliveTracker(mHandler); mNotifier = new NetworkNotificationManager(mContext, mTelephonyManager, mContext.getSystemService(NotificationManager.class)); mLingerMonitor = new LingerMonitor(mContext, mNotifier); final int dailyLimit = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT, LingerMonitor.DEFAULT_NOTIFICATION_DAILY_LIMIT); final long rateLimit = Settings.Global.getLong(mContext.getContentResolver(), Settings.Global.NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS, LingerMonitor.DEFAULT_NOTIFICATION_RATE_LIMIT_MILLIS); mLingerMonitor = new LingerMonitor(mContext, mNotifier, dailyLimit, rateLimit); } private NetworkRequest createInternetRequestForTransport(int transportType) { Loading
services/core/java/com/android/server/connectivity/LingerMonitor.java +82 −40 Original line number Diff line number Diff line Loading @@ -17,15 +17,14 @@ package com.android.server.connectivity; import android.app.PendingIntent; import android.net.ConnectivityManager; import android.net.NetworkCapabilities; import android.net.Uri; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.net.NetworkCapabilities; import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; import android.text.TextUtils; import android.text.format.DateUtils; import android.util.Log; import android.util.SparseArray; import android.util.SparseIntArray; Loading @@ -33,6 +32,8 @@ import android.util.SparseBooleanArray; import java.util.Arrays; import java.util.HashMap; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.MessageUtils; import com.android.server.connectivity.NetworkNotificationManager; import com.android.server.connectivity.NetworkNotificationManager.NotificationType; Loading @@ -52,19 +53,30 @@ public class LingerMonitor { private static final boolean VDBG = false; private static final String TAG = LingerMonitor.class.getSimpleName(); private static final HashMap<String, Integer> sTransportNames = makeTransportToNameMap(); private static final Intent CELLULAR_SETTINGS = new Intent().setComponent(new ComponentName( public static final int DEFAULT_NOTIFICATION_DAILY_LIMIT = 3; public static final long DEFAULT_NOTIFICATION_RATE_LIMIT_MILLIS = DateUtils.MINUTE_IN_MILLIS; private static final HashMap<String, Integer> TRANSPORT_NAMES = makeTransportToNameMap(); @VisibleForTesting public static final Intent CELLULAR_SETTINGS = new Intent().setComponent(new ComponentName( "com.android.settings", "com.android.settings.Settings$DataUsageSummaryActivity")); private static final int NOTIFY_TYPE_NONE = 0; private static final int NOTIFY_TYPE_NOTIFICATION = 1; private static final int NOTIFY_TYPE_TOAST = 2; @VisibleForTesting public static final int NOTIFY_TYPE_NONE = 0; public static final int NOTIFY_TYPE_NOTIFICATION = 1; public static final int NOTIFY_TYPE_TOAST = 2; private static SparseArray<String> sNotifyTypeNames = MessageUtils.findMessageNames( new Class[] { LingerMonitor.class }, new String[]{ "NOTIFY_TYPE_" }); private final Context mContext; private final NetworkNotificationManager mNotifier; private final int mDailyLimit; private final long mRateLimitMillis; private long mFirstNotificationMillis; private long mLastNotificationMillis; private int mNotificationCounter; /** Current notifications. Maps the netId we switched away from to the netId we switched to. */ private final SparseIntArray mNotifications = new SparseIntArray(); Loading @@ -72,9 +84,12 @@ public class LingerMonitor { /** Whether we ever notified that we switched away from a particular network. */ private final SparseBooleanArray mEverNotified = new SparseBooleanArray(); public LingerMonitor(Context context, NetworkNotificationManager notifier) { public LingerMonitor(Context context, NetworkNotificationManager notifier, int dailyLimit, long rateLimitMillis) { mContext = context; mNotifier = notifier; mDailyLimit = dailyLimit; mRateLimitMillis = rateLimitMillis; } private static HashMap<String, Integer> makeTransportToNameMap() { Loading Loading @@ -106,10 +121,11 @@ public class LingerMonitor { return mEverNotified.get(nai.network.netId, false); } private boolean isNotificationEnabled(NetworkAgentInfo fromNai, NetworkAgentInfo toNai) { @VisibleForTesting public boolean isNotificationEnabled(NetworkAgentInfo fromNai, NetworkAgentInfo toNai) { // TODO: Evaluate moving to CarrierConfigManager. String[] notifySwitches = mContext.getResources().getStringArray( com.android.internal.R.array.config_networkNotifySwitches); String[] notifySwitches = mContext.getResources().getStringArray(R.array.config_networkNotifySwitches); if (VDBG) { Log.d(TAG, "Notify on network switches: " + Arrays.toString(notifySwitches)); Loading @@ -122,8 +138,8 @@ public class LingerMonitor { Log.e(TAG, "Invalid network switch notification configuration: " + notifySwitch); continue; } int fromTransport = sTransportNames.get("TRANSPORT_" + transports[0]); int toTransport = sTransportNames.get("TRANSPORT_" + transports[1]); int fromTransport = TRANSPORT_NAMES.get("TRANSPORT_" + transports[0]); int toTransport = TRANSPORT_NAMES.get("TRANSPORT_" + transports[1]); if (hasTransport(fromNai, fromTransport) && hasTransport(toNai, toTransport)) { return true; } Loading @@ -133,12 +149,14 @@ public class LingerMonitor { } private void showNotification(NetworkAgentInfo fromNai, NetworkAgentInfo toNai) { PendingIntent pendingIntent = PendingIntent.getActivityAsUser( mContext, 0, CELLULAR_SETTINGS, PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT); mNotifier.showNotification(fromNai.network.netId, NotificationType.NETWORK_SWITCH, fromNai, toNai, pendingIntent, true); fromNai, toNai, createNotificationIntent(), true); } @VisibleForTesting protected PendingIntent createNotificationIntent() { return PendingIntent.getActivityAsUser(mContext, 0, CELLULAR_SETTINGS, PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT); } // Removes any notification that was put up as a result of switching to nai. Loading @@ -153,42 +171,38 @@ public class LingerMonitor { // Notify the user of a network switch using a notification or a toast. private void notify(NetworkAgentInfo fromNai, NetworkAgentInfo toNai, boolean forceToast) { boolean notify = false; int notifyType = mContext.getResources().getInteger( com.android.internal.R.integer.config_networkNotifySwitchType); int notifyType = mContext.getResources().getInteger(R.integer.config_networkNotifySwitchType); if (notifyType == NOTIFY_TYPE_NOTIFICATION && forceToast) { notifyType = NOTIFY_TYPE_TOAST; } if (VDBG) { Log.d(TAG, "Notify type: " + sNotifyTypeNames.get(notifyType, "" + notifyType)); } switch (notifyType) { case NOTIFY_TYPE_NONE: break; return; case NOTIFY_TYPE_NOTIFICATION: showNotification(fromNai, toNai); notify = true; break; case NOTIFY_TYPE_TOAST: mNotifier.showToast(fromNai, toNai); notify = true; break; default: Log.e(TAG, "Unknown notify type " + notifyType); return; } if (VDBG) { Log.d(TAG, "Notify type: " + sNotifyTypeNames.get(notifyType, "" + notifyType)); } if (notify) { if (DBG) { Log.d(TAG, "Notifying switch from=" + fromNai.name() + " to=" + toNai.name() + " type=" + sNotifyTypeNames.get(notifyType, "unknown(" + notifyType + ")")); } mNotifications.put(fromNai.network.netId, toNai.network.netId); mEverNotified.put(fromNai.network.netId, true); } } // The default network changed from fromNai to toNai due to a change in score. public void noteLingerDefaultNetwork(NetworkAgentInfo fromNai, NetworkAgentInfo toNai) { Loading Loading @@ -248,10 +262,13 @@ public class LingerMonitor { // unvalidated. if (fromNai.lastValidated) return; if (isNotificationEnabled(fromNai, toNai)) { if (!isNotificationEnabled(fromNai, toNai)) return; final long now = SystemClock.elapsedRealtime(); if (isRateLimited(now) || isAboveDailyLimit(now)) return; notify(fromNai, toNai, forceToast); } } public void noteDisconnect(NetworkAgentInfo nai) { mNotifications.delete(nai.network.netId); Loading @@ -259,4 +276,29 @@ public class LingerMonitor { maybeStopNotifying(nai); // No need to cancel notifications on nai: NetworkMonitor does that on disconnect. } private boolean isRateLimited(long now) { final long millisSinceLast = now - mLastNotificationMillis; if (millisSinceLast < mRateLimitMillis) { return true; } mLastNotificationMillis = now; return false; } private boolean isAboveDailyLimit(long now) { if (mFirstNotificationMillis == 0) { mFirstNotificationMillis = now; } final long millisSinceFirst = now - mFirstNotificationMillis; if (millisSinceFirst > DateUtils.DAY_IN_MILLIS) { mNotificationCounter = 0; mFirstNotificationMillis = 0; } if (mNotificationCounter >= mDailyLimit) { return true; } mNotificationCounter++; return false; } }
services/tests/servicestests/src/com/android/server/connectivity/LingerMonitorTest.java 0 → 100644 +349 −0 Original line number Diff line number Diff line /* * 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.server.connectivity; import android.app.PendingIntent; import android.content.Context; import android.content.res.Resources; import android.net.ConnectivityManager; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.net.NetworkMisc; import android.text.format.DateUtils; import com.android.internal.R; import com.android.server.ConnectivityService; import com.android.server.connectivity.NetworkNotificationManager; import com.android.server.connectivity.NetworkNotificationManager.NotificationType; import junit.framework.TestCase; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyBoolean; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.mockito.Mockito.reset; public class LingerMonitorTest extends TestCase { static final String CELLULAR = "CELLULAR"; static final String WIFI = "WIFI"; static final long LOW_RATE_LIMIT = DateUtils.MINUTE_IN_MILLIS; static final long HIGH_RATE_LIMIT = 0; static final int LOW_DAILY_LIMIT = 2; static final int HIGH_DAILY_LIMIT = 1000; LingerMonitor mMonitor; @Mock ConnectivityService mConnService; @Mock Context mCtx; @Mock NetworkMisc mMisc; @Mock NetworkNotificationManager mNotifier; @Mock Resources mResources; public void setUp() { MockitoAnnotations.initMocks(this); when(mCtx.getResources()).thenReturn(mResources); when(mCtx.getPackageName()).thenReturn("com.android.server.connectivity"); when(mConnService.createNetworkMonitor(any(), any(), any(), any())).thenReturn(null); mMonitor = new TestableLingerMonitor(mCtx, mNotifier, HIGH_DAILY_LIMIT, HIGH_RATE_LIMIT); } public void testTransitions() { setNotificationSwitch(transition(WIFI, CELLULAR)); NetworkAgentInfo nai1 = wifiNai(100); NetworkAgentInfo nai2 = cellNai(101); assertTrue(mMonitor.isNotificationEnabled(nai1, nai2)); assertFalse(mMonitor.isNotificationEnabled(nai2, nai1)); } public void testNotificationOnLinger() { setNotificationSwitch(transition(WIFI, CELLULAR)); setNotificationType(LingerMonitor.NOTIFY_TYPE_NOTIFICATION); NetworkAgentInfo from = wifiNai(100); NetworkAgentInfo to = cellNai(101); mMonitor.noteLingerDefaultNetwork(from, to); verifyNotification(from, to); } public void testToastOnLinger() { setNotificationSwitch(transition(WIFI, CELLULAR)); setNotificationType(LingerMonitor.NOTIFY_TYPE_TOAST); NetworkAgentInfo from = wifiNai(100); NetworkAgentInfo to = cellNai(101); mMonitor.noteLingerDefaultNetwork(from, to); verifyToast(from, to); } public void testNotificationClearedAfterDisconnect() { setNotificationSwitch(transition(WIFI, CELLULAR)); setNotificationType(LingerMonitor.NOTIFY_TYPE_NOTIFICATION); NetworkAgentInfo from = wifiNai(100); NetworkAgentInfo to = cellNai(101); mMonitor.noteLingerDefaultNetwork(from, to); verifyNotification(from, to); mMonitor.noteDisconnect(to); verify(mNotifier, times(1)).clearNotification(100); } public void testNotificationClearedAfterSwitchingBack() { setNotificationSwitch(transition(WIFI, CELLULAR)); setNotificationType(LingerMonitor.NOTIFY_TYPE_NOTIFICATION); NetworkAgentInfo from = wifiNai(100); NetworkAgentInfo to = cellNai(101); mMonitor.noteLingerDefaultNetwork(from, to); verifyNotification(from, to); mMonitor.noteLingerDefaultNetwork(to, from); verify(mNotifier, times(1)).clearNotification(100); } public void testUniqueToast() { setNotificationSwitch(transition(WIFI, CELLULAR)); setNotificationType(LingerMonitor.NOTIFY_TYPE_TOAST); NetworkAgentInfo from = wifiNai(100); NetworkAgentInfo to = cellNai(101); mMonitor.noteLingerDefaultNetwork(from, to); verifyToast(from, to); mMonitor.noteLingerDefaultNetwork(to, from); verify(mNotifier, times(1)).clearNotification(100); reset(mNotifier); mMonitor.noteLingerDefaultNetwork(from, to); verifyNoNotifications(); } public void testMultipleNotifications() { setNotificationSwitch(transition(WIFI, CELLULAR)); setNotificationType(LingerMonitor.NOTIFY_TYPE_NOTIFICATION); NetworkAgentInfo wifi1 = wifiNai(100); NetworkAgentInfo wifi2 = wifiNai(101); NetworkAgentInfo cell = cellNai(102); mMonitor.noteLingerDefaultNetwork(wifi1, cell); verifyNotification(wifi1, cell); mMonitor.noteLingerDefaultNetwork(cell, wifi2); verify(mNotifier, times(1)).clearNotification(100); reset(mNotifier); mMonitor.noteLingerDefaultNetwork(wifi2, cell); verifyNotification(wifi2, cell); } public void testRateLimiting() throws InterruptedException { mMonitor = new TestableLingerMonitor(mCtx, mNotifier, HIGH_DAILY_LIMIT, LOW_RATE_LIMIT); setNotificationSwitch(transition(WIFI, CELLULAR)); setNotificationType(LingerMonitor.NOTIFY_TYPE_NOTIFICATION); NetworkAgentInfo wifi1 = wifiNai(100); NetworkAgentInfo wifi2 = wifiNai(101); NetworkAgentInfo wifi3 = wifiNai(102); NetworkAgentInfo cell = cellNai(103); mMonitor.noteLingerDefaultNetwork(wifi1, cell); verifyNotification(wifi1, cell); reset(mNotifier); Thread.sleep(50); mMonitor.noteLingerDefaultNetwork(cell, wifi2); mMonitor.noteLingerDefaultNetwork(wifi2, cell); verifyNoNotifications(); Thread.sleep(50); mMonitor.noteLingerDefaultNetwork(cell, wifi3); mMonitor.noteLingerDefaultNetwork(wifi3, cell); verifyNoNotifications(); } public void testDailyLimiting() throws InterruptedException { mMonitor = new TestableLingerMonitor(mCtx, mNotifier, LOW_DAILY_LIMIT, HIGH_RATE_LIMIT); setNotificationSwitch(transition(WIFI, CELLULAR)); setNotificationType(LingerMonitor.NOTIFY_TYPE_NOTIFICATION); NetworkAgentInfo wifi1 = wifiNai(100); NetworkAgentInfo wifi2 = wifiNai(101); NetworkAgentInfo wifi3 = wifiNai(102); NetworkAgentInfo cell = cellNai(103); mMonitor.noteLingerDefaultNetwork(wifi1, cell); verifyNotification(wifi1, cell); reset(mNotifier); Thread.sleep(50); mMonitor.noteLingerDefaultNetwork(cell, wifi2); mMonitor.noteLingerDefaultNetwork(wifi2, cell); verifyNotification(wifi2, cell); reset(mNotifier); Thread.sleep(50); mMonitor.noteLingerDefaultNetwork(cell, wifi3); mMonitor.noteLingerDefaultNetwork(wifi3, cell); verifyNoNotifications(); } public void testUniqueNotification() { setNotificationSwitch(transition(WIFI, CELLULAR)); setNotificationType(LingerMonitor.NOTIFY_TYPE_NOTIFICATION); NetworkAgentInfo from = wifiNai(100); NetworkAgentInfo to = cellNai(101); mMonitor.noteLingerDefaultNetwork(from, to); verifyNotification(from, to); mMonitor.noteLingerDefaultNetwork(to, from); verify(mNotifier, times(1)).clearNotification(100); mMonitor.noteLingerDefaultNetwork(from, to); verifyNotification(from, to); } public void testIgnoreNeverValidatedNetworks() { setNotificationType(LingerMonitor.NOTIFY_TYPE_TOAST); setNotificationSwitch(transition(WIFI, CELLULAR)); NetworkAgentInfo from = wifiNai(100); NetworkAgentInfo to = cellNai(101); from.everValidated = false; mMonitor.noteLingerDefaultNetwork(from, to); verifyNoNotifications(); } public void testIgnoreCurrentlyValidatedNetworks() { setNotificationType(LingerMonitor.NOTIFY_TYPE_TOAST); setNotificationSwitch(transition(WIFI, CELLULAR)); NetworkAgentInfo from = wifiNai(100); NetworkAgentInfo to = cellNai(101); from.lastValidated = true; mMonitor.noteLingerDefaultNetwork(from, to); verifyNoNotifications(); } public void testNoNotificationType() { setNotificationType(LingerMonitor.NOTIFY_TYPE_TOAST); setNotificationSwitch(); NetworkAgentInfo from = wifiNai(100); NetworkAgentInfo to = cellNai(101); mMonitor.noteLingerDefaultNetwork(from, to); verifyNoNotifications(); } public void testNoTransitionToNotify() { setNotificationType(LingerMonitor.NOTIFY_TYPE_NONE); setNotificationSwitch(transition(WIFI, CELLULAR)); NetworkAgentInfo from = wifiNai(100); NetworkAgentInfo to = cellNai(101); mMonitor.noteLingerDefaultNetwork(from, to); verifyNoNotifications(); } public void testDifferentTransitionToNotify() { setNotificationType(LingerMonitor.NOTIFY_TYPE_TOAST); setNotificationSwitch(transition(CELLULAR, WIFI)); NetworkAgentInfo from = wifiNai(100); NetworkAgentInfo to = cellNai(101); mMonitor.noteLingerDefaultNetwork(from, to); verifyNoNotifications(); } void setNotificationSwitch(String... transitions) { when(mResources.getStringArray(R.array.config_networkNotifySwitches)) .thenReturn(transitions); } String transition(String from, String to) { return from + "-" + to; } void setNotificationType(int type) { when(mResources.getInteger(R.integer.config_networkNotifySwitchType)).thenReturn(type); } void verifyNoToast() { verify(mNotifier, never()).showToast(any(), any()); } void verifyNoNotification() { verify(mNotifier, never()) .showNotification(anyInt(), any(), any(), any(), any(), anyBoolean()); } void verifyNoNotifications() { verifyNoToast(); verifyNoNotification(); } void verifyToast(NetworkAgentInfo from, NetworkAgentInfo to) { verifyNoNotification(); verify(mNotifier, times(1)).showToast(from, to); } void verifyNotification(NetworkAgentInfo from, NetworkAgentInfo to) { verifyNoToast(); verify(mNotifier, times(1)).showNotification(eq(from.network.netId), eq(NotificationType.NETWORK_SWITCH), eq(from), eq(to), any(), eq(true)); } NetworkAgentInfo nai(int netId, int transport, int networkType, String networkTypeName) { NetworkInfo info = new NetworkInfo(networkType, 0, networkTypeName, ""); NetworkCapabilities caps = new NetworkCapabilities(); caps.addCapability(0); caps.addTransportType(transport); NetworkAgentInfo nai = new NetworkAgentInfo(null, null, new Network(netId), info, null, caps, 50, mCtx, null, mMisc, null, mConnService); nai.everValidated = true; return nai; } NetworkAgentInfo wifiNai(int netId) { return nai(netId, NetworkCapabilities.TRANSPORT_WIFI, ConnectivityManager.TYPE_WIFI, WIFI); } NetworkAgentInfo cellNai(int netId) { return nai(netId, NetworkCapabilities.TRANSPORT_CELLULAR, ConnectivityManager.TYPE_MOBILE, CELLULAR); } public static class TestableLingerMonitor extends LingerMonitor { public TestableLingerMonitor(Context c, NetworkNotificationManager n, int l, long r) { super(c, n, l, r); } @Override protected PendingIntent createNotificationIntent() { return null; } } }