diff options
7 files changed, 247 insertions, 94 deletions
diff --git a/packages/Tethering/res/values-mcc310-mnc004/config.xml b/packages/Tethering/res/values-mcc310-mnc004/config.xml index 8c627d5df058..5c5be0466a36 100644 --- a/packages/Tethering/res/values-mcc310-mnc004/config.xml +++ b/packages/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/packages/Tethering/res/values-mcc311-mnc480/config.xml b/packages/Tethering/res/values-mcc311-mnc480/config.xml index 8c627d5df058..5c5be0466a36 100644 --- a/packages/Tethering/res/values-mcc311-mnc480/config.xml +++ b/packages/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/packages/Tethering/res/values/config.xml b/packages/Tethering/res/values/config.xml index 780a015961b4..aed5ab8df6af 100644 --- a/packages/Tethering/res/values/config.xml +++ b/packages/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/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java b/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java index 14d288699e99..97b19466b38b 100644 --- a/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java +++ b/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java @@ -81,6 +81,7 @@ import android.net.IpPrefix; 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 @@ public class Tethering { if (mTetherUpstream != newUpstream) { mTetherUpstream = newUpstream; mUpstreamNetworkMonitor.setCurrentUpstream(mTetherUpstream); - reportUpstreamChanged(mTetherUpstream); + reportUpstreamChanged(ns); } } @@ -1598,7 +1599,8 @@ public class Tethering { } } - 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 @@ public class Tethering { 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 @@ public class Tethering { }); } - 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 @@ public class Tethering { } 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/packages/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java b/packages/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java index de2f90e50e2f..f490cc4719fa 100644 --- a/packages/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java +++ b/packages/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.Context; 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 androidx.annotation.Nullable; 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 @@ public class TetheringNotificationUpdater { // 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 @@ public class TetheringNotificationUpdater { 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 @@ public class TetheringNotificationUpdater { // 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 @@ public class TetheringNotificationUpdater { /** 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 @@ public class TetheringNotificationUpdater { 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 @@ public class TetheringNotificationUpdater { 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 @@ public class TetheringNotificationUpdater { 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/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt index 294bf1b7e169..04f31a7a2880 100644 --- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt +++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt @@ -23,10 +23,11 @@ import android.content.res.Resources 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.ENABLE_NO 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_MESSAGE = "Tap to set up hotspot." 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 @@ class TetheringNotificationUpdaterTest { "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 @@ class TetheringNotificationUpdaterTest { .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 @@ class TetheringNotificationUpdaterTest { .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 @@ class TetheringNotificationUpdaterTest { 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 @@ class TetheringNotificationUpdaterTest { // 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 @@ class TetheringNotificationUpdaterTest { // 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 @@ class TetheringNotificationUpdaterTest { // 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 @@ class TetheringNotificationUpdaterTest { } @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 @@ class TetheringNotificationUpdaterTest { 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/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java index 15e253af12a9..28bfae0ced4a 100644 --- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java +++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java @@ -48,6 +48,7 @@ import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED; 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 @@ public class TetheringTest { 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 |