Merge changes Ic33d8976,Ib5bd74d8 into sc-dev

* changes:
  Mock connectivity resources in integration tests
  Add overlay options for no internet notifications
This commit is contained in:
Remi NGUYEN VAN
2021-08-05 07:17:31 +00:00
committed by Android (Google) Code Review
8 changed files with 200 additions and 3 deletions

View File

@@ -114,4 +114,15 @@
<!-- Whether to cancel network notifications automatically when tapped -->
<bool name="config_autoCancelNetworkNotifications">true</bool>
<!-- When no internet or partial connectivity is detected on a network, and a high priority
(heads up) notification would be shown due to the network being explicitly selected,
directly show the dialog that would normally be shown when tapping the notification
instead of showing the notification. -->
<bool name="config_notifyNoInternetAsDialogWhenHighPriority">false</bool>
<!-- When showing notifications indicating partial connectivity, display the same notifications
as no connectivity instead. This may be easier to understand for users but offers less
details on what is happening. -->
<bool name="config_partialConnectivityNotifiedAsNoInternet">false</bool>
</resources>

View File

@@ -32,6 +32,8 @@
<item type="array" name="config_networkNotifySwitches"/>
<item type="bool" name="config_ongoingSignInNotification"/>
<item type="bool" name="config_autoCancelNetworkNotifications"/>
<item type="bool" name="config_notifyNoInternetAsDialogWhenHighPriority"/>
<item type="bool" name="config_partialConnectivityNotifiedAsNoInternet"/>
<item type="drawable" name="stat_notify_wifi_in_range"/>
<item type="drawable" name="stat_notify_rssi_in_range"/>
</policy>

View File

