[TNU06] Add roaming notification
Warn user of potential data charges if the backhaul is
cellular and user is roaming.
Bug: 145629001
Test: atest TetheringTests
Change-Id: I74b4f87c2f6aad09e05d3f2a779f880396885953
diff --git a/Tethering/res/values-mcc310-mnc004/config.xml b/Tethering/res/values-mcc310-mnc004/config.xml
index 8c627d5..5c5be04 100644
--- a/Tethering/res/values-mcc310-mnc004/config.xml
+++ b/Tethering/res/values-mcc310-mnc004/config.xml
@@ -17,4 +17,7 @@
<!-- Delay(millisecond) to show no upstream notification after there's no Backhaul. Set delay to
"0" for disable this feature. -->
<integer name="delay_to_show_no_upstream_after_no_backhaul">5000</integer>
+
+ <!-- Config for showing upstream roaming notification. -->
+ <bool name="config_upstream_roaming_notification">true</bool>
</resources>
\ No newline at end of file
diff --git a/Tethering/res/values-mcc311-mnc480/config.xml b/Tethering/res/values-mcc311-mnc480/config.xml
index 8c627d5..5c5be04 100644
--- a/Tethering/res/values-mcc311-mnc480/config.xml
+++ b/Tethering/res/values-mcc311-mnc480/config.xml
@@ -17,4 +17,7 @@
<!-- Delay(millisecond) to show no upstream notification after there's no Backhaul. Set delay to
"0" for disable this feature. -->
<integer name="delay_to_show_no_upstream_after_no_backhaul">5000</integer>
+
+ <!-- Config for showing upstream roaming notification. -->
+ <bool name="config_upstream_roaming_notification">true</bool>
</resources>
\ No newline at end of file
diff --git a/Tethering/res/values/config.xml b/Tethering/res/values/config.xml
index 780a015..aed5ab8 100644
--- a/Tethering/res/values/config.xml
+++ b/Tethering/res/values/config.xml
@@ -206,4 +206,9 @@
<!-- Delay(millisecond) to show no upstream notification after there's no Backhaul. Set delay to
"-1" for disable this feature. -->
<integer name="delay_to_show_no_upstream_after_no_backhaul">-1</integer>
+
+ <!-- Cellular roaming notification is shown when upstream is cellular network and in roaming
+ state. -->
+ <!-- Config for showing upstream roaming notification. -->
+ <bool name="config_upstream_roaming_notification">false</bool>
</resources>
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 14d2886..97b1946 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -81,6 +81,7 @@
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network;
+import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.TetherStatesParcel;
import android.net.TetheredClient;
@@ -1476,7 +1477,7 @@
if (mTetherUpstream != newUpstream) {
mTetherUpstream = newUpstream;
mUpstreamNetworkMonitor.setCurrentUpstream(mTetherUpstream);
- reportUpstreamChanged(mTetherUpstream);
+ reportUpstreamChanged(ns);
}
}
@@ -1598,7 +1599,8 @@
}
}
- private void handleUpstreamNetworkMonitorCallback(int arg1, Object o) {
+ @VisibleForTesting
+ void handleUpstreamNetworkMonitorCallback(int arg1, Object o) {
if (arg1 == UpstreamNetworkMonitor.NOTIFY_LOCAL_PREFIXES) {
mOffload.sendOffloadExemptPrefixes((Set<IpPrefix>) o);
return;
@@ -1624,6 +1626,9 @@
switch (arg1) {
case UpstreamNetworkMonitor.EVENT_ON_CAPABILITIES:
+ if (ns.network.equals(mTetherUpstream)) {
+ mNotificationUpdater.onUpstreamCapabilitiesChanged(ns.networkCapabilities);
+ }
handleNewUpstreamNetworkState(ns);
break;
case UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES:
@@ -2009,8 +2014,10 @@
});
}
- private void reportUpstreamChanged(Network network) {
+ private void reportUpstreamChanged(UpstreamNetworkState ns) {
final int length = mTetheringEventCallbacks.beginBroadcast();
+ final Network network = (ns != null) ? ns.network : null;
+ final NetworkCapabilities capabilities = (ns != null) ? ns.networkCapabilities : null;
try {
for (int i = 0; i < length; i++) {
try {
@@ -2022,7 +2029,9 @@
} finally {
mTetheringEventCallbacks.finishBroadcast();
}
- mNotificationUpdater.onUpstreamNetworkChanged(network);
+ // Need to notify capabilities change after upstream network changed because new network's
+ // capabilities should be checked every time.
+ mNotificationUpdater.onUpstreamCapabilitiesChanged(capabilities);
}
private void reportConfigurationChanged(TetheringConfigurationParcel config) {
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java b/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java
index de2f90e..f490cc4 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java
@@ -16,6 +16,7 @@
package com.android.networkstack.tethering;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
import static android.net.TetheringManager.TETHERING_BLUETOOTH;
import static android.net.TetheringManager.TETHERING_USB;
import static android.net.TetheringManager.TETHERING_WIFI;
@@ -30,7 +31,7 @@
import android.content.Intent;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.net.Network;
+import android.net.NetworkCapabilities;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -50,6 +51,9 @@
import com.android.internal.annotations.VisibleForTesting;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* A class to display tethering-related notifications.
*
@@ -82,6 +86,9 @@
// Id to update and cancel no upstream notification. Must be unique within the tethering app.
@VisibleForTesting
static final int NO_UPSTREAM_NOTIFICATION_ID = 1002;
+ // Id to update and cancel roaming notification. Must be unique within the tethering app.
+ @VisibleForTesting
+ static final int ROAMING_NOTIFICATION_ID = 1003;
@VisibleForTesting
static final int NO_ICON_ID = 0;
@VisibleForTesting
@@ -95,13 +102,14 @@
private final Handler mHandler;
// WARNING : the constructor is called on a different thread. Thread safety therefore
- // relies on these values being initialized to 0 or false, and not any other value. If you need
- // to change this, you will need to change the thread where the constructor is invoked,
- // or to introduce synchronization.
+ // relies on these values being initialized to 0, false or null, and not any other value. If you
+ // need to change this, you will need to change the thread where the constructor is invoked, or
+ // to introduce synchronization.
// Downstream type is one of ConnectivityManager.TETHERING_* constants, 0 1 or 2.
// This value has to be made 1 2 and 4, and OR'd with the others.
private int mDownstreamTypesMask = DOWNSTREAM_NONE;
private boolean mNoUpstream = false;
+ private boolean mRoaming = false;
// WARNING : this value is not able to being initialized to 0 and must have volatile because
// telephony service is not guaranteed that is up before tethering service starts. If telephony
@@ -110,7 +118,13 @@
// INVALID_SUBSCRIPTION_ID.
private volatile int mActiveDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
- @IntDef({ENABLE_NOTIFICATION_ID, RESTRICTED_NOTIFICATION_ID, NO_UPSTREAM_NOTIFICATION_ID})
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {
+ ENABLE_NOTIFICATION_ID,
+ RESTRICTED_NOTIFICATION_ID,
+ NO_UPSTREAM_NOTIFICATION_ID,
+ ROAMING_NOTIFICATION_ID
+ })
@interface NotificationId {}
private static final class MccMncOverrideInfo {
@@ -160,26 +174,22 @@
/** Called when downstream has changed */
public void onDownstreamChanged(@IntRange(from = 0, to = 7) final int downstreamTypesMask) {
- if (mDownstreamTypesMask == downstreamTypesMask) return;
- mDownstreamTypesMask = downstreamTypesMask;
- updateEnableNotification();
- updateNoUpstreamNotification();
+ updateActiveNotifications(
+ mActiveDataSubId, downstreamTypesMask, mNoUpstream, mRoaming);
}
/** Called when active data subscription id changed */
public void onActiveDataSubscriptionIdChanged(final int subId) {
- if (mActiveDataSubId == subId) return;
- mActiveDataSubId = subId;
- updateEnableNotification();
- updateNoUpstreamNotification();
+ updateActiveNotifications(subId, mDownstreamTypesMask, mNoUpstream, mRoaming);
}
- /** Called when upstream network changed */
- public void onUpstreamNetworkChanged(@Nullable final Network network) {
- final boolean isNoUpstream = (network == null);
- if (mNoUpstream == isNoUpstream) return;
- mNoUpstream = isNoUpstream;
- updateNoUpstreamNotification();
+ /** Called when upstream network capabilities changed */
+ public void onUpstreamCapabilitiesChanged(@Nullable final NetworkCapabilities capabilities) {
+ final boolean isNoUpstream = (capabilities == null);
+ final boolean isRoaming = capabilities != null
+ && !capabilities.hasCapability(NET_CAPABILITY_NOT_ROAMING);
+ updateActiveNotifications(
+ mActiveDataSubId, mDownstreamTypesMask, isNoUpstream, isRoaming);
}
@NonNull
@@ -208,6 +218,25 @@
return res;
}
+ private void updateActiveNotifications(final int subId, final int downstreamTypes,
+ final boolean noUpstream, final boolean isRoaming) {
+ final boolean tetheringActiveChanged =
+ (downstreamTypes == DOWNSTREAM_NONE) != (mDownstreamTypesMask == DOWNSTREAM_NONE);
+ final boolean subIdChanged = subId != mActiveDataSubId;
+ final boolean downstreamChanged = downstreamTypes != mDownstreamTypesMask;
+ final boolean upstreamChanged = noUpstream != mNoUpstream;
+ final boolean roamingChanged = isRoaming != mRoaming;
+ final boolean updateAll = tetheringActiveChanged || subIdChanged;
+ mActiveDataSubId = subId;
+ mDownstreamTypesMask = downstreamTypes;
+ mNoUpstream = noUpstream;
+ mRoaming = isRoaming;
+
+ if (updateAll || downstreamChanged) updateEnableNotification();
+ if (updateAll || upstreamChanged) updateNoUpstreamNotification();
+ if (updateAll || roamingChanged) updateRoamingNotification();
+ }
+
private void updateEnableNotification() {
final boolean tetheringInactive = mDownstreamTypesMask == DOWNSTREAM_NONE;
@@ -219,14 +248,20 @@
private void updateNoUpstreamNotification() {
final boolean tetheringInactive = mDownstreamTypesMask == DOWNSTREAM_NONE;
- if (tetheringInactive
- || !mNoUpstream
- || setupNoUpstreamNotification() == NO_NOTIFY) {
+ if (tetheringInactive || !mNoUpstream || setupNoUpstreamNotification() == NO_NOTIFY) {
clearNotification(NO_UPSTREAM_NOTIFICATION_ID);
mHandler.removeMessages(EVENT_SHOW_NO_UPSTREAM);
}
}
+ private void updateRoamingNotification() {
+ final boolean tetheringInactive = mDownstreamTypesMask == DOWNSTREAM_NONE;
+
+ if (tetheringInactive || !mRoaming || setupRoamingNotification() == NO_NOTIFY) {
+ clearNotification(ROAMING_NOTIFICATION_ID);
+ }
+ }
+
@VisibleForTesting
void tetheringRestrictionLifted() {
clearNotification(RESTRICTED_NOTIFICATION_ID);
@@ -333,6 +368,29 @@
return icons;
}
+ private boolean setupRoamingNotification() {
+ final Resources res = getResourcesForSubId(mContext, mActiveDataSubId);
+ final boolean upstreamRoamingNotification =
+ res.getBoolean(R.bool.config_upstream_roaming_notification);
+
+ if (!upstreamRoamingNotification) return NO_NOTIFY;
+
+ final String title = res.getString(R.string.upstream_roaming_notification_title);
+ final String message = res.getString(R.string.upstream_roaming_notification_message);
+ if (isEmpty(title) || isEmpty(message)) return NO_NOTIFY;
+
+ final PendingIntent pi = PendingIntent.getActivity(
+ mContext.createContextAsUser(UserHandle.CURRENT, 0 /* flags */),
+ 0 /* requestCode */,
+ new Intent(Settings.ACTION_TETHER_SETTINGS),
+ Intent.FLAG_ACTIVITY_NEW_TASK,
+ null /* options */);
+
+ showNotification(R.drawable.stat_sys_tether_general, title, message,
+ ROAMING_NOTIFICATION_ID, pi, new Action[0]);
+ return NOTIFY_DONE;
+ }
+
private boolean setupNoUpstreamNotification() {
final Resources res = getResourcesForSubId(mContext, mActiveDataSubId);
final int delayToShowUpstreamNotification =
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt
index 294bf1b..04f31a7 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt
@@ -23,10 +23,11 @@
import android.net.ConnectivityManager.TETHERING_BLUETOOTH
import android.net.ConnectivityManager.TETHERING_USB
import android.net.ConnectivityManager.TETHERING_WIFI
-import android.net.Network
import android.os.Handler
import android.os.HandlerThread
import android.os.Looper
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING
import android.os.UserHandle
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import android.telephony.TelephonyManager
@@ -39,6 +40,7 @@
import com.android.networkstack.tethering.TetheringNotificationUpdater.EVENT_SHOW_NO_UPSTREAM
import com.android.networkstack.tethering.TetheringNotificationUpdater.NO_UPSTREAM_NOTIFICATION_ID
import com.android.networkstack.tethering.TetheringNotificationUpdater.RESTRICTED_NOTIFICATION_ID
+import com.android.networkstack.tethering.TetheringNotificationUpdater.ROAMING_NOTIFICATION_ID
import com.android.networkstack.tethering.TetheringNotificationUpdater.VERIZON_CARRIER_ID
import com.android.testutils.waitForIdle
import org.junit.After
@@ -75,6 +77,8 @@
const val TEST_NO_UPSTREAM_TITLE = "Hotspot has no internet access"
const val TEST_NO_UPSTREAM_MESSAGE = "Device cannot connect to internet."
const val TEST_NO_UPSTREAM_BUTTON = "Turn off hotspot"
+const val TEST_ROAMING_TITLE = "Hotspot is on"
+const val TEST_ROAMING_MESSAGE = "Additional charges may apply while roaming."
@RunWith(AndroidJUnit4::class)
@SmallTest
@@ -98,6 +102,11 @@
"WIFI|BT;android.test:drawable/general", "WIFI|USB;android.test:drawable/general",
"USB|BT;android.test:drawable/general", "WIFI|USB|BT;android.test:drawable/general")
+ private val ROAMING_CAPABILITIES = NetworkCapabilities()
+ private val HOME_CAPABILITIES = NetworkCapabilities().addCapability(NET_CAPABILITY_NOT_ROAMING)
+ private val NOTIFICATION_ICON_ID = R.drawable.stat_sys_tether_general
+ private val TIMEOUT_MS = 500L
+
private inner class TestContext(c: Context) : BroadcastInterceptingContext(c) {
override fun createContextAsUser(user: UserHandle, flags: Int) =
if (user == UserHandle.ALL) mockContext else this
@@ -123,6 +132,8 @@
.getStringArray(R.array.tethering_notification_icons)
doReturn(5).`when`(testResources)
.getInteger(R.integer.delay_to_show_no_upstream_after_no_backhaul)
+ doReturn(true).`when`(testResources)
+ .getBoolean(R.bool.config_upstream_roaming_notification)
doReturn(TITLE).`when`(defaultResources).getString(R.string.tethering_notification_title)
doReturn(MESSAGE).`when`(defaultResources)
.getString(R.string.tethering_notification_message)
@@ -135,6 +146,10 @@
.getString(R.string.no_upstream_notification_message)
doReturn(TEST_NO_UPSTREAM_BUTTON).`when`(testResources)
.getString(R.string.no_upstream_notification_disable_button)
+ doReturn(TEST_ROAMING_TITLE).`when`(testResources)
+ .getString(R.string.upstream_roaming_notification_title)
+ doReturn(TEST_ROAMING_MESSAGE).`when`(testResources)
+ .getString(R.string.upstream_roaming_notification_message)
doReturn(USB_ICON_ID).`when`(defaultResources)
.getIdentifier(eq("android.test:drawable/usb"), any(), any())
doReturn(BT_ICON_ID).`when`(defaultResources)
@@ -176,41 +191,25 @@
assertEquals(iconId, notification.smallIcon.resId)
assertEquals(title, notification.title())
assertEquals(text, notification.text())
- }
- private fun verifyNotificationCancelled(id: Int) =
- verify(notificationManager, times(1)).cancel(any(), eq(id))
-
- private val tetheringActiveNotifications =
- listOf(NO_UPSTREAM_NOTIFICATION_ID, ENABLE_NOTIFICATION_ID)
-
- private fun verifyCancelAllTetheringActiveNotifications() {
- tetheringActiveNotifications.forEach {
- verifyNotificationCancelled(it)
- }
reset(notificationManager)
}
- private fun verifyOnlyTetheringActiveNotification(
- notifyId: Int,
- iconId: Int,
- title: String,
- text: String
+ private fun verifyNotificationCancelled(
+ notificationIds: List<Int>,
+ resetAfterVerified: Boolean = true
) {
- tetheringActiveNotifications.forEach {
- when (it) {
- notifyId -> verifyNotification(iconId, title, text, notifyId)
- else -> verifyNotificationCancelled(it)
- }
+ notificationIds.forEach {
+ verify(notificationManager, times(1)).cancel(any(), eq(it))
}
- reset(notificationManager)
+ if (resetAfterVerified) reset(notificationManager)
}
@Test
fun testNotificationWithDownstreamChanged() {
// Wifi downstream. No notification.
notificationUpdater.onDownstreamChanged(WIFI_MASK)
- verifyCancelAllTetheringActiveNotifications()
+ verifyNotificationCancelled(listOf(ENABLE_NOTIFICATION_ID))
// Same downstream changed. Nothing happened.
notificationUpdater.onDownstreamChanged(WIFI_MASK)
@@ -218,23 +217,23 @@
// Wifi and usb downstreams. Show enable notification
notificationUpdater.onDownstreamChanged(WIFI_MASK or USB_MASK)
- verifyOnlyTetheringActiveNotification(
- ENABLE_NOTIFICATION_ID, GENERAL_ICON_ID, TITLE, MESSAGE)
+ verifyNotification(GENERAL_ICON_ID, TITLE, MESSAGE, ENABLE_NOTIFICATION_ID)
// Usb downstream. Still show enable notification.
notificationUpdater.onDownstreamChanged(USB_MASK)
- verifyOnlyTetheringActiveNotification(ENABLE_NOTIFICATION_ID, USB_ICON_ID, TITLE, MESSAGE)
+ verifyNotification(USB_ICON_ID, TITLE, MESSAGE, ENABLE_NOTIFICATION_ID)
// No downstream. No notification.
notificationUpdater.onDownstreamChanged(DOWNSTREAM_NONE)
- verifyCancelAllTetheringActiveNotifications()
+ verifyNotificationCancelled(listOf(ENABLE_NOTIFICATION_ID, NO_UPSTREAM_NOTIFICATION_ID,
+ ROAMING_NOTIFICATION_ID))
}
@Test
fun testNotificationWithActiveDataSubscriptionIdChanged() {
// Usb downstream. Showed enable notification with default resource.
notificationUpdater.onDownstreamChanged(USB_MASK)
- verifyOnlyTetheringActiveNotification(ENABLE_NOTIFICATION_ID, USB_ICON_ID, TITLE, MESSAGE)
+ verifyNotification(USB_ICON_ID, TITLE, MESSAGE, ENABLE_NOTIFICATION_ID)
// Same subId changed. Nothing happened.
notificationUpdater.onActiveDataSubscriptionIdChanged(INVALID_SUBSCRIPTION_ID)
@@ -242,16 +241,17 @@
// Set test sub id. Clear notification with test resource.
notificationUpdater.onActiveDataSubscriptionIdChanged(TEST_SUBID)
- verifyCancelAllTetheringActiveNotifications()
+ verifyNotificationCancelled(listOf(ENABLE_NOTIFICATION_ID, NO_UPSTREAM_NOTIFICATION_ID,
+ ROAMING_NOTIFICATION_ID))
// Wifi downstream. Show enable notification with test resource.
notificationUpdater.onDownstreamChanged(WIFI_MASK)
- verifyOnlyTetheringActiveNotification(
- ENABLE_NOTIFICATION_ID, WIFI_ICON_ID, TEST_TITLE, TEST_MESSAGE)
+ verifyNotification(WIFI_ICON_ID, TEST_TITLE, TEST_MESSAGE, ENABLE_NOTIFICATION_ID)
// No downstream. No notification.
notificationUpdater.onDownstreamChanged(DOWNSTREAM_NONE)
- verifyCancelAllTetheringActiveNotifications()
+ verifyNotificationCancelled(listOf(ENABLE_NOTIFICATION_ID, NO_UPSTREAM_NOTIFICATION_ID,
+ ROAMING_NOTIFICATION_ID))
}
private fun assertIconNumbers(number: Int, configs: Array<String?>) {
@@ -305,24 +305,21 @@
// User restrictions on. Show restricted notification.
notificationUpdater.notifyTetheringDisabledByRestriction()
- verifyNotification(R.drawable.stat_sys_tether_general, title, message,
- RESTRICTED_NOTIFICATION_ID)
- reset(notificationManager)
+ verifyNotification(NOTIFICATION_ICON_ID, title, message, RESTRICTED_NOTIFICATION_ID)
// User restrictions off. Clear notification.
notificationUpdater.tetheringRestrictionLifted()
- verifyNotificationCancelled(RESTRICTED_NOTIFICATION_ID)
- reset(notificationManager)
+ verifyNotificationCancelled(listOf(RESTRICTED_NOTIFICATION_ID))
// Set test sub id. No notification.
notificationUpdater.onActiveDataSubscriptionIdChanged(TEST_SUBID)
- verifyCancelAllTetheringActiveNotifications()
+ verifyNotificationCancelled(listOf(ENABLE_NOTIFICATION_ID, NO_UPSTREAM_NOTIFICATION_ID,
+ ROAMING_NOTIFICATION_ID))
// User restrictions on again. Show restricted notification with test resource.
notificationUpdater.notifyTetheringDisabledByRestriction()
- verifyNotification(R.drawable.stat_sys_tether_general, disallowTitle, disallowMessage,
+ verifyNotification(NOTIFICATION_ICON_ID, disallowTitle, disallowMessage,
RESTRICTED_NOTIFICATION_ID)
- reset(notificationManager)
}
val MAX_BACKOFF_MS = 200L
@@ -359,51 +356,53 @@
}
@Test
- fun testNotificationWithUpstreamNetworkChanged() {
+ fun testNotificationWithUpstreamCapabilitiesChanged_NoUpstream() {
// Set test sub id. No notification.
notificationUpdater.onActiveDataSubscriptionIdChanged(TEST_SUBID)
- verifyCancelAllTetheringActiveNotifications()
+ verifyNotificationCancelled(listOf(ENABLE_NOTIFICATION_ID, NO_UPSTREAM_NOTIFICATION_ID,
+ ROAMING_NOTIFICATION_ID))
// Wifi downstream. Show enable notification with test resource.
notificationUpdater.onDownstreamChanged(WIFI_MASK)
- verifyOnlyTetheringActiveNotification(
- ENABLE_NOTIFICATION_ID, WIFI_ICON_ID, TEST_TITLE, TEST_MESSAGE)
+ verifyNotification(WIFI_ICON_ID, TEST_TITLE, TEST_MESSAGE, ENABLE_NOTIFICATION_ID)
// There is no upstream. Show no upstream notification.
- notificationUpdater.onUpstreamNetworkChanged(null)
- notificationUpdater.handler.waitForDelayedMessage(EVENT_SHOW_NO_UPSTREAM, 500L)
- verifyNotification(R.drawable.stat_sys_tether_general, TEST_NO_UPSTREAM_TITLE,
- TEST_NO_UPSTREAM_MESSAGE, NO_UPSTREAM_NOTIFICATION_ID)
- reset(notificationManager)
+ notificationUpdater.onUpstreamCapabilitiesChanged(null)
+ notificationUpdater.handler.waitForDelayedMessage(EVENT_SHOW_NO_UPSTREAM, TIMEOUT_MS)
+ verifyNotification(NOTIFICATION_ICON_ID, TEST_NO_UPSTREAM_TITLE, TEST_NO_UPSTREAM_MESSAGE,
+ NO_UPSTREAM_NOTIFICATION_ID)
- // Same upstream network changed. Nothing happened.
- notificationUpdater.onUpstreamNetworkChanged(null)
+ // Same capabilities changed. Nothing happened.
+ notificationUpdater.onUpstreamCapabilitiesChanged(null)
verifyZeroInteractions(notificationManager)
// Upstream come back. Clear no upstream notification.
- notificationUpdater.onUpstreamNetworkChanged(Network(1000))
- verifyNotificationCancelled(NO_UPSTREAM_NOTIFICATION_ID)
- reset(notificationManager)
+ notificationUpdater.onUpstreamCapabilitiesChanged(HOME_CAPABILITIES)
+ verifyNotificationCancelled(listOf(NO_UPSTREAM_NOTIFICATION_ID))
// No upstream again. Show no upstream notification.
- notificationUpdater.onUpstreamNetworkChanged(null)
- notificationUpdater.handler.waitForDelayedMessage(EVENT_SHOW_NO_UPSTREAM, 500L)
- verifyNotification(R.drawable.stat_sys_tether_general, TEST_NO_UPSTREAM_TITLE,
- TEST_NO_UPSTREAM_MESSAGE, NO_UPSTREAM_NOTIFICATION_ID)
- reset(notificationManager)
+ notificationUpdater.onUpstreamCapabilitiesChanged(null)
+ notificationUpdater.handler.waitForDelayedMessage(EVENT_SHOW_NO_UPSTREAM, TIMEOUT_MS)
+ verifyNotification(NOTIFICATION_ICON_ID, TEST_NO_UPSTREAM_TITLE, TEST_NO_UPSTREAM_MESSAGE,
+ NO_UPSTREAM_NOTIFICATION_ID)
// No downstream. No notification.
notificationUpdater.onDownstreamChanged(DOWNSTREAM_NONE)
- verifyCancelAllTetheringActiveNotifications()
+ verifyNotificationCancelled(listOf(ENABLE_NOTIFICATION_ID, NO_UPSTREAM_NOTIFICATION_ID,
+ ROAMING_NOTIFICATION_ID))
- // Set R.integer.delay_to_show_no_upstream_after_no_backhaul to 0 and have wifi downstream
- // again. Show enable notification only.
+ // Put up enable notification with wifi downstream and home capabilities.
+ notificationUpdater.onDownstreamChanged(WIFI_MASK)
+ notificationUpdater.onUpstreamCapabilitiesChanged(HOME_CAPABILITIES)
+ verifyNotification(WIFI_ICON_ID, TEST_TITLE, TEST_MESSAGE, ENABLE_NOTIFICATION_ID)
+
+ // Set R.integer.delay_to_show_no_upstream_after_no_backhaul to -1 and change to no upstream
+ // again. Don't put up no upstream notification.
doReturn(-1).`when`(testResources)
.getInteger(R.integer.delay_to_show_no_upstream_after_no_backhaul)
- notificationUpdater.onDownstreamChanged(WIFI_MASK)
- notificationUpdater.handler.waitForDelayedMessage(EVENT_SHOW_NO_UPSTREAM, 500L)
- verifyOnlyTetheringActiveNotification(
- ENABLE_NOTIFICATION_ID, WIFI_ICON_ID, TEST_TITLE, TEST_MESSAGE)
+ notificationUpdater.onUpstreamCapabilitiesChanged(null)
+ notificationUpdater.handler.waitForDelayedMessage(EVENT_SHOW_NO_UPSTREAM, TIMEOUT_MS)
+ verifyNotificationCancelled(listOf(NO_UPSTREAM_NOTIFICATION_ID))
}
@Test
@@ -428,4 +427,57 @@
assertEquals(311, res.configuration.mcc)
assertEquals(480, res.configuration.mnc)
}
+
+ @Test
+ fun testNotificationWithUpstreamCapabilitiesChanged_Roaming() {
+ // Set test sub id. Clear notification.
+ notificationUpdater.onActiveDataSubscriptionIdChanged(TEST_SUBID)
+ verifyNotificationCancelled(listOf(ENABLE_NOTIFICATION_ID, NO_UPSTREAM_NOTIFICATION_ID,
+ ROAMING_NOTIFICATION_ID))
+
+ // Wifi downstream. Show enable notification with test resource.
+ notificationUpdater.onDownstreamChanged(WIFI_MASK)
+ verifyNotification(WIFI_ICON_ID, TEST_TITLE, TEST_MESSAGE, ENABLE_NOTIFICATION_ID)
+
+ // Upstream capabilities changed to roaming state. Show roaming notification.
+ notificationUpdater.onUpstreamCapabilitiesChanged(ROAMING_CAPABILITIES)
+ verifyNotification(NOTIFICATION_ICON_ID, TEST_ROAMING_TITLE, TEST_ROAMING_MESSAGE,
+ ROAMING_NOTIFICATION_ID)
+
+ // Same capabilities change. Nothing happened.
+ notificationUpdater.onUpstreamCapabilitiesChanged(ROAMING_CAPABILITIES)
+ verifyZeroInteractions(notificationManager)
+
+ // Upstream capabilities changed to home state. Clear roaming notification.
+ notificationUpdater.onUpstreamCapabilitiesChanged(HOME_CAPABILITIES)
+ verifyNotificationCancelled(listOf(ROAMING_NOTIFICATION_ID))
+
+ // Upstream capabilities changed to roaming state again. Show roaming notification.
+ notificationUpdater.onUpstreamCapabilitiesChanged(ROAMING_CAPABILITIES)
+ verifyNotification(NOTIFICATION_ICON_ID, TEST_ROAMING_TITLE, TEST_ROAMING_MESSAGE,
+ ROAMING_NOTIFICATION_ID)
+
+ // No upstream. Clear roaming notification and show no upstream notification.
+ notificationUpdater.onUpstreamCapabilitiesChanged(null)
+ notificationUpdater.handler.waitForDelayedMessage(EVENT_SHOW_NO_UPSTREAM, TIMEOUT_MS)
+ verifyNotificationCancelled(listOf(ROAMING_NOTIFICATION_ID), false)
+ verifyNotification(NOTIFICATION_ICON_ID, TEST_NO_UPSTREAM_TITLE, TEST_NO_UPSTREAM_MESSAGE,
+ NO_UPSTREAM_NOTIFICATION_ID)
+
+ // No downstream. No notification.
+ notificationUpdater.onDownstreamChanged(DOWNSTREAM_NONE)
+ verifyNotificationCancelled(listOf(ENABLE_NOTIFICATION_ID, NO_UPSTREAM_NOTIFICATION_ID,
+ ROAMING_NOTIFICATION_ID))
+
+ // Wifi downstream again. Show enable notification with test resource.
+ notificationUpdater.onDownstreamChanged(WIFI_MASK)
+ verifyNotification(WIFI_ICON_ID, TEST_TITLE, TEST_MESSAGE, ENABLE_NOTIFICATION_ID)
+
+ // Set R.bool.config_upstream_roaming_notification to false and change upstream
+ // network to roaming state again. No roaming notification.
+ doReturn(false).`when`(testResources)
+ .getBoolean(R.bool.config_upstream_roaming_notification)
+ notificationUpdater.onUpstreamCapabilitiesChanged(ROAMING_CAPABILITIES)
+ verifyNotificationCancelled(listOf(NO_UPSTREAM_NOTIFICATION_ID, ROAMING_NOTIFICATION_ID))
+ }
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index 15e253a..28bfae0 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -48,6 +48,7 @@
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
import static com.android.networkstack.tethering.TetheringNotificationUpdater.DOWNSTREAM_NONE;
+import static com.android.networkstack.tethering.UpstreamNetworkMonitor.EVENT_ON_CAPABILITIES;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
@@ -1700,7 +1701,29 @@
stateMachine.chooseUpstreamType(true);
verify(mUpstreamNetworkMonitor, times(1)).setCurrentUpstream(eq(upstreamState.network));
- verify(mNotificationUpdater, times(1)).onUpstreamNetworkChanged(eq(upstreamState.network));
+ verify(mNotificationUpdater, times(1)).onUpstreamCapabilitiesChanged(any());
+ }
+
+ @Test
+ public void testUpstreamCapabilitiesChanged() {
+ final Tethering.TetherMasterSM stateMachine = (Tethering.TetherMasterSM)
+ mTetheringDependencies.mUpstreamNetworkMonitorMasterSM;
+ final UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState();
+ when(mUpstreamNetworkMonitor.selectPreferredUpstreamType(any())).thenReturn(upstreamState);
+ stateMachine.chooseUpstreamType(true);
+
+ stateMachine.handleUpstreamNetworkMonitorCallback(EVENT_ON_CAPABILITIES, upstreamState);
+ // Should have two onUpstreamCapabilitiesChanged().
+ // One is called by reportUpstreamChanged(). One is called by EVENT_ON_CAPABILITIES.
+ verify(mNotificationUpdater, times(2)).onUpstreamCapabilitiesChanged(any());
+ reset(mNotificationUpdater);
+
+ // Verify that onUpstreamCapabilitiesChanged won't be called if not current upstream network
+ // capabilities changed.
+ final UpstreamNetworkState upstreamState2 = new UpstreamNetworkState(
+ upstreamState.linkProperties, upstreamState.networkCapabilities, new Network(101));
+ stateMachine.handleUpstreamNetworkMonitorCallback(EVENT_ON_CAPABILITIES, upstreamState2);
+ verify(mNotificationUpdater, never()).onUpstreamCapabilitiesChanged(any());
}
// TODO: Test that a request for hotspot mode doesn't interfere with an