DO NOT MERGE: Move the connectivity tests to frameworks/base/tests/net.

This will give us a good place to put all the networking tests.

Fix: 31479480
Test: adb  shell am instrument -w -e notClass com.android.server.connectivity.tethering.TetherInterfaceStateMachineTest 'com.android.frameworks.tests.net/android.support.test.runner.AndroidJUnitRunner'  # PASS

(cherry picked from commit 5a7c486d70)

Change-Id: I4c417c2ba0747d2085169e47ae4a99e93c4d8814
This commit is contained in:
Lorenzo Colitti
2016-10-28 12:48:36 +09:00
parent 0c7a3c349d
commit e8d1e9daa0
7 changed files with 0 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,389 @@
/*
* 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 static com.android.server.connectivity.MetricsTestUtil.aBool;
import static com.android.server.connectivity.MetricsTestUtil.aByteArray;
import static com.android.server.connectivity.MetricsTestUtil.aLong;
import static com.android.server.connectivity.MetricsTestUtil.aString;
import static com.android.server.connectivity.MetricsTestUtil.aType;
import static com.android.server.connectivity.MetricsTestUtil.anInt;
import static com.android.server.connectivity.MetricsTestUtil.anIntArray;
import static com.android.server.connectivity.MetricsTestUtil.b;
import static com.android.server.connectivity.MetricsTestUtil.describeIpEvent;
import static com.android.server.connectivity.metrics.IpConnectivityLogClass.IpConnectivityLog;
import android.net.ConnectivityMetricsEvent;
import android.net.metrics.ApfProgramEvent;
import android.net.metrics.ApfStats;
import android.net.metrics.DefaultNetworkEvent;
import android.net.metrics.DhcpClientEvent;
import android.net.metrics.DhcpErrorEvent;
import android.net.metrics.DnsEvent;
import android.net.metrics.IpManagerEvent;
import android.net.metrics.IpReachabilityEvent;
import android.net.metrics.NetworkEvent;
import android.net.metrics.RaEvent;
import android.net.metrics.ValidationProbeEvent;
import android.test.suitebuilder.annotation.SmallTest;
import java.util.Arrays;
import junit.framework.TestCase;
public class IpConnectivityEventBuilderTest extends TestCase {
@SmallTest
public void testDefaultNetworkEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent(
aType(DefaultNetworkEvent.class),
anInt(102),
anIntArray(1, 2, 3),
anInt(101),
aBool(true),
aBool(false));
String want = joinLines(
"dropped_events: 0",
"events <",
" time_ms: 1",
" transport: 0",
" default_network_event <",
" network_id <",
" network_id: 102",
" >",
" previous_network_id <",
" network_id: 101",
" >",
" previous_network_ip_support: 1",
" transport_types: 1",
" transport_types: 2",
" transport_types: 3",
" >",
">",
"version: 2");
verifySerialization(want, ev);
}
@SmallTest
public void testDhcpClientEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent(
aType(DhcpClientEvent.class),
aString("wlan0"),
aString("SomeState"),
anInt(192));
String want = joinLines(
"dropped_events: 0",
"events <",
" time_ms: 1",
" transport: 0",
" dhcp_event <",
" duration_ms: 192",
" if_name: \"wlan0\"",
" state_transition: \"SomeState\"",
" >",
">",
"version: 2");
verifySerialization(want, ev);
}
@SmallTest
public void testDhcpErrorEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent(
aType(DhcpErrorEvent.class),
aString("wlan0"),
anInt(DhcpErrorEvent.L4_NOT_UDP));
String want = joinLines(
"dropped_events: 0",
"events <",
" time_ms: 1",
" transport: 0",
" dhcp_event <",
" duration_ms: 0",
" if_name: \"wlan0\"",
" error_code: 50397184",
" >",
">",
"version: 2");
verifySerialization(want, ev);
}
@SmallTest
public void testDnsEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent(
aType(DnsEvent.class),
anInt(101),
aByteArray(b(1), b(1), b(2), b(1), b(1), b(1), b(2), b(2)),
aByteArray(b(0), b(0), b(22), b(3), b(1), b(0), b(200), b(178)),
anIntArray(3456, 267, 1230, 45, 2111, 450, 638, 1300));
String want = joinLines(
"dropped_events: 0",
"events <",
" time_ms: 1",
" transport: 0",
" dns_lookup_batch <",
" event_types: 1",
" event_types: 1",
" event_types: 2",
" event_types: 1",
" event_types: 1",
" event_types: 1",
" event_types: 2",
" event_types: 2",
" latencies_ms: 3456",
" latencies_ms: 267",
" latencies_ms: 1230",
" latencies_ms: 45",
" latencies_ms: 2111",
" latencies_ms: 450",
" latencies_ms: 638",
" latencies_ms: 1300",
" network_id <",
" network_id: 101",
" >",
" return_codes: 0",
" return_codes: 0",
" return_codes: 22",
" return_codes: 3",
" return_codes: 1",
" return_codes: 0",
" return_codes: 200",
" return_codes: 178",
" >",
">",
"version: 2");
verifySerialization(want, ev);
}
@SmallTest
public void testIpManagerEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent(
aType(IpManagerEvent.class),
aString("wlan0"),
anInt(IpManagerEvent.PROVISIONING_OK),
aLong(5678));
String want = joinLines(
"dropped_events: 0",
"events <",
" time_ms: 1",
" transport: 0",
" ip_provisioning_event <",
" event_type: 1",
" if_name: \"wlan0\"",
" latency_ms: 5678",
" >",
">",
"version: 2");
verifySerialization(want, ev);
}
@SmallTest
public void testIpReachabilityEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent(
aType(IpReachabilityEvent.class),
aString("wlan0"),
anInt(IpReachabilityEvent.NUD_FAILED));
String want = joinLines(
"dropped_events: 0",
"events <",
" time_ms: 1",
" transport: 0",
" ip_reachability_event <",
" event_type: 512",
" if_name: \"wlan0\"",
" >",
">",
"version: 2");
verifySerialization(want, ev);
}
@SmallTest
public void testNetworkEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent(
aType(NetworkEvent.class),
anInt(100),
anInt(5),
aLong(20410));
String want = joinLines(
"dropped_events: 0",
"events <",
" time_ms: 1",
" transport: 0",
" network_event <",
" event_type: 5",
" latency_ms: 20410",
" network_id <",
" network_id: 100",
" >",
" >",
">",
"version: 2");
verifySerialization(want, ev);
}
@SmallTest
public void testValidationProbeEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent(
aType(ValidationProbeEvent.class),
anInt(120),
aLong(40730),
anInt(ValidationProbeEvent.PROBE_HTTP),
anInt(204));
String want = joinLines(
"dropped_events: 0",
"events <",
" time_ms: 1",
" transport: 0",
" validation_probe_event <",
" latency_ms: 40730",
" network_id <",
" network_id: 120",
" >",
" probe_result: 204",
" probe_type: 1",
" >",
">",
"version: 2");
verifySerialization(want, ev);
}
@SmallTest
public void testApfProgramEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent(
aType(ApfProgramEvent.class),
aLong(200),
anInt(7),
anInt(9),
anInt(2048),
anInt(3));
String want = joinLines(
"dropped_events: 0",
"events <",
" time_ms: 1",
" transport: 0",
" apf_program_event <",
" current_ras: 9",
" drop_multicast: true",
" filtered_ras: 7",
" has_ipv4_addr: true",
" lifetime: 200",
" program_length: 2048",
" >",
">",
"version: 2");
verifySerialization(want, ev);
}
@SmallTest
public void testApfStatsSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent(
aType(ApfStats.class),
aLong(45000),
anInt(10),
anInt(2),
anInt(2),
anInt(1),
anInt(2),
anInt(4),
anInt(2048));
String want = joinLines(
"dropped_events: 0",
"events <",
" time_ms: 1",
" transport: 0",
" apf_statistics <",
" dropped_ras: 2",
" duration_ms: 45000",
" matching_ras: 2",
" max_program_size: 2048",
" parse_errors: 2",
" program_updates: 4",
" received_ras: 10",
" zero_lifetime_ras: 1",
" >",
">",
"version: 2");
verifySerialization(want, ev);
}
@SmallTest
public void testRaEventSerialization() {
ConnectivityMetricsEvent ev = describeIpEvent(
aType(RaEvent.class),
aLong(2000),
aLong(400),
aLong(300),
aLong(-1),
aLong(1000),
aLong(-1));
String want = joinLines(
"dropped_events: 0",
"events <",
" time_ms: 1",
" transport: 0",
" ra_event <",
" dnssl_lifetime: -1",
" prefix_preferred_lifetime: 300",
" prefix_valid_lifetime: 400",
" rdnss_lifetime: 1000",
" route_info_lifetime: -1",
" router_lifetime: 2000",
" >",
">",
"version: 2");
verifySerialization(want, ev);
}
static void verifySerialization(String want, ConnectivityMetricsEvent... input) {
try {
byte[] got = IpConnectivityEventBuilder.serialize(0,
IpConnectivityEventBuilder.toProto(Arrays.asList(input)));
IpConnectivityLog log = IpConnectivityLog.parseFrom(got);
assertEquals(want, log.toString());
} catch (Exception e) {
fail(e.toString());
}
}
static String joinLines(String ... elems) {
StringBuilder b = new StringBuilder();
for (String s : elems) {
b.append(s);
b.append("\n");
}
return b.toString();
}
}

View File

@@ -0,0 +1,296 @@
/*
* 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 static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import android.content.Context;
import android.net.ConnectivityMetricsEvent;
import android.net.IIpConnectivityMetrics;
import android.net.metrics.ApfProgramEvent;
import android.net.metrics.ApfStats;
import android.net.metrics.DefaultNetworkEvent;
import android.net.metrics.DhcpClientEvent;
import android.net.metrics.IpConnectivityLog;
import android.net.metrics.IpManagerEvent;
import android.net.metrics.IpReachabilityEvent;
import android.net.metrics.RaEvent;
import android.net.metrics.ValidationProbeEvent;
import android.os.Parcelable;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Base64;
import com.android.server.connectivity.metrics.IpConnectivityLogClass;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import junit.framework.TestCase;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
public class IpConnectivityMetricsTest extends TestCase {
static final IpReachabilityEvent FAKE_EV =
new IpReachabilityEvent("wlan0", IpReachabilityEvent.NUD_FAILED);
@Mock Context mCtx;
@Mock IIpConnectivityMetrics mMockService;
IpConnectivityMetrics mService;
public void setUp() {
MockitoAnnotations.initMocks(this);
mService = new IpConnectivityMetrics(mCtx, (ctx) -> 2000);
}
@SmallTest
public void testLoggingEvents() throws Exception {
IpConnectivityLog logger = new IpConnectivityLog(mMockService);
assertTrue(logger.log(1, FAKE_EV));
assertTrue(logger.log(2, FAKE_EV));
assertTrue(logger.log(3, FAKE_EV));
List<ConnectivityMetricsEvent> got = verifyEvents(3);
assertEventsEqual(expectedEvent(1), got.get(0));
assertEventsEqual(expectedEvent(2), got.get(1));
assertEventsEqual(expectedEvent(3), got.get(2));
}
@SmallTest
public void testLoggingEventsWithMultipleCallers() throws Exception {
IpConnectivityLog logger = new IpConnectivityLog(mMockService);
final int nCallers = 10;
final int nEvents = 10;
for (int n = 0; n < nCallers; n++) {
final int i = n;
new Thread() {
public void run() {
for (int j = 0; j < nEvents; j++) {
assertTrue(logger.log(i * 100 + j, FAKE_EV));
}
}
}.start();
}
List<ConnectivityMetricsEvent> got = verifyEvents(nCallers * nEvents, 100);
Collections.sort(got, EVENT_COMPARATOR);
Iterator<ConnectivityMetricsEvent> iter = got.iterator();
for (int i = 0; i < nCallers; i++) {
for (int j = 0; j < nEvents; j++) {
int expectedTimestamp = i * 100 + j;
assertEventsEqual(expectedEvent(expectedTimestamp), iter.next());
}
}
}
@SmallTest
public void testBufferFlushing() {
String output1 = getdump("flush");
assertEquals("", output1);
new IpConnectivityLog(mService.impl).log(1, FAKE_EV);
String output2 = getdump("flush");
assertFalse("".equals(output2));
String output3 = getdump("flush");
assertEquals("", output3);
}
@SmallTest
public void testRateLimiting() {
final IpConnectivityLog logger = new IpConnectivityLog(mService.impl);
final ApfProgramEvent ev = new ApfProgramEvent(0, 0, 0, 0, 0);
final long fakeTimestamp = 1;
int attempt = 100; // More than burst quota, but less than buffer size.
for (int i = 0; i < attempt; i++) {
logger.log(ev);
}
String output1 = getdump("flush");
assertFalse("".equals(output1));
for (int i = 0; i < attempt; i++) {
assertFalse("expected event to be dropped", logger.log(fakeTimestamp, ev));
}
String output2 = getdump("flush");
assertEquals("", output2);
}
@SmallTest
public void testEndToEndLogging() {
IpConnectivityLog logger = new IpConnectivityLog(mService.impl);
Parcelable[] events = {
new IpReachabilityEvent("wlan0", IpReachabilityEvent.NUD_FAILED),
new DhcpClientEvent("wlan0", "SomeState", 192),
new DefaultNetworkEvent(102, new int[]{1,2,3}, 101, true, false),
new IpManagerEvent("wlan0", IpManagerEvent.PROVISIONING_OK, 5678),
new ValidationProbeEvent(120, 40730, ValidationProbeEvent.PROBE_HTTP, 204),
new ApfStats(45000, 10, 2, 2, 1, 2, 4, 2048),
new RaEvent(2000, 400, 300, -1, 1000, -1)
};
for (int i = 0; i < events.length; i++) {
logger.log(100 * (i + 1), events[i]);
}
String want = joinLines(
"dropped_events: 0",
"events <",
" time_ms: 100",
" transport: 0",
" ip_reachability_event <",
" event_type: 512",
" if_name: \"wlan0\"",
" >",
">",
"events <",
" time_ms: 200",
" transport: 0",
" dhcp_event <",
" duration_ms: 192",
" if_name: \"wlan0\"",
" state_transition: \"SomeState\"",
" >",
">",
"events <",
" time_ms: 300",
" transport: 0",
" default_network_event <",
" network_id <",
" network_id: 102",
" >",
" previous_network_id <",
" network_id: 101",
" >",
" previous_network_ip_support: 1",
" transport_types: 1",
" transport_types: 2",
" transport_types: 3",
" >",
">",
"events <",
" time_ms: 400",
" transport: 0",
" ip_provisioning_event <",
" event_type: 1",
" if_name: \"wlan0\"",
" latency_ms: 5678",
" >",
">",
"events <",
" time_ms: 500",
" transport: 0",
" validation_probe_event <",
" latency_ms: 40730",
" network_id <",
" network_id: 120",
" >",
" probe_result: 204",
" probe_type: 1",
" >",
">",
"events <",
" time_ms: 600",
" transport: 0",
" apf_statistics <",
" dropped_ras: 2",
" duration_ms: 45000",
" matching_ras: 2",
" max_program_size: 2048",
" parse_errors: 2",
" program_updates: 4",
" received_ras: 10",
" zero_lifetime_ras: 1",
" >",
">",
"events <",
" time_ms: 700",
" transport: 0",
" ra_event <",
" dnssl_lifetime: -1",
" prefix_preferred_lifetime: 300",
" prefix_valid_lifetime: 400",
" rdnss_lifetime: 1000",
" route_info_lifetime: -1",
" router_lifetime: 2000",
" >",
">",
"version: 2");
verifySerialization(want, getdump("flush"));
}
String getdump(String ... command) {
StringWriter buffer = new StringWriter();
PrintWriter writer = new PrintWriter(buffer);
mService.impl.dump(null, writer, command);
return buffer.toString();
}
List<ConnectivityMetricsEvent> verifyEvents(int n, int timeoutMs) throws Exception {
ArgumentCaptor<ConnectivityMetricsEvent> captor =
ArgumentCaptor.forClass(ConnectivityMetricsEvent.class);
verify(mMockService, timeout(timeoutMs).times(n)).logEvent(captor.capture());
return captor.getAllValues();
}
List<ConnectivityMetricsEvent> verifyEvents(int n) throws Exception {
return verifyEvents(n, 10);
}
static void verifySerialization(String want, String output) {
try {
byte[] got = Base64.decode(output, Base64.DEFAULT);
IpConnectivityLogClass.IpConnectivityLog log =
IpConnectivityLogClass.IpConnectivityLog.parseFrom(got);
assertEquals(want, log.toString());
} catch (Exception e) {
fail(e.toString());
}
}
static String joinLines(String ... elems) {
StringBuilder b = new StringBuilder();
for (String s : elems) {
b.append(s).append("\n");
}
return b.toString();
}
static ConnectivityMetricsEvent expectedEvent(int timestamp) {
return new ConnectivityMetricsEvent((long)timestamp, 0, 0, FAKE_EV);
}
/** Outer equality for ConnectivityMetricsEvent to avoid overriding equals() and hashCode(). */
static void assertEventsEqual(ConnectivityMetricsEvent expected, ConnectivityMetricsEvent got) {
assertEquals(expected.timestamp, got.timestamp);
assertEquals(expected.componentTag, got.componentTag);
assertEquals(expected.eventTag, got.eventTag);
assertEquals(expected.data, got.data);
}
static final Comparator<ConnectivityMetricsEvent> EVENT_COMPARATOR =
Comparator.comparingLong((ev) -> ev.timestamp);
}