@@ -198,11 +198,22 @@ public class NetworkNotificationManager {
}
final Resources r = mResources.get();
if (highPriority && maybeNotifyViaDialog(r, notifyType, intent)) {
Log.d(TAG, "Notified via dialog for event " + nameOf(eventId));
return;
}
final CharSequence title;
final CharSequence details;
Icon icon = Icon.createWithResource(
mResources.getResourcesContext(), getIcon(transportType));
if (notifyType == NotificationType.NO_INTERNET && transportType == TRANSPORT_WIFI) {
final boolean showAsNoInternet = notifyType == NotificationType.PARTIAL_CONNECTIVITY
&& r.getBoolean(R.bool.config_partialConnectivityNotifiedAsNoInternet);
if (showAsNoInternet) {
Log.d(TAG, "Showing partial connectivity as NO_INTERNET");
}
if ((notifyType == NotificationType.NO_INTERNET || showAsNoInternet)
&& transportType == TRANSPORT_WIFI) {
title = r.getString(R.string.wifi_no_internet, name);
details = r.getString(R.string.wifi_no_internet_detailed);
} else if (notifyType == NotificationType.PRIVATE_DNS_BROKEN) {
@@ -306,6 +317,24 @@ public class NetworkNotificationManager {
}
}
private boolean maybeNotifyViaDialog(Resources res, NotificationType notifyType,
PendingIntent intent) {
if (notifyType != NotificationType.NO_INTERNET
&& notifyType != NotificationType.PARTIAL_CONNECTIVITY) {
return false;
}
if (!res.getBoolean(R.bool.config_notifyNoInternetAsDialogWhenHighPriority)) {
return false;
}
try {
intent.send();
} catch (PendingIntent.CanceledException e) {
Log.e(TAG, "Error sending dialog PendingIntent", e);
}
return true;
}
/**
* Clear the notification with the given id, only if it matches the given type.
*/

View File

@@ -30,6 +30,7 @@ android_test {
],
libs: [
"android.test.mock",
"ServiceConnectivityResources",
],
static_libs: [
"NetworkStackApiStableLib",

View File

@@ -23,7 +23,9 @@ import android.content.Context.BIND_AUTO_CREATE
import android.content.Context.BIND_IMPORTANT
import android.content.Intent
import android.content.ServiceConnection
import android.content.res.Resources
import android.net.ConnectivityManager
import android.net.ConnectivityResources
import android.net.IDnsResolver
import android.net.INetd
import android.net.LinkProperties
@@ -35,6 +37,7 @@ import android.net.NetworkRequest
import android.net.TestNetworkStackClient
import android.net.Uri
import android.net.metrics.IpConnectivityLog
import android.net.util.MultinetworkPolicyTracker
import android.os.ConditionVariable
import android.os.IBinder
import android.os.SystemConfigManager
@@ -43,6 +46,7 @@ import android.testing.TestableContext
import android.util.Log
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.android.connectivity.resources.R
import com.android.server.ConnectivityService
import com.android.server.NetworkAgentWrapper
import com.android.server.TestNetIdManager
@@ -59,6 +63,7 @@ import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
import org.mockito.Mockito.any
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.doNothing
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.eq
@@ -93,6 +98,10 @@ class ConnectivityServiceIntegrationTest {
private lateinit var dnsResolver: IDnsResolver
@Mock
private lateinit var systemConfigManager: SystemConfigManager
@Mock
private lateinit var resources: Resources
@Mock
private lateinit var resourcesContext: Context
@Spy
private var context = TestableContext(realContext)
@@ -110,9 +119,11 @@ class ConnectivityServiceIntegrationTest {
private val realContext get() = InstrumentationRegistry.getInstrumentation().context
private val httpProbeUrl get() =
realContext.getResources().getString(R.string.config_captive_portal_http_url)
realContext.getResources().getString(com.android.server.net.integrationtests.R.string
.config_captive_portal_http_url)
private val httpsProbeUrl get() =
realContext.getResources().getString(R.string.config_captive_portal_https_url)
realContext.getResources().getString(com.android.server.net.integrationtests.R.string
.config_captive_portal_https_url)
private class InstrumentationServiceConnection : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
@@ -156,6 +167,27 @@ class ConnectivityServiceIntegrationTest {
.getSystemService(Context.SYSTEM_CONFIG_SERVICE)
doReturn(IntArray(0)).`when`(systemConfigManager).getSystemPermissionUids(anyString())
doReturn(60000).`when`(resources).getInteger(R.integer.config_networkTransitionTimeout)
doReturn("").`when`(resources).getString(R.string.config_networkCaptivePortalServerUrl)
doReturn(arrayOf<String>("test_wlan_wol")).`when`(resources)
.getStringArray(R.array.config_wakeonlan_supported_interfaces)
doReturn(arrayOf("0,1", "1,3")).`when`(resources)
.getStringArray(R.array.config_networkSupportedKeepaliveCount)
doReturn(emptyArray<String>()).`when`(resources)
.getStringArray(R.array.config_networkNotifySwitches)
doReturn(intArrayOf(10, 11, 12, 14, 15)).`when`(resources)
.getIntArray(R.array.config_protectedNetworks)
// We don't test the actual notification value strings, so just return an empty array.
// It doesn't matter what the values are as long as it's not null.
doReturn(emptyArray<String>()).`when`(resources).getStringArray(
R.array.network_switch_type_name)
doReturn(1).`when`(resources).getInteger(R.integer.config_networkAvoidBadWifi)
doReturn(R.array.config_networkSupportedKeepaliveCount).`when`(resources)
.getIdentifier(eq("config_networkSupportedKeepaliveCount"), eq("array"), any())
doReturn(resources).`when`(resourcesContext).getResources()
ConnectivityResources.setResourcesContextForTest(resourcesContext)
networkStackClient = TestNetworkStackClient(realContext)
networkStackClient.start()
@@ -176,12 +208,19 @@ class ConnectivityServiceIntegrationTest {
doReturn(mock(ProxyTracker::class.java)).`when`(deps).makeProxyTracker(any(), any())
doReturn(mock(MockableSystemProperties::class.java)).`when`(deps).systemProperties
doReturn(TestNetIdManager()).`when`(deps).makeNetIdManager()
doAnswer { inv ->
object : MultinetworkPolicyTracker(inv.getArgument(0), inv.getArgument(1),
inv.getArgument(2)) {
override fun getResourcesForActiveSubId() = resources
}
}.`when`(deps).makeMultinetworkPolicyTracker(any(), any(), any())
return deps
}
@After
fun tearDown() {
nsInstrumentation.clearAllState()
ConnectivityResources.setResourcesContextForTest(null)
}
@Test

View File

@@ -62,6 +62,7 @@ android_library {
jarjar_rules: "jarjar-rules.txt",
static_libs: [
"androidx.test.rules",
"androidx.test.uiautomator",
"bouncycastle-repackaged-unbundled",
"core-tests-support",
"FrameworksNetCommonTests",

View File

@@ -53,6 +53,8 @@
<application>
<uses-library android:name="android.test.runner" />
<uses-library android:name="android.net.ipsec.ike" />
<activity
android:name="com.android.server.connectivity.NetworkNotificationManagerTest$TestDialogActivity"/>
</application>
<instrumentation

View File

@@ -27,6 +27,7 @@ import static com.android.server.connectivity.NetworkNotificationManager.Notific
import static com.android.server.connectivity.NetworkNotificationManager.NotificationType.SIGN_IN;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.clearInvocations;
@@ -39,9 +40,14 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Activity;
import android.app.Instrumentation;
import android.app.KeyguardManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
@@ -49,11 +55,19 @@ import android.net.ConnectivityResources;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.os.Build;
import android.os.Bundle;
import android.os.UserHandle;
import android.telephony.TelephonyManager;
import android.util.DisplayMetrics;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.UiObject;
import androidx.test.uiautomator.UiSelector;
import com.android.connectivity.resources.R;
import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
@@ -84,6 +98,7 @@ public class NetworkNotificationManagerTest {
private static final String TEST_EXTRA_INFO = "extra";
private static final int TEST_NOTIF_ID = 101;
private static final String TEST_NOTIF_TAG = NetworkNotificationManager.tagFor(TEST_NOTIF_ID);
private static final long TEST_TIMEOUT_MS = 10_000L;
static final NetworkCapabilities CELL_CAPABILITIES = new NetworkCapabilities();
static final NetworkCapabilities WIFI_CAPABILITIES = new NetworkCapabilities();
static final NetworkCapabilities VPN_CAPABILITIES = new NetworkCapabilities();
@@ -102,6 +117,25 @@ public class NetworkNotificationManagerTest {
VPN_CAPABILITIES.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
}
/**
* Test activity that shows the action it was started with on screen, and dismisses when the
* text is tapped.
*/
public static class TestDialogActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTurnScreenOn(true);
getSystemService(KeyguardManager.class).requestDismissKeyguard(
this, null /* callback */);
final TextView txt = new TextView(this);
txt.setText(getIntent().getAction());
txt.setOnClickListener(e -> finish());
setContentView(txt);
}
}
@Mock Context mCtx;
@Mock Resources mResources;
@Mock DisplayMetrics mDisplayMetrics;
@@ -345,4 +379,82 @@ public class NetworkNotificationManagerTest {
mManager.clearNotification(id, PARTIAL_CONNECTIVITY);
verify(mNotificationManager, never()).cancel(eq(tag), eq(PARTIAL_CONNECTIVITY.eventId));
}
@Test
public void testNotifyNoInternetAsDialogWhenHighPriority() throws Exception {
doReturn(true).when(mResources).getBoolean(
R.bool.config_notifyNoInternetAsDialogWhenHighPriority);
mManager.showNotification(TEST_NOTIF_ID, NETWORK_SWITCH, mWifiNai, mCellNai, null, false);
// Non-"no internet" notifications are not affected
verify(mNotificationManager).notify(eq(TEST_NOTIF_TAG), eq(NETWORK_SWITCH.eventId), any());
final Instrumentation instr = InstrumentationRegistry.getInstrumentation();
final Context ctx = instr.getContext();
final String testAction = "com.android.connectivity.coverage.TEST_DIALOG";
final Intent intent = new Intent(testAction)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.setClassName(ctx.getPackageName(), TestDialogActivity.class.getName());
final PendingIntent pendingIntent = PendingIntent.getActivity(ctx, 0 /* requestCode */,
intent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
mManager.showNotification(TEST_NOTIF_ID, NO_INTERNET, mWifiNai, null /* switchToNai */,
pendingIntent, true /* highPriority */);
// Previous notifications are still dismissed
verify(mNotificationManager).cancel(TEST_NOTIF_TAG, NETWORK_SWITCH.eventId);
// Verify that the activity is shown (the activity shows the action on screen)
final UiObject actionText = UiDevice.getInstance(instr).findObject(
new UiSelector().text(testAction));
assertTrue("Activity not shown", actionText.waitForExists(TEST_TIMEOUT_MS));
// Tapping the text should dismiss the dialog
actionText.click();
assertTrue("Activity not dismissed", actionText.waitUntilGone(TEST_TIMEOUT_MS));
// Verify no NO_INTERNET notification was posted
verify(mNotificationManager, never()).notify(any(), eq(NO_INTERNET.eventId), any());
}
private void doNotificationTextTest(NotificationType type, @StringRes int expectedTitleRes,
String expectedTitleArg, @StringRes int expectedContentRes) {
final String expectedTitle = "title " + expectedTitleArg;
final String expectedContent = "expected content";
doReturn(expectedTitle).when(mResources).getString(expectedTitleRes, expectedTitleArg);
doReturn(expectedContent).when(mResources).getString(expectedContentRes);
mManager.showNotification(TEST_NOTIF_ID, type, mWifiNai, mCellNai, null, false);
final ArgumentCaptor<Notification> notifCap = ArgumentCaptor.forClass(Notification.class);
verify(mNotificationManager).notify(eq(TEST_NOTIF_TAG), eq(type.eventId),
notifCap.capture());
final Notification notif = notifCap.getValue();
assertEquals(expectedTitle, notif.extras.getString(Notification.EXTRA_TITLE));
assertEquals(expectedContent, notif.extras.getString(Notification.EXTRA_TEXT));
}
@Test
public void testNotificationText_NoInternet() {
doNotificationTextTest(NO_INTERNET,
R.string.wifi_no_internet, TEST_EXTRA_INFO,
R.string.wifi_no_internet_detailed);
}
@Test
public void testNotificationText_Partial() {
doNotificationTextTest(PARTIAL_CONNECTIVITY,
R.string.network_partial_connectivity, TEST_EXTRA_INFO,
R.string.network_partial_connectivity_detailed);
}
@Test
public void testNotificationText_PartialAsNoInternet() {
doReturn(true).when(mResources).getBoolean(
R.bool.config_partialConnectivityNotifiedAsNoInternet);
doNotificationTextTest(PARTIAL_CONNECTIVITY,
R.string.wifi_no_internet, TEST_EXTRA_INFO,
R.string.wifi_no_internet_detailed);
}
}