View File

@@ -0,0 +1,349 @@
/*
* 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;
}
}
}

View File

@@ -0,0 +1,120 @@
/*
* 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.net.ConnectivityMetricsEvent;
import android.net.ConnectivityMetricsLogger;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
abstract public class MetricsTestUtil {
private MetricsTestUtil() {
}
static ConnectivityMetricsEvent ipEv(Parcelable p) {
return ev(ConnectivityMetricsLogger.COMPONENT_TAG_CONNECTIVITY, p);
}
static ConnectivityMetricsEvent telephonyEv() {
return ev(ConnectivityMetricsLogger.COMPONENT_TAG_TELEPHONY, new Bundle());
}
static ConnectivityMetricsEvent ev(int tag, Parcelable p) {
return new ConnectivityMetricsEvent(1L, tag, 0, p);
}
// Utiliy interface for describing the content of a Parcel. This relies on
// the implementation defails of Parcelable and on the fact that the fully
// qualified Parcelable class names are written as string in the Parcels.
interface ParcelField {
void write(Parcel p);
}
static ConnectivityMetricsEvent describeIpEvent(ParcelField... fs) {
Parcel p = Parcel.obtain();
for (ParcelField f : fs) {
f.write(p);
}
p.setDataPosition(0);
return ipEv(p.readParcelable(ClassLoader.getSystemClassLoader()));
}
static ParcelField aType(Class<?> c) {
return new ParcelField() {
public void write(Parcel p) {
p.writeString(c.getName());
}
};
}
static ParcelField aBool(boolean b) {
return aByte((byte) (b ? 1 : 0));
}
static ParcelField aByte(byte b) {
return new ParcelField() {
public void write(Parcel p) {
p.writeByte(b);
}
};
}
static ParcelField anInt(int i) {
return new ParcelField() {
public void write(Parcel p) {
p.writeInt(i);
}
};
}
static ParcelField aLong(long l) {
return new ParcelField() {
public void write(Parcel p) {
p.writeLong(l);
}
};
}
static ParcelField aString(String s) {
return new ParcelField() {
public void write(Parcel p) {
p.writeString(s);
}
};
}
static ParcelField aByteArray(byte... ary) {
return new ParcelField() {
public void write(Parcel p) {
p.writeByteArray(ary);
}
};
}
static ParcelField anIntArray(int... ary) {
return new ParcelField() {
public void write(Parcel p) {
p.writeIntArray(ary);
}
};
}
static byte b(int i) {
return (byte) i;
}
}

View File

@@ -0,0 +1,326 @@
/*
* 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.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.Network;
import android.net.metrics.DnsEvent;
import android.net.metrics.INetdEventListener;
import android.net.metrics.IpConnectivityLog;
import android.os.RemoteException;
import android.system.OsConstants;
import android.test.suitebuilder.annotation.SmallTest;
import com.android.server.connectivity.metrics.IpConnectivityLogClass.IpConnectivityEvent;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.OptionalInt;
import java.util.stream.IntStream;
import junit.framework.TestCase;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
public class NetdEventListenerServiceTest extends TestCase {
// TODO: read from NetdEventListenerService after this constant is read from system property
static final int BATCH_SIZE = 100;
static final int EVENT_TYPE = INetdEventListener.EVENT_GETADDRINFO;
// TODO: read from INetdEventListener
static final int RETURN_CODE = 1;
static final byte[] EVENT_TYPES = new byte[BATCH_SIZE];
static final byte[] RETURN_CODES = new byte[BATCH_SIZE];
static final int[] LATENCIES = new int[BATCH_SIZE];
static {
for (int i = 0; i < BATCH_SIZE; i++) {
EVENT_TYPES[i] = EVENT_TYPE;
RETURN_CODES[i] = RETURN_CODE;
LATENCIES[i] = i;
}
}
private static final String EXAMPLE_IPV4 = "192.0.2.1";
private static final String EXAMPLE_IPV6 = "2001:db8:1200::2:1";
NetdEventListenerService mNetdEventListenerService;
@Mock ConnectivityManager mCm;
@Mock IpConnectivityLog mLog;
ArgumentCaptor<NetworkCallback> mCallbackCaptor;
ArgumentCaptor<DnsEvent> mDnsEvCaptor;
public void setUp() {
MockitoAnnotations.initMocks(this);
mCallbackCaptor = ArgumentCaptor.forClass(NetworkCallback.class);
mDnsEvCaptor = ArgumentCaptor.forClass(DnsEvent.class);
mNetdEventListenerService = new NetdEventListenerService(mCm, mLog);
verify(mCm, times(1)).registerNetworkCallback(any(), mCallbackCaptor.capture());
}
@SmallTest
public void testOneDnsBatch() throws Exception {
log(105, LATENCIES);
log(106, Arrays.copyOf(LATENCIES, BATCH_SIZE - 1)); // one lookup short of a batch event
verifyLoggedDnsEvents(new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES));
log(106, Arrays.copyOfRange(LATENCIES, BATCH_SIZE - 1, BATCH_SIZE));
mDnsEvCaptor = ArgumentCaptor.forClass(DnsEvent.class); // reset argument captor
verifyLoggedDnsEvents(
new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES),
new DnsEvent(106, EVENT_TYPES, RETURN_CODES, LATENCIES));
}
@SmallTest
public void testSeveralDmsBatches() throws Exception {
log(105, LATENCIES);
log(106, LATENCIES);
log(105, LATENCIES);
log(107, LATENCIES);
verifyLoggedDnsEvents(
new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES),
new DnsEvent(106, EVENT_TYPES, RETURN_CODES, LATENCIES),
new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES),
new DnsEvent(107, EVENT_TYPES, RETURN_CODES, LATENCIES));
}
@SmallTest
public void testDnsBatchAndNetworkLost() throws Exception {
byte[] eventTypes = Arrays.copyOf(EVENT_TYPES, 20);
byte[] returnCodes = Arrays.copyOf(RETURN_CODES, 20);
int[] latencies = Arrays.copyOf(LATENCIES, 20);
log(105, LATENCIES);
log(105, latencies);
mCallbackCaptor.getValue().onLost(new Network(105));
log(105, LATENCIES);
verifyLoggedDnsEvents(
new DnsEvent(105, eventTypes, returnCodes, latencies),
new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES),
new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES));
}
@SmallTest
public void testConcurrentDnsBatchesAndDumps() throws Exception {
final long stop = System.currentTimeMillis() + 100;
final PrintWriter pw = new PrintWriter(new FileOutputStream("/dev/null"));
new Thread() {
public void run() {
while (System.currentTimeMillis() < stop) {
mNetdEventListenerService.dump(pw);
}
}
}.start();
logDnsAsync(105, LATENCIES);
logDnsAsync(106, LATENCIES);
logDnsAsync(107, LATENCIES);
verifyLoggedDnsEvents(500,
new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES),
new DnsEvent(106, EVENT_TYPES, RETURN_CODES, LATENCIES),
new DnsEvent(107, EVENT_TYPES, RETURN_CODES, LATENCIES));
}
@SmallTest
public void testConcurrentDnsBatchesAndNetworkLoss() throws Exception {
logDnsAsync(105, LATENCIES);
Thread.sleep(10L);
// call onLost() asynchronously to logDnsAsync's onDnsEvent() calls.
mCallbackCaptor.getValue().onLost(new Network(105));
// do not verify unpredictable batch
verify(mLog, timeout(500).times(1)).log(any());
}
@SmallTest
public void testConnectLogging() throws Exception {
final int OK = 0;
Thread[] logActions = {
// ignored
connectEventAction(OsConstants.EALREADY, 0, EXAMPLE_IPV4),
connectEventAction(OsConstants.EALREADY, 0, EXAMPLE_IPV6),
connectEventAction(OsConstants.EINPROGRESS, 0, EXAMPLE_IPV4),
connectEventAction(OsConstants.EINPROGRESS, 0, EXAMPLE_IPV6),
connectEventAction(OsConstants.EINPROGRESS, 0, EXAMPLE_IPV6),
// valid latencies
connectEventAction(OK, 110, EXAMPLE_IPV4),
connectEventAction(OK, 23, EXAMPLE_IPV4),
connectEventAction(OK, 45, EXAMPLE_IPV4),
connectEventAction(OK, 56, EXAMPLE_IPV4),
connectEventAction(OK, 523, EXAMPLE_IPV6),
connectEventAction(OK, 214, EXAMPLE_IPV6),
connectEventAction(OK, 67, EXAMPLE_IPV6),
// errors
connectEventAction(OsConstants.EPERM, 0, EXAMPLE_IPV4),
connectEventAction(OsConstants.EPERM, 0, EXAMPLE_IPV4),
connectEventAction(OsConstants.EAGAIN, 0, EXAMPLE_IPV4),
connectEventAction(OsConstants.EACCES, 0, EXAMPLE_IPV4),
connectEventAction(OsConstants.EACCES, 0, EXAMPLE_IPV4),
connectEventAction(OsConstants.EACCES, 0, EXAMPLE_IPV6),
connectEventAction(OsConstants.EADDRINUSE, 0, EXAMPLE_IPV4),
connectEventAction(OsConstants.ETIMEDOUT, 0, EXAMPLE_IPV4),
connectEventAction(OsConstants.ETIMEDOUT, 0, EXAMPLE_IPV6),
connectEventAction(OsConstants.ETIMEDOUT, 0, EXAMPLE_IPV6),
connectEventAction(OsConstants.ECONNREFUSED, 0, EXAMPLE_IPV4),
};
for (Thread t : logActions) {
t.start();
}
for (Thread t : logActions) {
t.join();
}
List<IpConnectivityEvent> events = new ArrayList<>();
mNetdEventListenerService.flushStatistics(events);
IpConnectivityEvent got = events.get(0);
String want = joinLines(
"time_ms: 0",
"transport: 0",
"connect_statistics <",
" connect_count: 12",
" errnos_counters <",
" key: 1",
" value: 2",
" >",
" errnos_counters <",
" key: 11",
" value: 1",
" >",
" errnos_counters <",
" key: 13",
" value: 3",
" >",
" errnos_counters <",
" key: 98",
" value: 1",
" >",
" errnos_counters <",
" key: 110",
" value: 3",
" >",
" errnos_counters <",
" key: 111",
" value: 1",
" >",
" ipv6_addr_count: 6",
" latencies_ms: 23",
" latencies_ms: 45",
" latencies_ms: 56",
" latencies_ms: 67",
" latencies_ms: 110",
" latencies_ms: 214",
" latencies_ms: 523");
verifyConnectEvent(want, got);
}
Thread connectEventAction(int error, int latencyMs, String ipAddr) {
return new Thread(() -> {
try {
mNetdEventListenerService.onConnectEvent(100, error, latencyMs, ipAddr, 80, 1);
} catch (Exception e) {
fail(e.toString());
}
});
}
void log(int netId, int[] latencies) {
try {
for (int l : latencies) {
mNetdEventListenerService.onDnsEvent(netId, EVENT_TYPE, RETURN_CODE, l, null, null,
0, 0);
}
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
}
void logDnsAsync(int netId, int[] latencies) {
new Thread() {
public void run() {
log(netId, latencies);
}
}.start();
}
void verifyLoggedDnsEvents(DnsEvent... expected) {
verifyLoggedDnsEvents(0, expected);
}
void verifyLoggedDnsEvents(int wait, DnsEvent... expectedEvents) {
verify(mLog, timeout(wait).times(expectedEvents.length)).log(mDnsEvCaptor.capture());
for (DnsEvent got : mDnsEvCaptor.getAllValues()) {
OptionalInt index = IntStream.range(0, expectedEvents.length)
.filter(i -> dnsEventsEqual(expectedEvents[i], got))
.findFirst();
// Don't match same expected event more than once.
index.ifPresent(i -> expectedEvents[i] = null);
assertTrue(index.isPresent());
}
}
/** equality function for DnsEvent to avoid overriding equals() and hashCode(). */
static boolean dnsEventsEqual(DnsEvent expected, DnsEvent got) {
return (expected == got) || ((expected != null) && (got != null)
&& (expected.netId == got.netId)
&& Arrays.equals(expected.eventTypes, got.eventTypes)
&& Arrays.equals(expected.returnCodes, got.returnCodes)
&& Arrays.equals(expected.latenciesMs, got.latenciesMs));
}
static String joinLines(String ... elems) {
StringBuilder b = new StringBuilder();
for (String s : elems) {
b.append(s).append("\n");
}
return b.toString();
}
static void verifyConnectEvent(String expected, IpConnectivityEvent got) {
try {
Arrays.sort(got.connectStatistics.latenciesMs);
Arrays.sort(got.connectStatistics.errnosCounters,
Comparator.comparingInt((p) -> p.key));
assertEquals(expected, got.toString());
} catch (Exception e) {
fail(e.toString());
}
}
}

View File

@@ -0,0 +1,310 @@
/*
* 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 static android.content.pm.UserInfo.FLAG_ADMIN;
import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE;
import static android.content.pm.UserInfo.FLAG_PRIMARY;
import static android.content.pm.UserInfo.FLAG_RESTRICTED;
import static org.mockito.AdditionalMatchers.*;
import static org.mockito.Mockito.*;
import android.annotation.UserIdInt;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.net.UidRange;
import android.os.INetworkManagementService;
import android.os.Looper;
import android.os.UserHandle;
import android.os.UserManager;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.ArrayMap;
import android.util.ArraySet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
/**
* Tests for {@link Vpn}.
*
* Build, install and run with:
* runtest --path src/com/android/server/connectivity/VpnTest.java
*/
public class VpnTest extends AndroidTestCase {
private static final String TAG = "VpnTest";
// Mock users
static final UserInfo primaryUser = new UserInfo(27, "Primary", FLAG_ADMIN | FLAG_PRIMARY);
static final UserInfo secondaryUser = new UserInfo(15, "Secondary", FLAG_ADMIN);
static final UserInfo restrictedProfileA = new UserInfo(40, "RestrictedA", FLAG_RESTRICTED);
static final UserInfo restrictedProfileB = new UserInfo(42, "RestrictedB", FLAG_RESTRICTED);
static final UserInfo managedProfileA = new UserInfo(45, "ManagedA", FLAG_MANAGED_PROFILE);
static {
restrictedProfileA.restrictedProfileParentId = primaryUser.id;
restrictedProfileB.restrictedProfileParentId = secondaryUser.id;
managedProfileA.profileGroupId = primaryUser.id;
}
/**
* Names and UIDs for some fake packages. Important points:
* - UID is ordered increasing.
* - One pair of packages have consecutive UIDs.
*/
static final String[] PKGS = {"com.example", "org.example", "net.example", "web.vpn"};
static final int[] PKG_UIDS = {66, 77, 78, 400};
// Mock packages
static final Map<String, Integer> mPackages = new ArrayMap<>();
static {
for (int i = 0; i < PKGS.length; i++) {
mPackages.put(PKGS[i], PKG_UIDS[i]);
}
}
@Mock private Context mContext;
@Mock private UserManager mUserManager;
@Mock private PackageManager mPackageManager;
@Mock private INetworkManagementService mNetService;
@Mock private AppOpsManager mAppOps;
@Override
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
when(mContext.getPackageManager()).thenReturn(mPackageManager);
setMockedPackages(mPackages);
when(mContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mUserManager);
when(mContext.getSystemService(eq(Context.APP_OPS_SERVICE))).thenReturn(mAppOps);
doNothing().when(mNetService).registerObserver(any());
}
@SmallTest
public void testRestrictedProfilesAreAddedToVpn() {
setMockedUsers(primaryUser, secondaryUser, restrictedProfileA, restrictedProfileB);
final Vpn vpn = new MockVpn(primaryUser.id);
final Set<UidRange> ranges = vpn.createUserAndRestrictedProfilesRanges(primaryUser.id,
null, null);
assertEquals(new ArraySet<>(Arrays.asList(new UidRange[] {
UidRange.createForUser(primaryUser.id),
UidRange.createForUser(restrictedProfileA.id)
})), ranges);
}
@SmallTest
public void testManagedProfilesAreNotAddedToVpn() {
setMockedUsers(primaryUser, managedProfileA);
final Vpn vpn = new MockVpn(primaryUser.id);
final Set<UidRange> ranges = vpn.createUserAndRestrictedProfilesRanges(primaryUser.id,
null, null);
assertEquals(new ArraySet<>(Arrays.asList(new UidRange[] {
UidRange.createForUser(primaryUser.id)
})), ranges);
}
@SmallTest
public void testAddUserToVpnOnlyAddsOneUser() {
setMockedUsers(primaryUser, restrictedProfileA, managedProfileA);
final Vpn vpn = new MockVpn(primaryUser.id);
final Set<UidRange> ranges = new ArraySet<>();
vpn.addUserToRanges(ranges, primaryUser.id, null, null);
assertEquals(new ArraySet<>(Arrays.asList(new UidRange[] {
UidRange.createForUser(primaryUser.id)
})), ranges);
}
@SmallTest
public void testUidWhiteAndBlacklist() throws Exception {
final Vpn vpn = new MockVpn(primaryUser.id);
final UidRange user = UidRange.createForUser(primaryUser.id);
final String[] packages = {PKGS[0], PKGS[1], PKGS[2]};
// Whitelist
final Set<UidRange> allow = vpn.createUserAndRestrictedProfilesRanges(primaryUser.id,
Arrays.asList(packages), null);
assertEquals(new ArraySet<>(Arrays.asList(new UidRange[] {
new UidRange(user.start + PKG_UIDS[0], user.start + PKG_UIDS[0]),
new UidRange(user.start + PKG_UIDS[1], user.start + PKG_UIDS[2])
})), allow);
// Blacklist
final Set<UidRange> disallow = vpn.createUserAndRestrictedProfilesRanges(primaryUser.id,
null, Arrays.asList(packages));
assertEquals(new ArraySet<>(Arrays.asList(new UidRange[] {
new UidRange(user.start, user.start + PKG_UIDS[0] - 1),
new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[1] - 1),
/* Empty range between UIDS[1] and UIDS[2], should be excluded, */
new UidRange(user.start + PKG_UIDS[2] + 1, user.stop)
})), disallow);
}
@SmallTest
public void testLockdownChangingPackage() throws Exception {
final MockVpn vpn = new MockVpn(primaryUser.id);
final UidRange user = UidRange.createForUser(primaryUser.id);
// Default state.
vpn.assertUnblocked(user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
// Set always-on without lockdown.
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false));
vpn.assertUnblocked(user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
// Set always-on with lockdown.
assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true));
verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
new UidRange(user.start, user.start + PKG_UIDS[1] - 1),
new UidRange(user.start + PKG_UIDS[1] + 1, user.stop)
}));
vpn.assertBlocked(user.start + PKG_UIDS[0], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
vpn.assertUnblocked(user.start + PKG_UIDS[1]);
// Switch to another app.
assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true));
verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
new UidRange(user.start, user.start + PKG_UIDS[1] - 1),
new UidRange(user.start + PKG_UIDS[1] + 1, user.stop)
}));
verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
new UidRange(user.start, user.start + PKG_UIDS[3] - 1),
new UidRange(user.start + PKG_UIDS[3] + 1, user.stop)
}));
vpn.assertBlocked(user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2]);
vpn.assertUnblocked(user.start + PKG_UIDS[3]);
}
@SmallTest
public void testLockdownAddingAProfile() throws Exception {
final MockVpn vpn = new MockVpn(primaryUser.id);
setMockedUsers(primaryUser);
// Make a copy of the restricted profile, as we're going to mark it deleted halfway through.
final UserInfo tempProfile = new UserInfo(restrictedProfileA.id, restrictedProfileA.name,
restrictedProfileA.flags);
tempProfile.restrictedProfileParentId = primaryUser.id;
final UidRange user = UidRange.createForUser(primaryUser.id);
final UidRange profile = UidRange.createForUser(tempProfile.id);
// Set lockdown.
assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true));
verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
new UidRange(user.start, user.start + PKG_UIDS[3] - 1),
new UidRange(user.start + PKG_UIDS[3] + 1, user.stop)
}));
// Verify restricted user isn't affected at first.
vpn.assertUnblocked(profile.start + PKG_UIDS[0]);
// Add the restricted user.
setMockedUsers(primaryUser, tempProfile);
vpn.onUserAdded(tempProfile.id);
verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
new UidRange(profile.start, profile.start + PKG_UIDS[3] - 1),
new UidRange(profile.start + PKG_UIDS[3] + 1, profile.stop)
}));
// Remove the restricted user.
tempProfile.partial = true;
vpn.onUserRemoved(tempProfile.id);
verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
new UidRange(profile.start, profile.start + PKG_UIDS[3] - 1),
new UidRange(profile.start + PKG_UIDS[3] + 1, profile.stop)
}));
}
/**
* A subclass of {@link Vpn} with some of the fields pre-mocked.
*/
private class MockVpn extends Vpn {
public MockVpn(@UserIdInt int userId) {
super(Looper.myLooper(), mContext, mNetService, userId);
}
public void assertBlocked(int... uids) {
for (int uid : uids) {
assertTrue("Uid " + uid + " should be blocked", isBlockingUid(uid));
}
}
public void assertUnblocked(int... uids) {
for (int uid : uids) {
assertFalse("Uid " + uid + " should not be blocked", isBlockingUid(uid));
}
}
}
/**
* Populate {@link #mUserManager} with a list of fake users.
*/
private void setMockedUsers(UserInfo... users) {
final Map<Integer, UserInfo> userMap = new ArrayMap<>();
for (UserInfo user : users) {
userMap.put(user.id, user);
}
/**
* @see UserManagerService#getUsers(boolean)
*/
doAnswer(invocation -> {
final boolean excludeDying = (boolean) invocation.getArguments()[0];
final ArrayList<UserInfo> result = new ArrayList<>(users.length);
for (UserInfo ui : users) {
if (!excludeDying || (ui.isEnabled() && !ui.partial)) {
result.add(ui);
}
}
return result;
}).when(mUserManager).getUsers(anyBoolean());
doAnswer(invocation -> {
final int id = (int) invocation.getArguments()[0];
return userMap.get(id);
}).when(mUserManager).getUserInfo(anyInt());
doAnswer(invocation -> {
final int id = (int) invocation.getArguments()[0];
return (userMap.get(id).flags & UserInfo.FLAG_ADMIN) != 0;
}).when(mUserManager).canHaveRestrictedProfile(anyInt());
}
/**
* Populate {@link #mPackageManager} with a fake packageName-to-UID mapping.
*/
private void setMockedPackages(final Map<String, Integer> packages) {
try {
doAnswer(invocation -> {
final String appName = (String) invocation.getArguments()[0];
final int userId = (int) invocation.getArguments()[1];
return UserHandle.getUid(userId, packages.get(appName));
}).when(mPackageManager).getPackageUidAsUser(anyString(), anyInt());
} catch (Exception e) {
}
}
}