Merge history of CTS
BUG: 167962976 Test: TH Merged-In: Ic89c084604788b4d41cf854e5015c8ce7791c64d Change-Id: I475d5fc915b1525f02fa70cb0694f21c3eb7d15f
This commit is contained in:
29
tests/cts/hostside/Android.bp
Normal file
29
tests/cts/hostside/Android.bp
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright (C) 2014 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.
|
||||
|
||||
java_test_host {
|
||||
name: "CtsHostsideNetworkTests",
|
||||
defaults: ["cts_defaults"],
|
||||
// Only compile source java files in this apk.
|
||||
srcs: ["src/**/*.java"],
|
||||
libs: [
|
||||
"cts-tradefed",
|
||||
"tradefed",
|
||||
],
|
||||
// Tag this module as a cts test artifact
|
||||
test_suites: [
|
||||
"cts",
|
||||
"general-tests",
|
||||
],
|
||||
}
|
||||
41
tests/cts/hostside/AndroidTest.xml
Normal file
41
tests/cts/hostside/AndroidTest.xml
Normal file
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2015 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.
|
||||
-->
|
||||
<configuration description="Config for CTS net host test cases">
|
||||
<option name="test-suite-tag" value="cts" />
|
||||
<option name="config-descriptor:metadata" key="component" value="networking" />
|
||||
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
|
||||
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
|
||||
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
|
||||
|
||||
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
|
||||
<target_preparer class="com.android.cts.net.NetworkPolicyTestsPreparer" />
|
||||
|
||||
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
|
||||
<option name="teardown-command" value="cmd power set-mode 0" />
|
||||
<option name="teardown-command" value="cmd battery reset" />
|
||||
<option name="teardown-command" value="cmd netpolicy stop-watching" />
|
||||
</target_preparer>
|
||||
|
||||
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
|
||||
<option name="jar" value="CtsHostsideNetworkTests.jar" />
|
||||
<option name="runtime-hint" value="3m56s" />
|
||||
</test>
|
||||
|
||||
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
|
||||
<option name="directory-keys" value="/sdcard/CtsHostsideNetworkTests" />
|
||||
<option name="collect-on-run-ended-only" value="true" />
|
||||
</metrics_collector>
|
||||
</configuration>
|
||||
4
tests/cts/hostside/OWNERS
Normal file
4
tests/cts/hostside/OWNERS
Normal file
@@ -0,0 +1,4 @@
|
||||
# Bug component: 61373
|
||||
sudheersai@google.com
|
||||
lorenzo@google.com
|
||||
jchalard@google.com
|
||||
24
tests/cts/hostside/aidl/Android.bp
Normal file
24
tests/cts/hostside/aidl/Android.bp
Normal file
@@ -0,0 +1,24 @@
|
||||
// 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.
|
||||
|
||||
java_test_helper_library {
|
||||
name: "CtsHostsideNetworkTestsAidl",
|
||||
sdk_version: "current",
|
||||
srcs: [
|
||||
"com/android/cts/net/hostside/IMyService.aidl",
|
||||
"com/android/cts/net/hostside/INetworkCallback.aidl",
|
||||
"com/android/cts/net/hostside/INetworkStateObserver.aidl",
|
||||
"com/android/cts/net/hostside/IRemoteSocketFactory.aidl",
|
||||
],
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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.cts.net.hostside;
|
||||
|
||||
import com.android.cts.net.hostside.INetworkCallback;
|
||||
|
||||
interface IMyService {
|
||||
void registerBroadcastReceiver();
|
||||
int getCounters(String receiverName, String action);
|
||||
String checkNetworkStatus();
|
||||
String getRestrictBackgroundStatus();
|
||||
void sendNotification(int notificationId, String notificationType);
|
||||
void registerNetworkCallback(in INetworkCallback cb);
|
||||
void unregisterNetworkCallback();
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.cts.net.hostside;
|
||||
|
||||
import android.net.Network;
|
||||
import android.net.NetworkCapabilities;
|
||||
|
||||
interface INetworkCallback {
|
||||
void onBlockedStatusChanged(in Network network, boolean blocked);
|
||||
void onAvailable(in Network network);
|
||||
void onLost(in Network network);
|
||||
void onCapabilitiesChanged(in Network network, in NetworkCapabilities cap);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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.cts.net.hostside;
|
||||
|
||||
interface INetworkStateObserver {
|
||||
boolean isForeground();
|
||||
void onNetworkStateChecked(String resultData);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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.cts.net.hostside;
|
||||
|
||||
import android.os.ParcelFileDescriptor;
|
||||
|
||||
interface IRemoteSocketFactory {
|
||||
ParcelFileDescriptor openSocketFd(String host, int port, int timeoutMs);
|
||||
String getPackageName();
|
||||
int getUid();
|
||||
}
|
||||
40
tests/cts/hostside/app/Android.bp
Normal file
40
tests/cts/hostside/app/Android.bp
Normal file
@@ -0,0 +1,40 @@
|
||||
//
|
||||
// Copyright (C) 2014 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.
|
||||
//
|
||||
|
||||
android_test_helper_app {
|
||||
name: "CtsHostsideNetworkTestsApp",
|
||||
defaults: ["cts_support_defaults"],
|
||||
//sdk_version: "current",
|
||||
platform_apis: true,
|
||||
static_libs: [
|
||||
"androidx.test.rules",
|
||||
"androidx.test.ext.junit",
|
||||
"compatibility-device-util-axt",
|
||||
"ctstestrunner-axt",
|
||||
"ub-uiautomator",
|
||||
"CtsHostsideNetworkTestsAidl",
|
||||
],
|
||||
libs: [
|
||||
"android.test.runner",
|
||||
"android.test.base",
|
||||
],
|
||||
srcs: ["src/**/*.java"],
|
||||
// Tag this module as a cts test artifact
|
||||
test_suites: [
|
||||
"cts",
|
||||
"general-tests",
|
||||
],
|
||||
}
|
||||
56
tests/cts/hostside/app/AndroidManifest.xml
Normal file
56
tests/cts/hostside/app/AndroidManifest.xml
Normal file
@@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2014 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.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.android.cts.net.hostside">
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
||||
|
||||
<application android:requestLegacyExternalStorage="true" >
|
||||
<uses-library android:name="android.test.runner" />
|
||||
<activity android:name=".MyActivity" />
|
||||
<service android:name=".MyVpnService"
|
||||
android:permission="android.permission.BIND_VPN_SERVICE">
|
||||
<intent-filter>
|
||||
<action android:name="android.net.VpnService"/>
|
||||
</intent-filter>
|
||||
</service>
|
||||
<service
|
||||
android:name=".MyNotificationListenerService"
|
||||
android:label="MyNotificationListenerService"
|
||||
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE" >
|
||||
<intent-filter>
|
||||
<action android:name="android.service.notification.NotificationListenerService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
</application>
|
||||
|
||||
<instrumentation
|
||||
android:name="androidx.test.runner.AndroidJUnitRunner"
|
||||
android:targetPackage="com.android.cts.net.hostside" />
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* 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.cts.net.hostside;
|
||||
|
||||
import static com.android.cts.net.hostside.Property.APP_STANDBY_MODE;
|
||||
import static com.android.cts.net.hostside.Property.BATTERY_SAVER_MODE;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import android.os.SystemClock;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Base class for metered and non-metered tests on idle apps.
|
||||
*/
|
||||
@RequiredProperties({APP_STANDBY_MODE})
|
||||
abstract class AbstractAppIdleTestCase extends AbstractRestrictBackgroundNetworkTestCase {
|
||||
|
||||
@Before
|
||||
public final void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
// Set initial state.
|
||||
removePowerSaveModeWhitelist(TEST_APP2_PKG);
|
||||
removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
|
||||
setAppIdle(false);
|
||||
turnBatteryOn();
|
||||
|
||||
registerBroadcastReceiver();
|
||||
}
|
||||
|
||||
@After
|
||||
public final void tearDown() throws Exception {
|
||||
super.tearDown();
|
||||
|
||||
executeSilentShellCommand("cmd battery reset");
|
||||
setAppIdle(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBackgroundNetworkAccess_enabled() throws Exception {
|
||||
setAppIdle(true);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
|
||||
assertsForegroundAlwaysHasNetworkAccess();
|
||||
setAppIdle(true);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
|
||||
// Make sure foreground app doesn't lose access upon enabling it.
|
||||
setAppIdle(true);
|
||||
launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_ACTIVTIY);
|
||||
finishActivity();
|
||||
assertAppIdle(false); // verify - not idle anymore, since activity was launched...
|
||||
assertBackgroundNetworkAccess(true);
|
||||
setAppIdle(true);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
|
||||
// Same for foreground service.
|
||||
setAppIdle(true);
|
||||
launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_FOREGROUND_SERVICE);
|
||||
stopForegroundService();
|
||||
assertAppIdle(true);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
|
||||
// Set Idle after foreground service start.
|
||||
launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_FOREGROUND_SERVICE);
|
||||
setAppIdle(true);
|
||||
addPowerSaveModeWhitelist(TEST_PKG);
|
||||
removePowerSaveModeWhitelist(TEST_PKG);
|
||||
assertForegroundServiceNetworkAccess();
|
||||
stopForegroundService();
|
||||
assertAppIdle(true);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBackgroundNetworkAccess_whitelisted() throws Exception {
|
||||
setAppIdle(true);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
|
||||
addPowerSaveModeWhitelist(TEST_APP2_PKG);
|
||||
assertAppIdle(false); // verify - not idle anymore, since whitelisted
|
||||
assertBackgroundNetworkAccess(true);
|
||||
|
||||
setAppIdleNoAssert(true);
|
||||
assertAppIdle(false); // app is still whitelisted
|
||||
removePowerSaveModeWhitelist(TEST_APP2_PKG);
|
||||
assertAppIdle(true); // verify - idle again, once whitelisted was removed
|
||||
assertBackgroundNetworkAccess(false);
|
||||
|
||||
setAppIdle(true);
|
||||
addPowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
|
||||
assertAppIdle(false); // verify - not idle anymore, since whitelisted
|
||||
assertBackgroundNetworkAccess(true);
|
||||
|
||||
setAppIdleNoAssert(true);
|
||||
assertAppIdle(false); // app is still whitelisted
|
||||
removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
|
||||
assertAppIdle(true); // verify - idle again, once whitelisted was removed
|
||||
assertBackgroundNetworkAccess(false);
|
||||
|
||||
assertsForegroundAlwaysHasNetworkAccess();
|
||||
|
||||
// verify - no whitelist, no access!
|
||||
setAppIdle(true);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBackgroundNetworkAccess_tempWhitelisted() throws Exception {
|
||||
setAppIdle(true);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
|
||||
addTempPowerSaveModeWhitelist(TEST_APP2_PKG, TEMP_POWERSAVE_WHITELIST_DURATION_MS);
|
||||
assertBackgroundNetworkAccess(true);
|
||||
// Wait until the whitelist duration is expired.
|
||||
SystemClock.sleep(TEMP_POWERSAVE_WHITELIST_DURATION_MS);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBackgroundNetworkAccess_disabled() throws Exception {
|
||||
assertBackgroundNetworkAccess(true);
|
||||
|
||||
assertsForegroundAlwaysHasNetworkAccess();
|
||||
assertBackgroundNetworkAccess(true);
|
||||
}
|
||||
|
||||
@RequiredProperties({BATTERY_SAVER_MODE})
|
||||
@Test
|
||||
public void testAppIdleNetworkAccess_whenCharging() throws Exception {
|
||||
// Check that app is paroled when charging
|
||||
setAppIdle(true);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
turnBatteryOff();
|
||||
assertBackgroundNetworkAccess(true);
|
||||
turnBatteryOn();
|
||||
assertBackgroundNetworkAccess(false);
|
||||
|
||||
// Check that app is restricted when not idle but power-save is on
|
||||
setAppIdle(false);
|
||||
assertBackgroundNetworkAccess(true);
|
||||
setBatterySaverMode(true);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
// Use setBatterySaverMode API to leave power-save mode instead of plugging in charger
|
||||
setBatterySaverMode(false);
|
||||
turnBatteryOff();
|
||||
assertBackgroundNetworkAccess(true);
|
||||
|
||||
// And when no longer charging, it still has network access, since it's not idle
|
||||
turnBatteryOn();
|
||||
assertBackgroundNetworkAccess(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAppIdleNetworkAccess_idleWhitelisted() throws Exception {
|
||||
setAppIdle(true);
|
||||
assertAppIdle(true);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
|
||||
addAppIdleWhitelist(mUid);
|
||||
assertBackgroundNetworkAccess(true);
|
||||
|
||||
removeAppIdleWhitelist(mUid);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
|
||||
// Make sure whitelisting a random app doesn't affect the tested app.
|
||||
addAppIdleWhitelist(mUid + 1);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
removeAppIdleWhitelist(mUid + 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAppIdle_toast() throws Exception {
|
||||
setAppIdle(true);
|
||||
assertAppIdle(true);
|
||||
assertEquals("Shown", showToast());
|
||||
assertAppIdle(true);
|
||||
// Wait for a couple of seconds for the toast to actually be shown
|
||||
SystemClock.sleep(2000);
|
||||
assertAppIdle(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* 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.cts.net.hostside;
|
||||
|
||||
import static com.android.cts.net.hostside.Property.BATTERY_SAVER_MODE;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Base class for metered and non-metered Battery Saver Mode tests.
|
||||
*/
|
||||
@RequiredProperties({BATTERY_SAVER_MODE})
|
||||
abstract class AbstractBatterySaverModeTestCase extends AbstractRestrictBackgroundNetworkTestCase {
|
||||
|
||||
@Before
|
||||
public final void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
// Set initial state.
|
||||
removePowerSaveModeWhitelist(TEST_APP2_PKG);
|
||||
removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
|
||||
setBatterySaverMode(false);
|
||||
|
||||
registerBroadcastReceiver();
|
||||
}
|
||||
|
||||
@After
|
||||
public final void tearDown() throws Exception {
|
||||
super.tearDown();
|
||||
|
||||
setBatterySaverMode(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBackgroundNetworkAccess_enabled() throws Exception {
|
||||
setBatterySaverMode(true);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
|
||||
assertsForegroundAlwaysHasNetworkAccess();
|
||||
assertBackgroundNetworkAccess(false);
|
||||
|
||||
// Make sure foreground app doesn't lose access upon Battery Saver.
|
||||
setBatterySaverMode(false);
|
||||
launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_ACTIVTIY);
|
||||
setBatterySaverMode(true);
|
||||
assertForegroundNetworkAccess();
|
||||
|
||||
// Although it should not have access while the screen is off.
|
||||
turnScreenOff();
|
||||
assertBackgroundNetworkAccess(false);
|
||||
turnScreenOn();
|
||||
assertForegroundNetworkAccess();
|
||||
|
||||
// Goes back to background state.
|
||||
finishActivity();
|
||||
assertBackgroundNetworkAccess(false);
|
||||
|
||||
// Make sure foreground service doesn't lose access upon enabling Battery Saver.
|
||||
setBatterySaverMode(false);
|
||||
launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_FOREGROUND_SERVICE);
|
||||
setBatterySaverMode(true);
|
||||
assertForegroundNetworkAccess();
|
||||
stopForegroundService();
|
||||
assertBackgroundNetworkAccess(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBackgroundNetworkAccess_whitelisted() throws Exception {
|
||||
setBatterySaverMode(true);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
|
||||
addPowerSaveModeWhitelist(TEST_APP2_PKG);
|
||||
assertBackgroundNetworkAccess(true);
|
||||
|
||||
removePowerSaveModeWhitelist(TEST_APP2_PKG);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
|
||||
addPowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
|
||||
assertBackgroundNetworkAccess(true);
|
||||
|
||||
removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
|
||||
assertsForegroundAlwaysHasNetworkAccess();
|
||||
assertBackgroundNetworkAccess(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBackgroundNetworkAccess_disabled() throws Exception {
|
||||
assertBackgroundNetworkAccess(true);
|
||||
|
||||
assertsForegroundAlwaysHasNetworkAccess();
|
||||
assertBackgroundNetworkAccess(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* 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.cts.net.hostside;
|
||||
|
||||
import static com.android.cts.net.hostside.Property.DOZE_MODE;
|
||||
import static com.android.cts.net.hostside.Property.NOT_LOW_RAM_DEVICE;
|
||||
|
||||
import android.os.SystemClock;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Base class for metered and non-metered Doze Mode tests.
|
||||
*/
|
||||
@RequiredProperties({DOZE_MODE})
|
||||
abstract class AbstractDozeModeTestCase extends AbstractRestrictBackgroundNetworkTestCase {
|
||||
|
||||
@Before
|
||||
public final void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
// Set initial state.
|
||||
removePowerSaveModeWhitelist(TEST_APP2_PKG);
|
||||
removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
|
||||
setDozeMode(false);
|
||||
|
||||
registerBroadcastReceiver();
|
||||
}
|
||||
|
||||
@After
|
||||
public final void tearDown() throws Exception {
|
||||
super.tearDown();
|
||||
|
||||
setDozeMode(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBackgroundNetworkAccess_enabled() throws Exception {
|
||||
setDozeMode(true);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
|
||||
assertsForegroundAlwaysHasNetworkAccess();
|
||||
assertBackgroundNetworkAccess(false);
|
||||
|
||||
// Make sure foreground service doesn't lose network access upon enabling doze.
|
||||
setDozeMode(false);
|
||||
launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_FOREGROUND_SERVICE);
|
||||
setDozeMode(true);
|
||||
assertForegroundNetworkAccess();
|
||||
stopForegroundService();
|
||||
assertBackgroundState();
|
||||
assertBackgroundNetworkAccess(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBackgroundNetworkAccess_whitelisted() throws Exception {
|
||||
setDozeMode(true);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
|
||||
addPowerSaveModeWhitelist(TEST_APP2_PKG);
|
||||
assertBackgroundNetworkAccess(true);
|
||||
|
||||
removePowerSaveModeWhitelist(TEST_APP2_PKG);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
|
||||
addPowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
|
||||
removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
|
||||
assertsForegroundAlwaysHasNetworkAccess();
|
||||
assertBackgroundNetworkAccess(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBackgroundNetworkAccess_disabled() throws Exception {
|
||||
assertBackgroundNetworkAccess(true);
|
||||
|
||||
assertsForegroundAlwaysHasNetworkAccess();
|
||||
assertBackgroundNetworkAccess(true);
|
||||
}
|
||||
|
||||
@RequiredProperties({NOT_LOW_RAM_DEVICE})
|
||||
@Test
|
||||
public void testBackgroundNetworkAccess_enabledButWhitelistedOnNotificationAction()
|
||||
throws Exception {
|
||||
setPendingIntentWhitelistDuration(NETWORK_TIMEOUT_MS);
|
||||
try {
|
||||
registerNotificationListenerService();
|
||||
setDozeMode(true);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
|
||||
testNotification(4, NOTIFICATION_TYPE_CONTENT);
|
||||
testNotification(8, NOTIFICATION_TYPE_DELETE);
|
||||
testNotification(15, NOTIFICATION_TYPE_FULL_SCREEN);
|
||||
testNotification(16, NOTIFICATION_TYPE_BUNDLE);
|
||||
testNotification(23, NOTIFICATION_TYPE_ACTION);
|
||||
testNotification(42, NOTIFICATION_TYPE_ACTION_BUNDLE);
|
||||
testNotification(108, NOTIFICATION_TYPE_ACTION_REMOTE_INPUT);
|
||||
} finally {
|
||||
resetDeviceIdleSettings();
|
||||
}
|
||||
}
|
||||
|
||||
private void testNotification(int id, String type) throws Exception {
|
||||
sendNotification(id, type);
|
||||
assertBackgroundNetworkAccess(true);
|
||||
if (type.equals(NOTIFICATION_TYPE_ACTION)) {
|
||||
// Make sure access is disabled after it expires. Since this check considerably slows
|
||||
// downs the CTS tests, do it just once.
|
||||
SystemClock.sleep(NETWORK_TIMEOUT_MS);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Must override so it only tests foreground service - once an app goes to foreground, device
|
||||
// leaves Doze Mode.
|
||||
@Override
|
||||
protected void assertsForegroundAlwaysHasNetworkAccess() throws Exception {
|
||||
launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_FOREGROUND_SERVICE);
|
||||
stopForegroundService();
|
||||
assertBackgroundState();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,880 @@
|
||||
/*
|
||||
* 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.cts.net.hostside;
|
||||
|
||||
import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
|
||||
import static android.os.BatteryManager.BATTERY_PLUGGED_AC;
|
||||
import static android.os.BatteryManager.BATTERY_PLUGGED_USB;
|
||||
import static android.os.BatteryManager.BATTERY_PLUGGED_WIRELESS;
|
||||
|
||||
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.executeShellCommand;
|
||||
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getConnectivityManager;
|
||||
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getContext;
|
||||
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getInstrumentation;
|
||||
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getWifiManager;
|
||||
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isDozeModeSupported;
|
||||
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.restrictBackgroundValueToString;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.Instrumentation;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo.DetailedState;
|
||||
import android.net.NetworkInfo.State;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.os.BatteryManager;
|
||||
import android.os.Binder;
|
||||
import android.os.Bundle;
|
||||
import android.os.SystemClock;
|
||||
import android.provider.Settings;
|
||||
import android.service.notification.NotificationListenerService;
|
||||
import android.util.Log;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.rules.RuleChain;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Superclass for tests related to background network restrictions.
|
||||
*/
|
||||
@RunWith(NetworkPolicyTestRunner.class)
|
||||
public abstract class AbstractRestrictBackgroundNetworkTestCase {
|
||||
public static final String TAG = "RestrictBackgroundNetworkTests";
|
||||
|
||||
protected static final String TEST_PKG = "com.android.cts.net.hostside";
|
||||
protected static final String TEST_APP2_PKG = "com.android.cts.net.hostside.app2";
|
||||
|
||||
private static final String TEST_APP2_ACTIVITY_CLASS = TEST_APP2_PKG + ".MyActivity";
|
||||
private static final String TEST_APP2_SERVICE_CLASS = TEST_APP2_PKG + ".MyForegroundService";
|
||||
|
||||
private static final int SLEEP_TIME_SEC = 1;
|
||||
|
||||
// Constants below must match values defined on app2's Common.java
|
||||
private static final String MANIFEST_RECEIVER = "ManifestReceiver";
|
||||
private static final String DYNAMIC_RECEIVER = "DynamicReceiver";
|
||||
|
||||
private static final String ACTION_RECEIVER_READY =
|
||||
"com.android.cts.net.hostside.app2.action.RECEIVER_READY";
|
||||
static final String ACTION_SHOW_TOAST =
|
||||
"com.android.cts.net.hostside.app2.action.SHOW_TOAST";
|
||||
|
||||
protected static final String NOTIFICATION_TYPE_CONTENT = "CONTENT";
|
||||
protected static final String NOTIFICATION_TYPE_DELETE = "DELETE";
|
||||
protected static final String NOTIFICATION_TYPE_FULL_SCREEN = "FULL_SCREEN";
|
||||
protected static final String NOTIFICATION_TYPE_BUNDLE = "BUNDLE";
|
||||
protected static final String NOTIFICATION_TYPE_ACTION = "ACTION";
|
||||
protected static final String NOTIFICATION_TYPE_ACTION_BUNDLE = "ACTION_BUNDLE";
|
||||
protected static final String NOTIFICATION_TYPE_ACTION_REMOTE_INPUT = "ACTION_REMOTE_INPUT";
|
||||
|
||||
// TODO: Update BatteryManager.BATTERY_PLUGGED_ANY as @TestApi
|
||||
public static final int BATTERY_PLUGGED_ANY =
|
||||
BATTERY_PLUGGED_AC | BATTERY_PLUGGED_USB | BATTERY_PLUGGED_WIRELESS;
|
||||
|
||||
private static final String NETWORK_STATUS_SEPARATOR = "\\|";
|
||||
private static final int SECOND_IN_MS = 1000;
|
||||
static final int NETWORK_TIMEOUT_MS = 15 * SECOND_IN_MS;
|
||||
private static int PROCESS_STATE_FOREGROUND_SERVICE;
|
||||
|
||||
private static final String KEY_NETWORK_STATE_OBSERVER = TEST_PKG + ".observer";
|
||||
|
||||
protected static final int TYPE_COMPONENT_ACTIVTIY = 0;
|
||||
protected static final int TYPE_COMPONENT_FOREGROUND_SERVICE = 1;
|
||||
|
||||
private static final int BATTERY_STATE_TIMEOUT_MS = 5000;
|
||||
private static final int BATTERY_STATE_CHECK_INTERVAL_MS = 500;
|
||||
|
||||
private static final int FOREGROUND_PROC_NETWORK_TIMEOUT_MS = 6000;
|
||||
|
||||
// Must be higher than NETWORK_TIMEOUT_MS
|
||||
private static final int ORDERED_BROADCAST_TIMEOUT_MS = NETWORK_TIMEOUT_MS * 4;
|
||||
|
||||
private static final IntentFilter BATTERY_CHANGED_FILTER =
|
||||
new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
|
||||
|
||||
private static final String APP_NOT_FOREGROUND_ERROR = "app_not_fg";
|
||||
|
||||
protected static final long TEMP_POWERSAVE_WHITELIST_DURATION_MS = 5_000; // 5 sec
|
||||
|
||||
protected Context mContext;
|
||||
protected Instrumentation mInstrumentation;
|
||||
protected ConnectivityManager mCm;
|
||||
protected int mUid;
|
||||
private int mMyUid;
|
||||
private MyServiceClient mServiceClient;
|
||||
private String mDeviceIdleConstantsSetting;
|
||||
|
||||
@Rule
|
||||
public final RuleChain mRuleChain = RuleChain.outerRule(new RequiredPropertiesRule())
|
||||
.around(new MeterednessConfigurationRule());
|
||||
|
||||
protected void setUp() throws Exception {
|
||||
|
||||
PROCESS_STATE_FOREGROUND_SERVICE = (Integer) ActivityManager.class
|
||||
.getDeclaredField("PROCESS_STATE_FOREGROUND_SERVICE").get(null);
|
||||
mInstrumentation = getInstrumentation();
|
||||
mContext = getContext();
|
||||
mCm = getConnectivityManager();
|
||||
mUid = getUid(TEST_APP2_PKG);
|
||||
mMyUid = getUid(mContext.getPackageName());
|
||||
mServiceClient = new MyServiceClient(mContext);
|
||||
mServiceClient.bind();
|
||||
mDeviceIdleConstantsSetting = "device_idle_constants";
|
||||
executeShellCommand("cmd netpolicy start-watching " + mUid);
|
||||
setAppIdle(false);
|
||||
|
||||
Log.i(TAG, "Apps status:\n"
|
||||
+ "\ttest app: uid=" + mMyUid + ", state=" + getProcessStateByUid(mMyUid) + "\n"
|
||||
+ "\tapp2: uid=" + mUid + ", state=" + getProcessStateByUid(mUid));
|
||||
}
|
||||
|
||||
protected void tearDown() throws Exception {
|
||||
executeShellCommand("cmd netpolicy stop-watching");
|
||||
mServiceClient.unbind();
|
||||
}
|
||||
|
||||
protected int getUid(String packageName) throws Exception {
|
||||
return mContext.getPackageManager().getPackageUid(packageName, 0);
|
||||
}
|
||||
|
||||
protected void assertRestrictBackgroundChangedReceived(int expectedCount) throws Exception {
|
||||
assertRestrictBackgroundChangedReceived(DYNAMIC_RECEIVER, expectedCount);
|
||||
assertRestrictBackgroundChangedReceived(MANIFEST_RECEIVER, 0);
|
||||
}
|
||||
|
||||
protected void assertRestrictBackgroundChangedReceived(String receiverName, int expectedCount)
|
||||
throws Exception {
|
||||
int attempts = 0;
|
||||
int count = 0;
|
||||
final int maxAttempts = 5;
|
||||
do {
|
||||
attempts++;
|
||||
count = getNumberBroadcastsReceived(receiverName, ACTION_RESTRICT_BACKGROUND_CHANGED);
|
||||
assertFalse("Expected count " + expectedCount + " but actual is " + count,
|
||||
count > expectedCount);
|
||||
if (count == expectedCount) {
|
||||
break;
|
||||
}
|
||||
Log.d(TAG, "Expecting count " + expectedCount + " but actual is " + count + " after "
|
||||
+ attempts + " attempts; sleeping "
|
||||
+ SLEEP_TIME_SEC + " seconds before trying again");
|
||||
SystemClock.sleep(SLEEP_TIME_SEC * SECOND_IN_MS);
|
||||
} while (attempts <= maxAttempts);
|
||||
assertEquals("Number of expected broadcasts for " + receiverName + " not reached after "
|
||||
+ maxAttempts * SLEEP_TIME_SEC + " seconds", expectedCount, count);
|
||||
}
|
||||
|
||||
protected String sendOrderedBroadcast(Intent intent) throws Exception {
|
||||
return sendOrderedBroadcast(intent, ORDERED_BROADCAST_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
protected String sendOrderedBroadcast(Intent intent, int timeoutMs) throws Exception {
|
||||
final LinkedBlockingQueue<String> result = new LinkedBlockingQueue<>(1);
|
||||
Log.d(TAG, "Sending ordered broadcast: " + intent);
|
||||
mContext.sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
final String resultData = getResultData();
|
||||
if (resultData == null) {
|
||||
Log.e(TAG, "Received null data from ordered intent");
|
||||
return;
|
||||
}
|
||||
result.offer(resultData);
|
||||
}
|
||||
}, null, 0, null, null);
|
||||
|
||||
final String resultData = result.poll(timeoutMs, TimeUnit.MILLISECONDS);
|
||||
Log.d(TAG, "Ordered broadcast response after " + timeoutMs + "ms: " + resultData );
|
||||
return resultData;
|
||||
}
|
||||
|
||||
protected int getNumberBroadcastsReceived(String receiverName, String action) throws Exception {
|
||||
return mServiceClient.getCounters(receiverName, action);
|
||||
}
|
||||
|
||||
protected void assertRestrictBackgroundStatus(int expectedStatus) throws Exception {
|
||||
final String status = mServiceClient.getRestrictBackgroundStatus();
|
||||
assertNotNull("didn't get API status from app2", status);
|
||||
assertEquals(restrictBackgroundValueToString(expectedStatus),
|
||||
restrictBackgroundValueToString(Integer.parseInt(status)));
|
||||
}
|
||||
|
||||
protected void assertBackgroundNetworkAccess(boolean expectAllowed) throws Exception {
|
||||
assertBackgroundState();
|
||||
assertNetworkAccess(expectAllowed /* expectAvailable */, false /* needScreenOn */);
|
||||
}
|
||||
|
||||
protected void assertForegroundNetworkAccess() throws Exception {
|
||||
assertForegroundState();
|
||||
// We verified that app is in foreground state but if the screen turns-off while
|
||||
// verifying for network access, the app will go into background state (in case app's
|
||||
// foreground status was due to top activity). So, turn the screen on when verifying
|
||||
// network connectivity.
|
||||
assertNetworkAccess(true /* expectAvailable */, true /* needScreenOn */);
|
||||
}
|
||||
|
||||
protected void assertForegroundServiceNetworkAccess() throws Exception {
|
||||
assertForegroundServiceState();
|
||||
assertNetworkAccess(true /* expectAvailable */, false /* needScreenOn */);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that an app always have access while on foreground or running a foreground service.
|
||||
*
|
||||
* <p>This method will launch an activity and a foreground service to make the assertion, but
|
||||
* will finish the activity / stop the service afterwards.
|
||||
*/
|
||||
protected void assertsForegroundAlwaysHasNetworkAccess() throws Exception{
|
||||
// Checks foreground first.
|
||||
launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_ACTIVTIY);
|
||||
finishActivity();
|
||||
|
||||
// Then foreground service
|
||||
launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_FOREGROUND_SERVICE);
|
||||
stopForegroundService();
|
||||
}
|
||||
|
||||
protected final void assertBackgroundState() throws Exception {
|
||||
final int maxTries = 30;
|
||||
ProcessState state = null;
|
||||
for (int i = 1; i <= maxTries; i++) {
|
||||
state = getProcessStateByUid(mUid);
|
||||
Log.v(TAG, "assertBackgroundState(): status for app2 (" + mUid + ") on attempt #" + i
|
||||
+ ": " + state);
|
||||
if (isBackground(state.state)) {
|
||||
return;
|
||||
}
|
||||
Log.d(TAG, "App not on background state (" + state + ") on attempt #" + i
|
||||
+ "; sleeping 1s before trying again");
|
||||
SystemClock.sleep(SECOND_IN_MS);
|
||||
}
|
||||
fail("App2 is not on background state after " + maxTries + " attempts: " + state );
|
||||
}
|
||||
|
||||
protected final void assertForegroundState() throws Exception {
|
||||
final int maxTries = 30;
|
||||
ProcessState state = null;
|
||||
for (int i = 1; i <= maxTries; i++) {
|
||||
state = getProcessStateByUid(mUid);
|
||||
Log.v(TAG, "assertForegroundState(): status for app2 (" + mUid + ") on attempt #" + i
|
||||
+ ": " + state);
|
||||
if (!isBackground(state.state)) {
|
||||
return;
|
||||
}
|
||||
Log.d(TAG, "App not on foreground state on attempt #" + i
|
||||
+ "; sleeping 1s before trying again");
|
||||
turnScreenOn();
|
||||
SystemClock.sleep(SECOND_IN_MS);
|
||||
}
|
||||
fail("App2 is not on foreground state after " + maxTries + " attempts: " + state );
|
||||
}
|
||||
|
||||
protected final void assertForegroundServiceState() throws Exception {
|
||||
final int maxTries = 30;
|
||||
ProcessState state = null;
|
||||
for (int i = 1; i <= maxTries; i++) {
|
||||
state = getProcessStateByUid(mUid);
|
||||
Log.v(TAG, "assertForegroundServiceState(): status for app2 (" + mUid + ") on attempt #"
|
||||
+ i + ": " + state);
|
||||
if (state.state == PROCESS_STATE_FOREGROUND_SERVICE) {
|
||||
return;
|
||||
}
|
||||
Log.d(TAG, "App not on foreground service state on attempt #" + i
|
||||
+ "; sleeping 1s before trying again");
|
||||
SystemClock.sleep(SECOND_IN_MS);
|
||||
}
|
||||
fail("App2 is not on foreground service state after " + maxTries + " attempts: " + state );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether an app state should be considered "background" for restriction purposes.
|
||||
*/
|
||||
protected boolean isBackground(int state) {
|
||||
return state > PROCESS_STATE_FOREGROUND_SERVICE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts whether the active network is available or not.
|
||||
*/
|
||||
private void assertNetworkAccess(boolean expectAvailable, boolean needScreenOn)
|
||||
throws Exception {
|
||||
final int maxTries = 5;
|
||||
String error = null;
|
||||
int timeoutMs = 500;
|
||||
|
||||
for (int i = 1; i <= maxTries; i++) {
|
||||
error = checkNetworkAccess(expectAvailable);
|
||||
|
||||
if (error.isEmpty()) return;
|
||||
|
||||
// TODO: ideally, it should retry only when it cannot connect to an external site,
|
||||
// or no retry at all! But, currently, the initial change fails almost always on
|
||||
// battery saver tests because the netd changes are made asynchronously.
|
||||
// Once b/27803922 is fixed, this retry mechanism should be revisited.
|
||||
|
||||
Log.w(TAG, "Network status didn't match for expectAvailable=" + expectAvailable
|
||||
+ " on attempt #" + i + ": " + error + "\n"
|
||||
+ "Sleeping " + timeoutMs + "ms before trying again");
|
||||
if (needScreenOn) {
|
||||
turnScreenOn();
|
||||
}
|
||||
// No sleep after the last turn
|
||||
if (i < maxTries) {
|
||||
SystemClock.sleep(timeoutMs);
|
||||
}
|
||||
// Exponential back-off.
|
||||
timeoutMs = Math.min(timeoutMs*2, NETWORK_TIMEOUT_MS);
|
||||
}
|
||||
fail("Invalid state for expectAvailable=" + expectAvailable + " after " + maxTries
|
||||
+ " attempts.\nLast error: " + error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the network is available as expected.
|
||||
*
|
||||
* @return error message with the mismatch (or empty if assertion passed).
|
||||
*/
|
||||
private String checkNetworkAccess(boolean expectAvailable) throws Exception {
|
||||
final String resultData = mServiceClient.checkNetworkStatus();
|
||||
return checkForAvailabilityInResultData(resultData, expectAvailable);
|
||||
}
|
||||
|
||||
private String checkForAvailabilityInResultData(String resultData, boolean expectAvailable) {
|
||||
if (resultData == null) {
|
||||
assertNotNull("Network status from app2 is null", resultData);
|
||||
}
|
||||
// Network status format is described on MyBroadcastReceiver.checkNetworkStatus()
|
||||
final String[] parts = resultData.split(NETWORK_STATUS_SEPARATOR);
|
||||
assertEquals("Wrong network status: " + resultData, 5, parts.length);
|
||||
final State state = parts[0].equals("null") ? null : State.valueOf(parts[0]);
|
||||
final DetailedState detailedState = parts[1].equals("null")
|
||||
? null : DetailedState.valueOf(parts[1]);
|
||||
final boolean connected = Boolean.valueOf(parts[2]);
|
||||
final String connectionCheckDetails = parts[3];
|
||||
final String networkInfo = parts[4];
|
||||
|
||||
final StringBuilder errors = new StringBuilder();
|
||||
final State expectedState;
|
||||
final DetailedState expectedDetailedState;
|
||||
if (expectAvailable) {
|
||||
expectedState = State.CONNECTED;
|
||||
expectedDetailedState = DetailedState.CONNECTED;
|
||||
} else {
|
||||
expectedState = State.DISCONNECTED;
|
||||
expectedDetailedState = DetailedState.BLOCKED;
|
||||
}
|
||||
|
||||
if (expectAvailable != connected) {
|
||||
errors.append(String.format("External site connection failed: expected %s, got %s\n",
|
||||
expectAvailable, connected));
|
||||
}
|
||||
if (expectedState != state || expectedDetailedState != detailedState) {
|
||||
errors.append(String.format("Connection state mismatch: expected %s/%s, got %s/%s\n",
|
||||
expectedState, expectedDetailedState, state, detailedState));
|
||||
}
|
||||
|
||||
if (errors.length() > 0) {
|
||||
errors.append("\tnetworkInfo: " + networkInfo + "\n");
|
||||
errors.append("\tconnectionCheckDetails: " + connectionCheckDetails + "\n");
|
||||
}
|
||||
return errors.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a Shell command which is not expected to generate output.
|
||||
*/
|
||||
protected void executeSilentShellCommand(String command) {
|
||||
final String result = executeShellCommand(command);
|
||||
assertTrue("Command '" + command + "' failed: " + result, result.trim().isEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts the result of a command, wait and re-running it a couple times if necessary.
|
||||
*/
|
||||
protected void assertDelayedShellCommand(String command, final String expectedResult)
|
||||
throws Exception {
|
||||
assertDelayedShellCommand(command, 5, 1, expectedResult);
|
||||
}
|
||||
|
||||
protected void assertDelayedShellCommand(String command, int maxTries, int napTimeSeconds,
|
||||
final String expectedResult) throws Exception {
|
||||
assertDelayedShellCommand(command, maxTries, napTimeSeconds, new ExpectResultChecker() {
|
||||
|
||||
@Override
|
||||
public boolean isExpected(String result) {
|
||||
return expectedResult.equals(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExpected() {
|
||||
return expectedResult;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void assertDelayedShellCommand(String command, int maxTries, int napTimeSeconds,
|
||||
ExpectResultChecker checker) throws Exception {
|
||||
String result = "";
|
||||
for (int i = 1; i <= maxTries; i++) {
|
||||
result = executeShellCommand(command).trim();
|
||||
if (checker.isExpected(result)) return;
|
||||
Log.v(TAG, "Command '" + command + "' returned '" + result + " instead of '"
|
||||
+ checker.getExpected() + "' on attempt #" + i
|
||||
+ "; sleeping " + napTimeSeconds + "s before trying again");
|
||||
SystemClock.sleep(napTimeSeconds * SECOND_IN_MS);
|
||||
}
|
||||
fail("Command '" + command + "' did not return '" + checker.getExpected() + "' after "
|
||||
+ maxTries
|
||||
+ " attempts. Last result: '" + result + "'");
|
||||
}
|
||||
|
||||
protected void addRestrictBackgroundWhitelist(int uid) throws Exception {
|
||||
executeShellCommand("cmd netpolicy add restrict-background-whitelist " + uid);
|
||||
assertRestrictBackgroundWhitelist(uid, true);
|
||||
// UID policies live by the Highlander rule: "There can be only one".
|
||||
// Hence, if app is whitelisted, it should not be blacklisted.
|
||||
assertRestrictBackgroundBlacklist(uid, false);
|
||||
}
|
||||
|
||||
protected void removeRestrictBackgroundWhitelist(int uid) throws Exception {
|
||||
executeShellCommand("cmd netpolicy remove restrict-background-whitelist " + uid);
|
||||
assertRestrictBackgroundWhitelist(uid, false);
|
||||
}
|
||||
|
||||
protected void assertRestrictBackgroundWhitelist(int uid, boolean expected) throws Exception {
|
||||
assertRestrictBackground("restrict-background-whitelist", uid, expected);
|
||||
}
|
||||
|
||||
protected void addRestrictBackgroundBlacklist(int uid) throws Exception {
|
||||
executeShellCommand("cmd netpolicy add restrict-background-blacklist " + uid);
|
||||
assertRestrictBackgroundBlacklist(uid, true);
|
||||
// UID policies live by the Highlander rule: "There can be only one".
|
||||
// Hence, if app is blacklisted, it should not be whitelisted.
|
||||
assertRestrictBackgroundWhitelist(uid, false);
|
||||
}
|
||||
|
||||
protected void removeRestrictBackgroundBlacklist(int uid) throws Exception {
|
||||
executeShellCommand("cmd netpolicy remove restrict-background-blacklist " + uid);
|
||||
assertRestrictBackgroundBlacklist(uid, false);
|
||||
}
|
||||
|
||||
protected void assertRestrictBackgroundBlacklist(int uid, boolean expected) throws Exception {
|
||||
assertRestrictBackground("restrict-background-blacklist", uid, expected);
|
||||
}
|
||||
|
||||
protected void addAppIdleWhitelist(int uid) throws Exception {
|
||||
executeShellCommand("cmd netpolicy add app-idle-whitelist " + uid);
|
||||
assertAppIdleWhitelist(uid, true);
|
||||
}
|
||||
|
||||
protected void removeAppIdleWhitelist(int uid) throws Exception {
|
||||
executeShellCommand("cmd netpolicy remove app-idle-whitelist " + uid);
|
||||
assertAppIdleWhitelist(uid, false);
|
||||
}
|
||||
|
||||
protected void assertAppIdleWhitelist(int uid, boolean expected) throws Exception {
|
||||
assertRestrictBackground("app-idle-whitelist", uid, expected);
|
||||
}
|
||||
|
||||
private void assertRestrictBackground(String list, int uid, boolean expected) throws Exception {
|
||||
final int maxTries = 5;
|
||||
boolean actual = false;
|
||||
final String expectedUid = Integer.toString(uid);
|
||||
String uids = "";
|
||||
for (int i = 1; i <= maxTries; i++) {
|
||||
final String output =
|
||||
executeShellCommand("cmd netpolicy list " + list);
|
||||
uids = output.split(":")[1];
|
||||
for (String candidate : uids.split(" ")) {
|
||||
actual = candidate.trim().equals(expectedUid);
|
||||
if (expected == actual) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
Log.v(TAG, list + " check for uid " + uid + " doesn't match yet (expected "
|
||||
+ expected + ", got " + actual + "); sleeping 1s before polling again");
|
||||
SystemClock.sleep(SECOND_IN_MS);
|
||||
}
|
||||
fail(list + " check for uid " + uid + " failed: expected " + expected + ", got " + actual
|
||||
+ ". Full list: " + uids);
|
||||
}
|
||||
|
||||
protected void addTempPowerSaveModeWhitelist(String packageName, long duration)
|
||||
throws Exception {
|
||||
Log.i(TAG, "Adding pkg " + packageName + " to temp-power-save-mode whitelist");
|
||||
executeShellCommand("dumpsys deviceidle tempwhitelist -d " + duration + " " + packageName);
|
||||
}
|
||||
|
||||
protected void assertPowerSaveModeWhitelist(String packageName, boolean expected)
|
||||
throws Exception {
|
||||
// TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
|
||||
// need to use netpolicy for whitelisting
|
||||
assertDelayedShellCommand("dumpsys deviceidle whitelist =" + packageName,
|
||||
Boolean.toString(expected));
|
||||
}
|
||||
|
||||
protected void addPowerSaveModeWhitelist(String packageName) throws Exception {
|
||||
Log.i(TAG, "Adding package " + packageName + " to power-save-mode whitelist");
|
||||
// TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
|
||||
// need to use netpolicy for whitelisting
|
||||
executeShellCommand("dumpsys deviceidle whitelist +" + packageName);
|
||||
assertPowerSaveModeWhitelist(packageName, true);
|
||||
}
|
||||
|
||||
protected void removePowerSaveModeWhitelist(String packageName) throws Exception {
|
||||
Log.i(TAG, "Removing package " + packageName + " from power-save-mode whitelist");
|
||||
// TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
|
||||
// need to use netpolicy for whitelisting
|
||||
executeShellCommand("dumpsys deviceidle whitelist -" + packageName);
|
||||
assertPowerSaveModeWhitelist(packageName, false);
|
||||
}
|
||||
|
||||
protected void assertPowerSaveModeExceptIdleWhitelist(String packageName, boolean expected)
|
||||
throws Exception {
|
||||
// TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
|
||||
// need to use netpolicy for whitelisting
|
||||
assertDelayedShellCommand("dumpsys deviceidle except-idle-whitelist =" + packageName,
|
||||
Boolean.toString(expected));
|
||||
}
|
||||
|
||||
protected void addPowerSaveModeExceptIdleWhitelist(String packageName) throws Exception {
|
||||
Log.i(TAG, "Adding package " + packageName + " to power-save-mode-except-idle whitelist");
|
||||
// TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
|
||||
// need to use netpolicy for whitelisting
|
||||
executeShellCommand("dumpsys deviceidle except-idle-whitelist +" + packageName);
|
||||
assertPowerSaveModeExceptIdleWhitelist(packageName, true);
|
||||
}
|
||||
|
||||
protected void removePowerSaveModeExceptIdleWhitelist(String packageName) throws Exception {
|
||||
Log.i(TAG, "Removing package " + packageName
|
||||
+ " from power-save-mode-except-idle whitelist");
|
||||
// TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
|
||||
// need to use netpolicy for whitelisting
|
||||
executeShellCommand("dumpsys deviceidle except-idle-whitelist reset");
|
||||
assertPowerSaveModeExceptIdleWhitelist(packageName, false);
|
||||
}
|
||||
|
||||
protected void turnBatteryOn() throws Exception {
|
||||
executeSilentShellCommand("cmd battery unplug");
|
||||
executeSilentShellCommand("cmd battery set status "
|
||||
+ BatteryManager.BATTERY_STATUS_DISCHARGING);
|
||||
assertBatteryState(false);
|
||||
}
|
||||
|
||||
protected void turnBatteryOff() throws Exception {
|
||||
executeSilentShellCommand("cmd battery set ac " + BATTERY_PLUGGED_ANY);
|
||||
executeSilentShellCommand("cmd battery set level 100");
|
||||
executeSilentShellCommand("cmd battery set status "
|
||||
+ BatteryManager.BATTERY_STATUS_CHARGING);
|
||||
assertBatteryState(true);
|
||||
}
|
||||
|
||||
private void assertBatteryState(boolean pluggedIn) throws Exception {
|
||||
final long endTime = SystemClock.elapsedRealtime() + BATTERY_STATE_TIMEOUT_MS;
|
||||
while (isDevicePluggedIn() != pluggedIn && SystemClock.elapsedRealtime() <= endTime) {
|
||||
Thread.sleep(BATTERY_STATE_CHECK_INTERVAL_MS);
|
||||
}
|
||||
if (isDevicePluggedIn() != pluggedIn) {
|
||||
fail("Timed out waiting for the plugged-in state to change,"
|
||||
+ " expected pluggedIn: " + pluggedIn);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isDevicePluggedIn() {
|
||||
final Intent batteryIntent = mContext.registerReceiver(null, BATTERY_CHANGED_FILTER);
|
||||
return batteryIntent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) > 0;
|
||||
}
|
||||
|
||||
protected void turnScreenOff() throws Exception {
|
||||
executeSilentShellCommand("input keyevent KEYCODE_SLEEP");
|
||||
}
|
||||
|
||||
protected void turnScreenOn() throws Exception {
|
||||
executeSilentShellCommand("input keyevent KEYCODE_WAKEUP");
|
||||
executeSilentShellCommand("wm dismiss-keyguard");
|
||||
}
|
||||
|
||||
protected void setBatterySaverMode(boolean enabled) throws Exception {
|
||||
Log.i(TAG, "Setting Battery Saver Mode to " + enabled);
|
||||
if (enabled) {
|
||||
turnBatteryOn();
|
||||
executeSilentShellCommand("cmd power set-mode 1");
|
||||
} else {
|
||||
executeSilentShellCommand("cmd power set-mode 0");
|
||||
turnBatteryOff();
|
||||
}
|
||||
}
|
||||
|
||||
protected void setDozeMode(boolean enabled) throws Exception {
|
||||
// Check doze mode is supported.
|
||||
assertTrue("Device does not support Doze Mode", isDozeModeSupported());
|
||||
|
||||
Log.i(TAG, "Setting Doze Mode to " + enabled);
|
||||
if (enabled) {
|
||||
turnBatteryOn();
|
||||
turnScreenOff();
|
||||
executeShellCommand("dumpsys deviceidle force-idle deep");
|
||||
} else {
|
||||
turnScreenOn();
|
||||
turnBatteryOff();
|
||||
executeShellCommand("dumpsys deviceidle unforce");
|
||||
}
|
||||
assertDozeMode(enabled);
|
||||
}
|
||||
|
||||
protected void assertDozeMode(boolean enabled) throws Exception {
|
||||
assertDelayedShellCommand("dumpsys deviceidle get deep", enabled ? "IDLE" : "ACTIVE");
|
||||
}
|
||||
|
||||
protected void setAppIdle(boolean enabled) throws Exception {
|
||||
Log.i(TAG, "Setting app idle to " + enabled);
|
||||
executeSilentShellCommand("am set-inactive " + TEST_APP2_PKG + " " + enabled );
|
||||
assertAppIdle(enabled);
|
||||
}
|
||||
|
||||
protected void setAppIdleNoAssert(boolean enabled) throws Exception {
|
||||
Log.i(TAG, "Setting app idle to " + enabled);
|
||||
executeSilentShellCommand("am set-inactive " + TEST_APP2_PKG + " " + enabled );
|
||||
}
|
||||
|
||||
protected void assertAppIdle(boolean enabled) throws Exception {
|
||||
try {
|
||||
assertDelayedShellCommand("am get-inactive " + TEST_APP2_PKG, 15, 2, "Idle=" + enabled);
|
||||
} catch (Throwable e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a service that will register a broadcast receiver to receive
|
||||
* {@code RESTRICT_BACKGROUND_CHANGE} intents.
|
||||
* <p>
|
||||
* The service must run in a separate app because otherwise it would be killed every time
|
||||
* {@link #runDeviceTests(String, String)} is executed.
|
||||
*/
|
||||
protected void registerBroadcastReceiver() throws Exception {
|
||||
mServiceClient.registerBroadcastReceiver();
|
||||
|
||||
final Intent intent = new Intent(ACTION_RECEIVER_READY)
|
||||
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
|
||||
// Wait until receiver is ready.
|
||||
final int maxTries = 10;
|
||||
for (int i = 1; i <= maxTries; i++) {
|
||||
final String message = sendOrderedBroadcast(intent, SECOND_IN_MS * 4);
|
||||
Log.d(TAG, "app2 receiver acked: " + message);
|
||||
if (message != null) {
|
||||
return;
|
||||
}
|
||||
Log.v(TAG, "app2 receiver is not ready yet; sleeping 1s before polling again");
|
||||
SystemClock.sleep(SECOND_IN_MS);
|
||||
}
|
||||
fail("app2 receiver is not ready");
|
||||
}
|
||||
|
||||
protected void registerNetworkCallback(INetworkCallback cb) throws Exception {
|
||||
mServiceClient.registerNetworkCallback(cb);
|
||||
}
|
||||
|
||||
protected void unregisterNetworkCallback() throws Exception {
|
||||
mServiceClient.unregisterNetworkCallback();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a {@link NotificationListenerService} implementation that will execute the
|
||||
* notification actions right after the notification is sent.
|
||||
*/
|
||||
protected void registerNotificationListenerService() throws Exception {
|
||||
executeShellCommand("cmd notification allow_listener "
|
||||
+ MyNotificationListenerService.getId());
|
||||
final NotificationManager nm = mContext.getSystemService(NotificationManager.class);
|
||||
final ComponentName listenerComponent = MyNotificationListenerService.getComponentName();
|
||||
assertTrue(listenerComponent + " has not been granted access",
|
||||
nm.isNotificationListenerAccessGranted(listenerComponent));
|
||||
}
|
||||
|
||||
protected void setPendingIntentWhitelistDuration(int durationMs) throws Exception {
|
||||
executeSilentShellCommand(String.format(
|
||||
"settings put global %s %s=%d", mDeviceIdleConstantsSetting,
|
||||
"notification_whitelist_duration", durationMs));
|
||||
}
|
||||
|
||||
protected void resetDeviceIdleSettings() throws Exception {
|
||||
executeShellCommand(String.format("settings delete global %s",
|
||||
mDeviceIdleConstantsSetting));
|
||||
}
|
||||
|
||||
protected void launchComponentAndAssertNetworkAccess(int type) throws Exception {
|
||||
if (type == TYPE_COMPONENT_FOREGROUND_SERVICE) {
|
||||
startForegroundService();
|
||||
assertForegroundServiceNetworkAccess();
|
||||
return;
|
||||
} else if (type == TYPE_COMPONENT_ACTIVTIY) {
|
||||
turnScreenOn();
|
||||
// Wait for screen-on state to propagate through the system.
|
||||
SystemClock.sleep(2000);
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
final Intent launchIntent = getIntentForComponent(type);
|
||||
final Bundle extras = new Bundle();
|
||||
final String[] errors = new String[]{null};
|
||||
extras.putBinder(KEY_NETWORK_STATE_OBSERVER, getNewNetworkStateObserver(latch, errors));
|
||||
launchIntent.putExtras(extras);
|
||||
mContext.startActivity(launchIntent);
|
||||
if (latch.await(FOREGROUND_PROC_NETWORK_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
|
||||
if (!errors[0].isEmpty()) {
|
||||
if (errors[0] == APP_NOT_FOREGROUND_ERROR) {
|
||||
// App didn't come to foreground when the activity is started, so try again.
|
||||
assertForegroundNetworkAccess();
|
||||
} else {
|
||||
fail("Network is not available for app2 (" + mUid + "): " + errors[0]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fail("Timed out waiting for network availability status from app2 (" + mUid + ")");
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown type: " + type);
|
||||
}
|
||||
}
|
||||
|
||||
private void startForegroundService() throws Exception {
|
||||
final Intent launchIntent = getIntentForComponent(TYPE_COMPONENT_FOREGROUND_SERVICE);
|
||||
mContext.startForegroundService(launchIntent);
|
||||
assertForegroundServiceState();
|
||||
}
|
||||
|
||||
private Intent getIntentForComponent(int type) {
|
||||
final Intent intent = new Intent();
|
||||
if (type == TYPE_COMPONENT_ACTIVTIY) {
|
||||
intent.setComponent(new ComponentName(TEST_APP2_PKG, TEST_APP2_ACTIVITY_CLASS))
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
} else if (type == TYPE_COMPONENT_FOREGROUND_SERVICE) {
|
||||
intent.setComponent(new ComponentName(TEST_APP2_PKG, TEST_APP2_SERVICE_CLASS))
|
||||
.setFlags(1);
|
||||
} else {
|
||||
fail("Unknown type: " + type);
|
||||
}
|
||||
return intent;
|
||||
}
|
||||
|
||||
protected void stopForegroundService() throws Exception {
|
||||
executeShellCommand(String.format("am startservice -f 2 %s/%s",
|
||||
TEST_APP2_PKG, TEST_APP2_SERVICE_CLASS));
|
||||
// NOTE: cannot assert state because it depends on whether activity was on top before.
|
||||
}
|
||||
|
||||
private Binder getNewNetworkStateObserver(final CountDownLatch latch,
|
||||
final String[] errors) {
|
||||
return new INetworkStateObserver.Stub() {
|
||||
@Override
|
||||
public boolean isForeground() {
|
||||
try {
|
||||
final ProcessState state = getProcessStateByUid(mUid);
|
||||
return !isBackground(state.state);
|
||||
} catch (Exception e) {
|
||||
Log.d(TAG, "Error while reading the proc state for " + mUid + ": " + e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNetworkStateChecked(String resultData) {
|
||||
errors[0] = resultData == null
|
||||
? APP_NOT_FOREGROUND_ERROR
|
||||
: checkForAvailabilityInResultData(resultData, true);
|
||||
latch.countDown();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Finishes an activity on app2 so its process is demoted fromforeground status.
|
||||
*/
|
||||
protected void finishActivity() throws Exception {
|
||||
executeShellCommand("am broadcast -a "
|
||||
+ " com.android.cts.net.hostside.app2.action.FINISH_ACTIVITY "
|
||||
+ "--receiver-foreground --receiver-registered-only");
|
||||
}
|
||||
|
||||
protected void sendNotification(int notificationId, String notificationType) throws Exception {
|
||||
Log.d(TAG, "Sending notification broadcast (id=" + notificationId
|
||||
+ ", type=" + notificationType);
|
||||
mServiceClient.sendNotification(notificationId, notificationType);
|
||||
}
|
||||
|
||||
protected String showToast() {
|
||||
final Intent intent = new Intent(ACTION_SHOW_TOAST);
|
||||
intent.setPackage(TEST_APP2_PKG);
|
||||
Log.d(TAG, "Sending request to show toast");
|
||||
try {
|
||||
return sendOrderedBroadcast(intent, 3 * SECOND_IN_MS);
|
||||
} catch (Exception e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private ProcessState getProcessStateByUid(int uid) throws Exception {
|
||||
return new ProcessState(executeShellCommand("cmd activity get-uid-state " + uid));
|
||||
}
|
||||
|
||||
private static class ProcessState {
|
||||
private final String fullState;
|
||||
final int state;
|
||||
|
||||
ProcessState(String fullState) {
|
||||
this.fullState = fullState;
|
||||
try {
|
||||
this.state = Integer.parseInt(fullState.split(" ")[0]);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("Could not parse " + fullState);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return fullState;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class used to assert the result of a Shell command.
|
||||
*/
|
||||
protected static interface ExpectResultChecker {
|
||||
/**
|
||||
* Checkes whether the result of the command matched the expectation.
|
||||
*/
|
||||
boolean isExpected(String result);
|
||||
/**
|
||||
* Gets the expected result so it's displayed on log and failure messages.
|
||||
*/
|
||||
String getExpected();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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.cts.net.hostside;
|
||||
|
||||
import static com.android.cts.net.hostside.Property.METERED_NETWORK;
|
||||
|
||||
@RequiredProperties({METERED_NETWORK})
|
||||
public class AppIdleMeteredTest extends AbstractAppIdleTestCase {
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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.cts.net.hostside;
|
||||
|
||||
import static com.android.cts.net.hostside.Property.NON_METERED_NETWORK;
|
||||
|
||||
@RequiredProperties({NON_METERED_NETWORK})
|
||||
public class AppIdleNonMeteredTest extends AbstractAppIdleTestCase {
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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.cts.net.hostside;
|
||||
|
||||
import static com.android.cts.net.hostside.Property.METERED_NETWORK;
|
||||
|
||||
@RequiredProperties({METERED_NETWORK})
|
||||
public class BatterySaverModeMeteredTest extends AbstractBatterySaverModeTestCase {
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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.cts.net.hostside;
|
||||
|
||||
|
||||
import static com.android.cts.net.hostside.Property.NON_METERED_NETWORK;
|
||||
|
||||
@RequiredProperties({NON_METERED_NETWORK})
|
||||
public class BatterySaverModeNonMeteredTest extends AbstractBatterySaverModeTestCase {
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
* 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.cts.net.hostside;
|
||||
|
||||
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
|
||||
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
|
||||
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED;
|
||||
|
||||
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setRestrictBackground;
|
||||
import static com.android.cts.net.hostside.Property.DATA_SAVER_MODE;
|
||||
import static com.android.cts.net.hostside.Property.METERED_NETWORK;
|
||||
import static com.android.cts.net.hostside.Property.NO_DATA_SAVER_MODE;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import com.android.compatibility.common.util.CddTest;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import androidx.test.filters.LargeTest;
|
||||
|
||||
@RequiredProperties({DATA_SAVER_MODE, METERED_NETWORK})
|
||||
@LargeTest
|
||||
public class DataSaverModeTest extends AbstractRestrictBackgroundNetworkTestCase {
|
||||
|
||||
private static final String[] REQUIRED_WHITELISTED_PACKAGES = {
|
||||
"com.android.providers.downloads"
|
||||
};
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
// Set initial state.
|
||||
setRestrictBackground(false);
|
||||
removeRestrictBackgroundWhitelist(mUid);
|
||||
removeRestrictBackgroundBlacklist(mUid);
|
||||
|
||||
registerBroadcastReceiver();
|
||||
assertRestrictBackgroundChangedReceived(0);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
super.tearDown();
|
||||
|
||||
setRestrictBackground(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRestrictBackgroundStatus_disabled() throws Exception {
|
||||
assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_DISABLED);
|
||||
|
||||
// Verify status is always disabled, never whitelisted
|
||||
addRestrictBackgroundWhitelist(mUid);
|
||||
assertRestrictBackgroundChangedReceived(0);
|
||||
assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_DISABLED);
|
||||
|
||||
assertsForegroundAlwaysHasNetworkAccess();
|
||||
assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_DISABLED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRestrictBackgroundStatus_whitelisted() throws Exception {
|
||||
setRestrictBackground(true);
|
||||
assertRestrictBackgroundChangedReceived(1);
|
||||
assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
|
||||
|
||||
addRestrictBackgroundWhitelist(mUid);
|
||||
assertRestrictBackgroundChangedReceived(2);
|
||||
assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_WHITELISTED);
|
||||
|
||||
removeRestrictBackgroundWhitelist(mUid);
|
||||
assertRestrictBackgroundChangedReceived(3);
|
||||
assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
|
||||
|
||||
assertsForegroundAlwaysHasNetworkAccess();
|
||||
assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRestrictBackgroundStatus_enabled() throws Exception {
|
||||
setRestrictBackground(true);
|
||||
assertRestrictBackgroundChangedReceived(1);
|
||||
assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
|
||||
|
||||
assertsForegroundAlwaysHasNetworkAccess();
|
||||
assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
|
||||
|
||||
// Make sure foreground app doesn't lose access upon enabling Data Saver.
|
||||
setRestrictBackground(false);
|
||||
launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_ACTIVTIY);
|
||||
setRestrictBackground(true);
|
||||
assertForegroundNetworkAccess();
|
||||
|
||||
// Although it should not have access while the screen is off.
|
||||
turnScreenOff();
|
||||
assertBackgroundNetworkAccess(false);
|
||||
turnScreenOn();
|
||||
assertForegroundNetworkAccess();
|
||||
|
||||
// Goes back to background state.
|
||||
finishActivity();
|
||||
assertBackgroundNetworkAccess(false);
|
||||
|
||||
// Make sure foreground service doesn't lose access upon enabling Data Saver.
|
||||
setRestrictBackground(false);
|
||||
launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_FOREGROUND_SERVICE);
|
||||
setRestrictBackground(true);
|
||||
assertForegroundNetworkAccess();
|
||||
stopForegroundService();
|
||||
assertBackgroundNetworkAccess(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRestrictBackgroundStatus_blacklisted() throws Exception {
|
||||
addRestrictBackgroundBlacklist(mUid);
|
||||
assertRestrictBackgroundChangedReceived(1);
|
||||
assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
|
||||
|
||||
assertsForegroundAlwaysHasNetworkAccess();
|
||||
assertRestrictBackgroundChangedReceived(1);
|
||||
assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
|
||||
|
||||
// UID policies live by the Highlander rule: "There can be only one".
|
||||
// Hence, if app is whitelisted, it should not be blacklisted anymore.
|
||||
setRestrictBackground(true);
|
||||
assertRestrictBackgroundChangedReceived(2);
|
||||
assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
|
||||
addRestrictBackgroundWhitelist(mUid);
|
||||
assertRestrictBackgroundChangedReceived(3);
|
||||
assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_WHITELISTED);
|
||||
|
||||
// Check status after removing blacklist.
|
||||
// ...re-enables first
|
||||
addRestrictBackgroundBlacklist(mUid);
|
||||
assertRestrictBackgroundChangedReceived(4);
|
||||
assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
|
||||
assertsForegroundAlwaysHasNetworkAccess();
|
||||
// ... remove blacklist - access's still rejected because Data Saver is on
|
||||
removeRestrictBackgroundBlacklist(mUid);
|
||||
assertRestrictBackgroundChangedReceived(4);
|
||||
assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
|
||||
assertsForegroundAlwaysHasNetworkAccess();
|
||||
// ... finally, disable Data Saver
|
||||
setRestrictBackground(false);
|
||||
assertRestrictBackgroundChangedReceived(5);
|
||||
assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_DISABLED);
|
||||
assertsForegroundAlwaysHasNetworkAccess();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRestrictBackgroundStatus_requiredWhitelistedPackages() throws Exception {
|
||||
final StringBuilder error = new StringBuilder();
|
||||
for (String packageName : REQUIRED_WHITELISTED_PACKAGES) {
|
||||
int uid = -1;
|
||||
try {
|
||||
uid = getUid(packageName);
|
||||
assertRestrictBackgroundWhitelist(uid, true);
|
||||
} catch (Throwable t) {
|
||||
error.append("\nFailed for '").append(packageName).append("'");
|
||||
if (uid > 0) {
|
||||
error.append(" (uid ").append(uid).append(")");
|
||||
}
|
||||
error.append(": ").append(t).append("\n");
|
||||
}
|
||||
}
|
||||
if (error.length() > 0) {
|
||||
fail(error.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@RequiredProperties({NO_DATA_SAVER_MODE})
|
||||
@CddTest(requirement="7.4.7/C-2-2")
|
||||
@Test
|
||||
public void testBroadcastNotSentOnUnsupportedDevices() throws Exception {
|
||||
setRestrictBackground(true);
|
||||
assertRestrictBackgroundChangedReceived(0);
|
||||
|
||||
setRestrictBackground(false);
|
||||
assertRestrictBackgroundChangedReceived(0);
|
||||
|
||||
setRestrictBackground(true);
|
||||
assertRestrictBackgroundChangedReceived(0);
|
||||
}
|
||||
|
||||
private void assertDataSaverStatusOnBackground(int expectedStatus) throws Exception {
|
||||
assertRestrictBackgroundStatus(expectedStatus);
|
||||
assertBackgroundNetworkAccess(expectedStatus != RESTRICT_BACKGROUND_STATUS_ENABLED);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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.cts.net.hostside;
|
||||
|
||||
import static com.android.cts.net.hostside.Property.METERED_NETWORK;
|
||||
|
||||
@RequiredProperties({METERED_NETWORK})
|
||||
public class DozeModeMeteredTest extends AbstractDozeModeTestCase {
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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.cts.net.hostside;
|
||||
|
||||
import static com.android.cts.net.hostside.Property.NON_METERED_NETWORK;
|
||||
|
||||
@RequiredProperties({NON_METERED_NETWORK})
|
||||
public class DozeModeNonMeteredTest extends AbstractDozeModeTestCase {
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.cts.net.hostside;
|
||||
|
||||
import static com.android.cts.net.hostside.AbstractRestrictBackgroundNetworkTestCase.TAG;
|
||||
import static com.android.cts.net.hostside.AbstractRestrictBackgroundNetworkTestCase.TEST_APP2_PKG;
|
||||
import static com.android.cts.net.hostside.AbstractRestrictBackgroundNetworkTestCase.TEST_PKG;
|
||||
|
||||
import android.os.Environment;
|
||||
import android.os.FileUtils;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.compatibility.common.util.OnFailureRule;
|
||||
|
||||
import org.junit.AssumptionViolatedException;
|
||||
import org.junit.runner.Description;
|
||||
import org.junit.runners.model.Statement;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
|
||||
public class DumpOnFailureRule extends OnFailureRule {
|
||||
private File mDumpDir = new File(Environment.getExternalStorageDirectory(),
|
||||
"CtsHostsideNetworkTests");
|
||||
|
||||
@Override
|
||||
public void onTestFailure(Statement base, Description description, Throwable throwable) {
|
||||
final String testName = description.getClassName() + "_" + description.getMethodName();
|
||||
|
||||
if (throwable instanceof AssumptionViolatedException) {
|
||||
Log.d(TAG, "Skipping test " + testName + ": " + throwable);
|
||||
return;
|
||||
}
|
||||
|
||||
prepareDumpRootDir();
|
||||
final File dumpFile = new File(mDumpDir, "dump-" + testName);
|
||||
Log.i(TAG, "Dumping debug info for " + description + ": " + dumpFile.getPath());
|
||||
try (FileOutputStream out = new FileOutputStream(dumpFile)) {
|
||||
for (String cmd : new String[] {
|
||||
"dumpsys netpolicy",
|
||||
"dumpsys network_management",
|
||||
"dumpsys usagestats " + TEST_PKG + " " + TEST_APP2_PKG,
|
||||
"dumpsys usagestats appstandby",
|
||||
}) {
|
||||
dumpCommandOutput(out, cmd);
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.e(TAG, "Error opening file: " + dumpFile, e);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Error closing file: " + dumpFile, e);
|
||||
}
|
||||
}
|
||||
|
||||
void dumpCommandOutput(FileOutputStream out, String cmd) {
|
||||
final ParcelFileDescriptor pfd = InstrumentationRegistry.getInstrumentation()
|
||||
.getUiAutomation().executeShellCommand(cmd);
|
||||
try (FileInputStream in = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) {
|
||||
out.write(("Output of '" + cmd + "':\n").getBytes(StandardCharsets.UTF_8));
|
||||
FileUtils.copy(in, out);
|
||||
out.write("\n\n=================================================================\n\n"
|
||||
.getBytes(StandardCharsets.UTF_8));
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Error dumping '" + cmd + "'", e);
|
||||
}
|
||||
}
|
||||
|
||||
void prepareDumpRootDir() {
|
||||
if (!mDumpDir.exists() && !mDumpDir.mkdir()) {
|
||||
Log.e(TAG, "Error creating " + mDumpDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.cts.net.hostside;
|
||||
|
||||
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.resetMeteredNetwork;
|
||||
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setupMeteredNetwork;
|
||||
import static com.android.cts.net.hostside.Property.METERED_NETWORK;
|
||||
import static com.android.cts.net.hostside.Property.NON_METERED_NETWORK;
|
||||
|
||||
import android.util.ArraySet;
|
||||
import android.util.Pair;
|
||||
|
||||
import com.android.compatibility.common.util.BeforeAfterRule;
|
||||
|
||||
import org.junit.runner.Description;
|
||||
import org.junit.runners.model.Statement;
|
||||
|
||||
public class MeterednessConfigurationRule extends BeforeAfterRule {
|
||||
private Pair<String, Boolean> mSsidAndInitialMeteredness;
|
||||
|
||||
@Override
|
||||
public void onBefore(Statement base, Description description) throws Throwable {
|
||||
final ArraySet<Property> requiredProperties
|
||||
= RequiredPropertiesRule.getRequiredProperties();
|
||||
if (requiredProperties.contains(METERED_NETWORK)) {
|
||||
configureNetworkMeteredness(true);
|
||||
} else if (requiredProperties.contains(NON_METERED_NETWORK)) {
|
||||
configureNetworkMeteredness(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAfter(Statement base, Description description) throws Throwable {
|
||||
resetNetworkMeteredness();
|
||||
}
|
||||
|
||||
public void configureNetworkMeteredness(boolean metered) throws Exception {
|
||||
mSsidAndInitialMeteredness = setupMeteredNetwork(metered);
|
||||
}
|
||||
|
||||
public void resetNetworkMeteredness() throws Exception {
|
||||
if (mSsidAndInitialMeteredness != null) {
|
||||
resetMeteredNetwork(mSsidAndInitialMeteredness.first,
|
||||
mSsidAndInitialMeteredness.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,370 @@
|
||||
/*
|
||||
* 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.cts.net.hostside;
|
||||
|
||||
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setRestrictBackground;
|
||||
import static com.android.cts.net.hostside.Property.APP_STANDBY_MODE;
|
||||
import static com.android.cts.net.hostside.Property.BATTERY_SAVER_MODE;
|
||||
import static com.android.cts.net.hostside.Property.DATA_SAVER_MODE;
|
||||
import static com.android.cts.net.hostside.Property.DOZE_MODE;
|
||||
import static com.android.cts.net.hostside.Property.METERED_NETWORK;
|
||||
import static com.android.cts.net.hostside.Property.NON_METERED_NETWORK;
|
||||
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Test cases for the more complex scenarios where multiple restrictions (like Battery Saver Mode
|
||||
* and Data Saver Mode) are applied simultaneously.
|
||||
* <p>
|
||||
* <strong>NOTE: </strong>it might sound like the test methods on this class are testing too much,
|
||||
* which would make it harder to diagnose individual failures, but the assumption is that such
|
||||
* failure most likely will happen when the restriction is tested individually as well.
|
||||
*/
|
||||
public class MixedModesTest extends AbstractRestrictBackgroundNetworkTestCase {
|
||||
private static final String TAG = "MixedModesTest";
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
// Set initial state.
|
||||
removeRestrictBackgroundWhitelist(mUid);
|
||||
removeRestrictBackgroundBlacklist(mUid);
|
||||
removePowerSaveModeWhitelist(TEST_APP2_PKG);
|
||||
removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
|
||||
|
||||
registerBroadcastReceiver();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
super.tearDown();
|
||||
|
||||
try {
|
||||
setRestrictBackground(false);
|
||||
} finally {
|
||||
setBatterySaverMode(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests all DS ON and BS ON scenarios from network-policy-restrictions.md on metered networks.
|
||||
*/
|
||||
@RequiredProperties({DATA_SAVER_MODE, BATTERY_SAVER_MODE, METERED_NETWORK})
|
||||
@Test
|
||||
public void testDataAndBatterySaverModes_meteredNetwork() throws Exception {
|
||||
final MeterednessConfigurationRule meterednessConfiguration
|
||||
= new MeterednessConfigurationRule();
|
||||
meterednessConfiguration.configureNetworkMeteredness(true);
|
||||
try {
|
||||
setRestrictBackground(true);
|
||||
setBatterySaverMode(true);
|
||||
|
||||
Log.v(TAG, "Not whitelisted for any.");
|
||||
assertBackgroundNetworkAccess(false);
|
||||
assertsForegroundAlwaysHasNetworkAccess();
|
||||
assertBackgroundNetworkAccess(false);
|
||||
|
||||
Log.v(TAG, "Whitelisted for Data Saver but not for Battery Saver.");
|
||||
addRestrictBackgroundWhitelist(mUid);
|
||||
removePowerSaveModeWhitelist(TEST_APP2_PKG);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
assertsForegroundAlwaysHasNetworkAccess();
|
||||
assertBackgroundNetworkAccess(false);
|
||||
removeRestrictBackgroundWhitelist(mUid);
|
||||
|
||||
Log.v(TAG, "Whitelisted for Battery Saver but not for Data Saver.");
|
||||
addPowerSaveModeWhitelist(TEST_APP2_PKG);
|
||||
removeRestrictBackgroundWhitelist(mUid);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
assertsForegroundAlwaysHasNetworkAccess();
|
||||
assertBackgroundNetworkAccess(false);
|
||||
removePowerSaveModeWhitelist(TEST_APP2_PKG);
|
||||
|
||||
Log.v(TAG, "Whitelisted for both.");
|
||||
addRestrictBackgroundWhitelist(mUid);
|
||||
addPowerSaveModeWhitelist(TEST_APP2_PKG);
|
||||
assertBackgroundNetworkAccess(true);
|
||||
assertsForegroundAlwaysHasNetworkAccess();
|
||||
assertBackgroundNetworkAccess(true);
|
||||
removePowerSaveModeWhitelist(TEST_APP2_PKG);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
removeRestrictBackgroundWhitelist(mUid);
|
||||
|
||||
Log.v(TAG, "Blacklisted for Data Saver, not whitelisted for Battery Saver.");
|
||||
addRestrictBackgroundBlacklist(mUid);
|
||||
removePowerSaveModeWhitelist(TEST_APP2_PKG);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
assertsForegroundAlwaysHasNetworkAccess();
|
||||
assertBackgroundNetworkAccess(false);
|
||||
removeRestrictBackgroundBlacklist(mUid);
|
||||
|
||||
Log.v(TAG, "Blacklisted for Data Saver, whitelisted for Battery Saver.");
|
||||
addRestrictBackgroundBlacklist(mUid);
|
||||
addPowerSaveModeWhitelist(TEST_APP2_PKG);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
assertsForegroundAlwaysHasNetworkAccess();
|
||||
assertBackgroundNetworkAccess(false);
|
||||
removeRestrictBackgroundBlacklist(mUid);
|
||||
removePowerSaveModeWhitelist(TEST_APP2_PKG);
|
||||
} finally {
|
||||
meterednessConfiguration.resetNetworkMeteredness();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests all DS ON and BS ON scenarios from network-policy-restrictions.md on non-metered
|
||||
* networks.
|
||||
*/
|
||||
@RequiredProperties({DATA_SAVER_MODE, BATTERY_SAVER_MODE, NON_METERED_NETWORK})
|
||||
@Test
|
||||
public void testDataAndBatterySaverModes_nonMeteredNetwork() throws Exception {
|
||||
final MeterednessConfigurationRule meterednessConfiguration
|
||||
= new MeterednessConfigurationRule();
|
||||
meterednessConfiguration.configureNetworkMeteredness(false);
|
||||
try {
|
||||
setRestrictBackground(true);
|
||||
setBatterySaverMode(true);
|
||||
|
||||
Log.v(TAG, "Not whitelisted for any.");
|
||||
assertBackgroundNetworkAccess(false);
|
||||
assertsForegroundAlwaysHasNetworkAccess();
|
||||
assertBackgroundNetworkAccess(false);
|
||||
|
||||
Log.v(TAG, "Whitelisted for Data Saver but not for Battery Saver.");
|
||||
addRestrictBackgroundWhitelist(mUid);
|
||||
removePowerSaveModeWhitelist(TEST_APP2_PKG);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
assertsForegroundAlwaysHasNetworkAccess();
|
||||
assertBackgroundNetworkAccess(false);
|
||||
removeRestrictBackgroundWhitelist(mUid);
|
||||
|
||||
Log.v(TAG, "Whitelisted for Battery Saver but not for Data Saver.");
|
||||
addPowerSaveModeWhitelist(TEST_APP2_PKG);
|
||||
removeRestrictBackgroundWhitelist(mUid);
|
||||
assertBackgroundNetworkAccess(true);
|
||||
assertsForegroundAlwaysHasNetworkAccess();
|
||||
assertBackgroundNetworkAccess(true);
|
||||
removePowerSaveModeWhitelist(TEST_APP2_PKG);
|
||||
|
||||
Log.v(TAG, "Whitelisted for both.");
|
||||
addRestrictBackgroundWhitelist(mUid);
|
||||
addPowerSaveModeWhitelist(TEST_APP2_PKG);
|
||||
assertBackgroundNetworkAccess(true);
|
||||
assertsForegroundAlwaysHasNetworkAccess();
|
||||
assertBackgroundNetworkAccess(true);
|
||||
removePowerSaveModeWhitelist(TEST_APP2_PKG);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
removeRestrictBackgroundWhitelist(mUid);
|
||||
|
||||
Log.v(TAG, "Blacklisted for Data Saver, not whitelisted for Battery Saver.");
|
||||
addRestrictBackgroundBlacklist(mUid);
|
||||
removePowerSaveModeWhitelist(TEST_APP2_PKG);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
assertsForegroundAlwaysHasNetworkAccess();
|
||||
assertBackgroundNetworkAccess(false);
|
||||
removeRestrictBackgroundBlacklist(mUid);
|
||||
|
||||
Log.v(TAG, "Blacklisted for Data Saver, whitelisted for Battery Saver.");
|
||||
addRestrictBackgroundBlacklist(mUid);
|
||||
addPowerSaveModeWhitelist(TEST_APP2_PKG);
|
||||
assertBackgroundNetworkAccess(true);
|
||||
assertsForegroundAlwaysHasNetworkAccess();
|
||||
assertBackgroundNetworkAccess(true);
|
||||
removeRestrictBackgroundBlacklist(mUid);
|
||||
removePowerSaveModeWhitelist(TEST_APP2_PKG);
|
||||
} finally {
|
||||
meterednessConfiguration.resetNetworkMeteredness();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that powersave whitelists works as expected when doze and battery saver modes
|
||||
* are enabled.
|
||||
*/
|
||||
@RequiredProperties({DOZE_MODE, BATTERY_SAVER_MODE})
|
||||
@Test
|
||||
public void testDozeAndBatterySaverMode_powerSaveWhitelists() throws Exception {
|
||||
setBatterySaverMode(true);
|
||||
setDozeMode(true);
|
||||
|
||||
try {
|
||||
addPowerSaveModeWhitelist(TEST_APP2_PKG);
|
||||
assertBackgroundNetworkAccess(true);
|
||||
|
||||
removePowerSaveModeWhitelist(TEST_APP2_PKG);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
|
||||
addPowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
|
||||
removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
} finally {
|
||||
setBatterySaverMode(false);
|
||||
setDozeMode(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that powersave whitelists works as expected when doze and appIdle modes
|
||||
* are enabled.
|
||||
*/
|
||||
@RequiredProperties({DOZE_MODE, APP_STANDBY_MODE})
|
||||
@Test
|
||||
public void testDozeAndAppIdle_powerSaveWhitelists() throws Exception {
|
||||
setDozeMode(true);
|
||||
setAppIdle(true);
|
||||
|
||||
try {
|
||||
addPowerSaveModeWhitelist(TEST_APP2_PKG);
|
||||
assertBackgroundNetworkAccess(true);
|
||||
|
||||
removePowerSaveModeWhitelist(TEST_APP2_PKG);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
|
||||
addPowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
|
||||
removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
} finally {
|
||||
setAppIdle(false);
|
||||
setDozeMode(false);
|
||||
}
|
||||
}
|
||||
|
||||
@RequiredProperties({APP_STANDBY_MODE, DOZE_MODE})
|
||||
@Test
|
||||
public void testAppIdleAndDoze_tempPowerSaveWhitelists() throws Exception {
|
||||
setDozeMode(true);
|
||||
setAppIdle(true);
|
||||
|
||||
try {
|
||||
assertBackgroundNetworkAccess(false);
|
||||
|
||||
addTempPowerSaveModeWhitelist(TEST_APP2_PKG, TEMP_POWERSAVE_WHITELIST_DURATION_MS);
|
||||
assertBackgroundNetworkAccess(true);
|
||||
|
||||
// Wait until the whitelist duration is expired.
|
||||
SystemClock.sleep(TEMP_POWERSAVE_WHITELIST_DURATION_MS);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
} finally {
|
||||
setAppIdle(false);
|
||||
setDozeMode(false);
|
||||
}
|
||||
}
|
||||
|
||||
@RequiredProperties({APP_STANDBY_MODE, BATTERY_SAVER_MODE})
|
||||
@Test
|
||||
public void testAppIdleAndBatterySaver_tempPowerSaveWhitelists() throws Exception {
|
||||
setBatterySaverMode(true);
|
||||
setAppIdle(true);
|
||||
|
||||
try {
|
||||
assertBackgroundNetworkAccess(false);
|
||||
|
||||
addTempPowerSaveModeWhitelist(TEST_APP2_PKG, TEMP_POWERSAVE_WHITELIST_DURATION_MS);
|
||||
assertBackgroundNetworkAccess(true);
|
||||
|
||||
// Wait until the whitelist duration is expired.
|
||||
SystemClock.sleep(TEMP_POWERSAVE_WHITELIST_DURATION_MS);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
} finally {
|
||||
setAppIdle(false);
|
||||
setBatterySaverMode(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the app idle whitelist works as expected when doze and appIdle mode are enabled.
|
||||
*/
|
||||
@RequiredProperties({DOZE_MODE, APP_STANDBY_MODE})
|
||||
@Test
|
||||
public void testDozeAndAppIdle_appIdleWhitelist() throws Exception {
|
||||
setDozeMode(true);
|
||||
setAppIdle(true);
|
||||
|
||||
try {
|
||||
assertBackgroundNetworkAccess(false);
|
||||
|
||||
// UID still shouldn't have access because of Doze.
|
||||
addAppIdleWhitelist(mUid);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
|
||||
removeAppIdleWhitelist(mUid);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
} finally {
|
||||
setAppIdle(false);
|
||||
setDozeMode(false);
|
||||
}
|
||||
}
|
||||
|
||||
@RequiredProperties({APP_STANDBY_MODE, DOZE_MODE})
|
||||
@Test
|
||||
public void testAppIdleAndDoze_tempPowerSaveAndAppIdleWhitelists() throws Exception {
|
||||
setDozeMode(true);
|
||||
setAppIdle(true);
|
||||
|
||||
try {
|
||||
assertBackgroundNetworkAccess(false);
|
||||
|
||||
addAppIdleWhitelist(mUid);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
|
||||
addTempPowerSaveModeWhitelist(TEST_APP2_PKG, TEMP_POWERSAVE_WHITELIST_DURATION_MS);
|
||||
assertBackgroundNetworkAccess(true);
|
||||
|
||||
// Wait until the whitelist duration is expired.
|
||||
SystemClock.sleep(TEMP_POWERSAVE_WHITELIST_DURATION_MS);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
} finally {
|
||||
setAppIdle(false);
|
||||
setDozeMode(false);
|
||||
removeAppIdleWhitelist(mUid);
|
||||
}
|
||||
}
|
||||
|
||||
@RequiredProperties({APP_STANDBY_MODE, BATTERY_SAVER_MODE})
|
||||
@Test
|
||||
public void testAppIdleAndBatterySaver_tempPowerSaveAndAppIdleWhitelists() throws Exception {
|
||||
setBatterySaverMode(true);
|
||||
setAppIdle(true);
|
||||
|
||||
try {
|
||||
assertBackgroundNetworkAccess(false);
|
||||
|
||||
addAppIdleWhitelist(mUid);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
|
||||
addTempPowerSaveModeWhitelist(TEST_APP2_PKG, TEMP_POWERSAVE_WHITELIST_DURATION_MS);
|
||||
assertBackgroundNetworkAccess(true);
|
||||
|
||||
// Wait until the whitelist duration is expired.
|
||||
SystemClock.sleep(TEMP_POWERSAVE_WHITELIST_DURATION_MS);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
} finally {
|
||||
setAppIdle(false);
|
||||
setBatterySaverMode(false);
|
||||
removeAppIdleWhitelist(mUid);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.cts.net.hostside;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class MyActivity extends Activity {
|
||||
private final LinkedBlockingQueue<Integer> mResult = new LinkedBlockingQueue<>(1);
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
|
||||
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
|
||||
| WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (mResult.offer(resultCode) == false) {
|
||||
throw new RuntimeException("Queue is full! This should never happen");
|
||||
}
|
||||
}
|
||||
|
||||
public Integer getResult(int timeoutMs) throws InterruptedException {
|
||||
return mResult.poll(timeoutMs, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* 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.cts.net.hostside;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.PendingIntent.CanceledException;
|
||||
import android.app.RemoteInput;
|
||||
import android.content.ComponentName;
|
||||
import android.os.Bundle;
|
||||
import android.service.notification.NotificationListenerService;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* NotificationListenerService implementation that executes the notification actions once they're
|
||||
* created.
|
||||
*/
|
||||
public class MyNotificationListenerService extends NotificationListenerService {
|
||||
private static final String TAG = "MyNotificationListenerService";
|
||||
|
||||
@Override
|
||||
public void onListenerConnected() {
|
||||
Log.d(TAG, "onListenerConnected()");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotificationPosted(StatusBarNotification sbn) {
|
||||
Log.d(TAG, "onNotificationPosted(): " + sbn);
|
||||
if (!sbn.getPackageName().startsWith(getPackageName())) {
|
||||
Log.v(TAG, "ignoring notification from a different package");
|
||||
return;
|
||||
}
|
||||
final PendingIntentSender sender = new PendingIntentSender();
|
||||
final Notification notification = sbn.getNotification();
|
||||
if (notification.contentIntent != null) {
|
||||
sender.send("content", notification.contentIntent);
|
||||
}
|
||||
if (notification.deleteIntent != null) {
|
||||
sender.send("delete", notification.deleteIntent);
|
||||
}
|
||||
if (notification.fullScreenIntent != null) {
|
||||
sender.send("full screen", notification.fullScreenIntent);
|
||||
}
|
||||
if (notification.actions != null) {
|
||||
for (Notification.Action action : notification.actions) {
|
||||
sender.send("action", action.actionIntent);
|
||||
sender.send("action extras", action.getExtras());
|
||||
final RemoteInput[] remoteInputs = action.getRemoteInputs();
|
||||
if (remoteInputs != null && remoteInputs.length > 0) {
|
||||
for (RemoteInput remoteInput : remoteInputs) {
|
||||
sender.send("remote input extras", remoteInput.getExtras());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
sender.send("notification extras", notification.extras);
|
||||
}
|
||||
|
||||
static String getId() {
|
||||
return String.format("%s/%s", MyNotificationListenerService.class.getPackage().getName(),
|
||||
MyNotificationListenerService.class.getName());
|
||||
}
|
||||
|
||||
static ComponentName getComponentName() {
|
||||
return new ComponentName(MyNotificationListenerService.class.getPackage().getName(),
|
||||
MyNotificationListenerService.class.getName());
|
||||
}
|
||||
|
||||
private static final class PendingIntentSender {
|
||||
private PendingIntent mSentIntent = null;
|
||||
private String mReason = null;
|
||||
|
||||
private void send(String reason, PendingIntent pendingIntent) {
|
||||
if (pendingIntent == null) {
|
||||
// Could happen on action that only has extras
|
||||
Log.v(TAG, "Not sending null pending intent for " + reason);
|
||||
return;
|
||||
}
|
||||
if (mSentIntent != null || mReason != null) {
|
||||
// Sanity check: make sure test case set up just one pending intent in the
|
||||
// notification, otherwise it could pass because another pending intent caused the
|
||||
// whitelisting.
|
||||
throw new IllegalStateException("Already sent a PendingIntent (" + mSentIntent
|
||||
+ ") for reason '" + mReason + "' when requested another for '" + reason
|
||||
+ "' (" + pendingIntent + ")");
|
||||
}
|
||||
Log.i(TAG, "Sending pending intent for " + reason + ":" + pendingIntent);
|
||||
try {
|
||||
pendingIntent.send();
|
||||
mSentIntent = pendingIntent;
|
||||
mReason = reason;
|
||||
} catch (CanceledException e) {
|
||||
Log.w(TAG, "Pending intent " + pendingIntent + " canceled");
|
||||
}
|
||||
}
|
||||
|
||||
private void send(String reason, Bundle extras) {
|
||||
if (extras != null) {
|
||||
for (String key : extras.keySet()) {
|
||||
Object value = extras.get(key);
|
||||
if (value instanceof PendingIntent) {
|
||||
send(reason + " with key '" + key + "'", (PendingIntent) value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* 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.cts.net.hostside;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.os.ConditionVariable;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import com.android.cts.net.hostside.IMyService;
|
||||
|
||||
public class MyServiceClient {
|
||||
private static final int TIMEOUT_MS = 5000;
|
||||
private static final String PACKAGE = MyServiceClient.class.getPackage().getName();
|
||||
private static final String APP2_PACKAGE = PACKAGE + ".app2";
|
||||
private static final String SERVICE_NAME = APP2_PACKAGE + ".MyService";
|
||||
|
||||
private Context mContext;
|
||||
private ServiceConnection mServiceConnection;
|
||||
private IMyService mService;
|
||||
|
||||
public MyServiceClient(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
public void bind() {
|
||||
if (mService != null) {
|
||||
throw new IllegalStateException("Already bound");
|
||||
}
|
||||
|
||||
final ConditionVariable cv = new ConditionVariable();
|
||||
mServiceConnection = new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||
mService = IMyService.Stub.asInterface(service);
|
||||
cv.open();
|
||||
}
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
mService = null;
|
||||
}
|
||||
};
|
||||
|
||||
final Intent intent = new Intent();
|
||||
intent.setComponent(new ComponentName(APP2_PACKAGE, SERVICE_NAME));
|
||||
// Needs to use BIND_NOT_FOREGROUND so app2 does not run in
|
||||
// the same process state as app
|
||||
mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE
|
||||
| Context.BIND_NOT_FOREGROUND);
|
||||
cv.block(TIMEOUT_MS);
|
||||
if (mService == null) {
|
||||
throw new IllegalStateException(
|
||||
"Could not bind to MyService service after " + TIMEOUT_MS + "ms");
|
||||
}
|
||||
}
|
||||
|
||||
public void unbind() {
|
||||
if (mService != null) {
|
||||
mContext.unbindService(mServiceConnection);
|
||||
}
|
||||
}
|
||||
|
||||
public void registerBroadcastReceiver() throws RemoteException {
|
||||
mService.registerBroadcastReceiver();
|
||||
}
|
||||
|
||||
public int getCounters(String receiverName, String action) throws RemoteException {
|
||||
return mService.getCounters(receiverName, action);
|
||||
}
|
||||
|
||||
public String checkNetworkStatus() throws RemoteException {
|
||||
return mService.checkNetworkStatus();
|
||||
}
|
||||
|
||||
public String getRestrictBackgroundStatus() throws RemoteException {
|
||||
return mService.getRestrictBackgroundStatus();
|
||||
}
|
||||
|
||||
public void sendNotification(int notificationId, String notificationType) throws RemoteException {
|
||||
mService.sendNotification(notificationId, notificationType);
|
||||
}
|
||||
|
||||
public void registerNetworkCallback(INetworkCallback cb) throws RemoteException {
|
||||
mService.registerNetworkCallback(cb);
|
||||
}
|
||||
|
||||
public void unregisterNetworkCallback() throws RemoteException {
|
||||
mService.unregisterNetworkCallback();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.cts.net.hostside;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Network;
|
||||
import android.net.ProxyInfo;
|
||||
import android.net.VpnService;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class MyVpnService extends VpnService {
|
||||
|
||||
private static String TAG = "MyVpnService";
|
||||
private static int MTU = 1799;
|
||||
|
||||
public static final String ACTION_ESTABLISHED = "com.android.cts.net.hostside.ESTABNLISHED";
|
||||
public static final String EXTRA_ALWAYS_ON = "is-always-on";
|
||||
public static final String EXTRA_LOCKDOWN_ENABLED = "is-lockdown-enabled";
|
||||
|
||||
private ParcelFileDescriptor mFd = null;
|
||||
private PacketReflector mPacketReflector = null;
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
String packageName = getPackageName();
|
||||
String cmd = intent.getStringExtra(packageName + ".cmd");
|
||||
if ("disconnect".equals(cmd)) {
|
||||
stop();
|
||||
} else if ("connect".equals(cmd)) {
|
||||
start(packageName, intent);
|
||||
}
|
||||
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
private void start(String packageName, Intent intent) {
|
||||
Builder builder = new Builder();
|
||||
|
||||
String addresses = intent.getStringExtra(packageName + ".addresses");
|
||||
if (addresses != null) {
|
||||
String[] addressArray = addresses.split(",");
|
||||
for (int i = 0; i < addressArray.length; i++) {
|
||||
String[] prefixAndMask = addressArray[i].split("/");
|
||||
try {
|
||||
InetAddress address = InetAddress.getByName(prefixAndMask[0]);
|
||||
int prefixLength = Integer.parseInt(prefixAndMask[1]);
|
||||
builder.addAddress(address, prefixLength);
|
||||
} catch (UnknownHostException|NumberFormatException|
|
||||
ArrayIndexOutOfBoundsException e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String routes = intent.getStringExtra(packageName + ".routes");
|
||||
if (routes != null) {
|
||||
String[] routeArray = routes.split(",");
|
||||
for (int i = 0; i < routeArray.length; i++) {
|
||||
String[] prefixAndMask = routeArray[i].split("/");
|
||||
try {
|
||||
InetAddress address = InetAddress.getByName(prefixAndMask[0]);
|
||||
int prefixLength = Integer.parseInt(prefixAndMask[1]);
|
||||
builder.addRoute(address, prefixLength);
|
||||
} catch (UnknownHostException|NumberFormatException|
|
||||
ArrayIndexOutOfBoundsException e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String allowed = intent.getStringExtra(packageName + ".allowedapplications");
|
||||
if (allowed != null) {
|
||||
String[] packageArray = allowed.split(",");
|
||||
for (int i = 0; i < packageArray.length; i++) {
|
||||
String allowedPackage = packageArray[i];
|
||||
if (!TextUtils.isEmpty(allowedPackage)) {
|
||||
try {
|
||||
builder.addAllowedApplication(allowedPackage);
|
||||
} catch(NameNotFoundException e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String disallowed = intent.getStringExtra(packageName + ".disallowedapplications");
|
||||
if (disallowed != null) {
|
||||
String[] packageArray = disallowed.split(",");
|
||||
for (int i = 0; i < packageArray.length; i++) {
|
||||
String disallowedPackage = packageArray[i];
|
||||
if (!TextUtils.isEmpty(disallowedPackage)) {
|
||||
try {
|
||||
builder.addDisallowedApplication(disallowedPackage);
|
||||
} catch(NameNotFoundException e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ArrayList<Network> underlyingNetworks =
|
||||
intent.getParcelableArrayListExtra(packageName + ".underlyingNetworks");
|
||||
if (underlyingNetworks == null) {
|
||||
// VPN tracks default network
|
||||
builder.setUnderlyingNetworks(null);
|
||||
} else {
|
||||
builder.setUnderlyingNetworks(underlyingNetworks.toArray(new Network[0]));
|
||||
}
|
||||
|
||||
boolean isAlwaysMetered = intent.getBooleanExtra(packageName + ".isAlwaysMetered", false);
|
||||
builder.setMetered(isAlwaysMetered);
|
||||
|
||||
ProxyInfo vpnProxy = intent.getParcelableExtra(packageName + ".httpProxy");
|
||||
builder.setHttpProxy(vpnProxy);
|
||||
builder.setMtu(MTU);
|
||||
builder.setBlocking(true);
|
||||
builder.setSession("MyVpnService");
|
||||
|
||||
Log.i(TAG, "Establishing VPN,"
|
||||
+ " addresses=" + addresses
|
||||
+ " routes=" + routes
|
||||
+ " allowedApplications=" + allowed
|
||||
+ " disallowedApplications=" + disallowed);
|
||||
|
||||
mFd = builder.establish();
|
||||
Log.i(TAG, "Established, fd=" + (mFd == null ? "null" : mFd.getFd()));
|
||||
|
||||
broadcastEstablished();
|
||||
|
||||
mPacketReflector = new PacketReflector(mFd.getFileDescriptor(), MTU);
|
||||
mPacketReflector.start();
|
||||
}
|
||||
|
||||
private void broadcastEstablished() {
|
||||
final Intent bcIntent = new Intent(ACTION_ESTABLISHED);
|
||||
bcIntent.putExtra(EXTRA_ALWAYS_ON, isAlwaysOn());
|
||||
bcIntent.putExtra(EXTRA_LOCKDOWN_ENABLED, isLockdownEnabled());
|
||||
sendBroadcast(bcIntent);
|
||||
}
|
||||
|
||||
private void stop() {
|
||||
if (mPacketReflector != null) {
|
||||
mPacketReflector.interrupt();
|
||||
mPacketReflector = null;
|
||||
}
|
||||
try {
|
||||
if (mFd != null) {
|
||||
Log.i(TAG, "Closing filedescriptor");
|
||||
mFd.close();
|
||||
}
|
||||
} catch(IOException e) {
|
||||
} finally {
|
||||
mFd = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
stop();
|
||||
super.onDestroy();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,300 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.cts.net.hostside;
|
||||
|
||||
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
|
||||
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.canChangeActiveNetworkMeteredness;
|
||||
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setRestrictBackground;
|
||||
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isActiveNetworkMetered;
|
||||
import static com.android.cts.net.hostside.Property.BATTERY_SAVER_MODE;
|
||||
import static com.android.cts.net.hostside.Property.DATA_SAVER_MODE;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
|
||||
import android.net.Network;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.util.Log;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class NetworkCallbackTest extends AbstractRestrictBackgroundNetworkTestCase {
|
||||
private Network mNetwork;
|
||||
private final TestNetworkCallback mTestNetworkCallback = new TestNetworkCallback();
|
||||
@Rule
|
||||
public final MeterednessConfigurationRule mMeterednessConfiguration
|
||||
= new MeterednessConfigurationRule();
|
||||
|
||||
enum CallbackState {
|
||||
NONE,
|
||||
AVAILABLE,
|
||||
LOST,
|
||||
BLOCKED_STATUS,
|
||||
CAPABILITIES
|
||||
}
|
||||
|
||||
private static class CallbackInfo {
|
||||
public final CallbackState state;
|
||||
public final Network network;
|
||||
public final Object arg;
|
||||
|
||||
CallbackInfo(CallbackState s, Network n, Object o) {
|
||||
state = s; network = n; arg = o;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return String.format("%s (%s) (%s)", state, network, arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof CallbackInfo)) return false;
|
||||
// Ignore timeMs, since it's unpredictable.
|
||||
final CallbackInfo other = (CallbackInfo) o;
|
||||
return (state == other.state) && Objects.equals(network, other.network)
|
||||
&& Objects.equals(arg, other.arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(state, network, arg);
|
||||
}
|
||||
}
|
||||
|
||||
private class TestNetworkCallback extends INetworkCallback.Stub {
|
||||
private static final int TEST_CONNECT_TIMEOUT_MS = 30_000;
|
||||
private static final int TEST_CALLBACK_TIMEOUT_MS = 5_000;
|
||||
|
||||
private final LinkedBlockingQueue<CallbackInfo> mCallbacks = new LinkedBlockingQueue<>();
|
||||
|
||||
protected void setLastCallback(CallbackState state, Network network, Object o) {
|
||||
mCallbacks.offer(new CallbackInfo(state, network, o));
|
||||
}
|
||||
|
||||
CallbackInfo nextCallback(int timeoutMs) {
|
||||
CallbackInfo cb = null;
|
||||
try {
|
||||
cb = mCallbacks.poll(timeoutMs, TimeUnit.MILLISECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
if (cb == null) {
|
||||
fail("Did not receive callback after " + timeoutMs + "ms");
|
||||
}
|
||||
return cb;
|
||||
}
|
||||
|
||||
CallbackInfo expectCallback(CallbackState state, Network expectedNetwork, Object o) {
|
||||
final CallbackInfo expected = new CallbackInfo(state, expectedNetwork, o);
|
||||
final CallbackInfo actual = nextCallback(TEST_CALLBACK_TIMEOUT_MS);
|
||||
assertEquals("Unexpected callback:", expected, actual);
|
||||
return actual;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAvailable(Network network) {
|
||||
setLastCallback(CallbackState.AVAILABLE, network, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLost(Network network) {
|
||||
setLastCallback(CallbackState.LOST, network, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBlockedStatusChanged(Network network, boolean blocked) {
|
||||
setLastCallback(CallbackState.BLOCKED_STATUS, network, blocked);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCapabilitiesChanged(Network network, NetworkCapabilities cap) {
|
||||
setLastCallback(CallbackState.CAPABILITIES, network, cap);
|
||||
}
|
||||
|
||||
public Network expectAvailableCallbackAndGetNetwork() {
|
||||
final CallbackInfo cb = nextCallback(TEST_CONNECT_TIMEOUT_MS);
|
||||
if (cb.state != CallbackState.AVAILABLE) {
|
||||
fail("Network is not available. Instead obtained the following callback :"
|
||||
+ cb);
|
||||
}
|
||||
return cb.network;
|
||||
}
|
||||
|
||||
public void expectBlockedStatusCallback(Network expectedNetwork, boolean expectBlocked) {
|
||||
expectCallback(CallbackState.BLOCKED_STATUS, expectedNetwork, expectBlocked);
|
||||
}
|
||||
|
||||
public void expectBlockedStatusCallbackEventually(Network expectedNetwork,
|
||||
boolean expectBlocked) {
|
||||
final long deadline = System.currentTimeMillis() + TEST_CALLBACK_TIMEOUT_MS;
|
||||
do {
|
||||
final CallbackInfo cb = nextCallback((int) (deadline - System.currentTimeMillis()));
|
||||
if (cb.state == CallbackState.BLOCKED_STATUS
|
||||
&& cb.network.equals(expectedNetwork)) {
|
||||
assertEquals(expectBlocked, cb.arg);
|
||||
return;
|
||||
}
|
||||
} while (System.currentTimeMillis() <= deadline);
|
||||
fail("Didn't receive onBlockedStatusChanged()");
|
||||
}
|
||||
|
||||
public void expectCapabilitiesCallbackEventually(Network expectedNetwork, boolean hasCap,
|
||||
int cap) {
|
||||
final long deadline = System.currentTimeMillis() + TEST_CALLBACK_TIMEOUT_MS;
|
||||
do {
|
||||
final CallbackInfo cb = nextCallback((int) (deadline - System.currentTimeMillis()));
|
||||
if (cb.state != CallbackState.CAPABILITIES
|
||||
|| !expectedNetwork.equals(cb.network)
|
||||
|| (hasCap != ((NetworkCapabilities) cb.arg).hasCapability(cap))) {
|
||||
Log.i("NetworkCallbackTest#expectCapabilitiesCallback",
|
||||
"Ignoring non-matching callback : " + cb);
|
||||
continue;
|
||||
}
|
||||
// Found a match, return
|
||||
return;
|
||||
} while (System.currentTimeMillis() <= deadline);
|
||||
fail("Didn't receive the expected callback to onCapabilitiesChanged(). Check the "
|
||||
+ "log for a list of received callbacks, if any.");
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
assumeTrue(isActiveNetworkMetered(true) || canChangeActiveNetworkMeteredness());
|
||||
|
||||
registerBroadcastReceiver();
|
||||
|
||||
removeRestrictBackgroundWhitelist(mUid);
|
||||
removeRestrictBackgroundBlacklist(mUid);
|
||||
assertRestrictBackgroundChangedReceived(0);
|
||||
|
||||
// Initial state
|
||||
setBatterySaverMode(false);
|
||||
setRestrictBackground(false);
|
||||
|
||||
// Make wifi a metered network.
|
||||
mMeterednessConfiguration.configureNetworkMeteredness(true);
|
||||
|
||||
// Register callback
|
||||
registerNetworkCallback((INetworkCallback.Stub) mTestNetworkCallback);
|
||||
// Once the wifi is marked as metered, the wifi will reconnect. Wait for onAvailable()
|
||||
// callback to ensure wifi is connected before the test and store the default network.
|
||||
mNetwork = mTestNetworkCallback.expectAvailableCallbackAndGetNetwork();
|
||||
// Check that the network is metered.
|
||||
mTestNetworkCallback.expectCapabilitiesCallbackEventually(mNetwork,
|
||||
false /* hasCapability */, NET_CAPABILITY_NOT_METERED);
|
||||
mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
super.tearDown();
|
||||
|
||||
setRestrictBackground(false);
|
||||
setBatterySaverMode(false);
|
||||
unregisterNetworkCallback();
|
||||
}
|
||||
|
||||
@RequiredProperties({DATA_SAVER_MODE})
|
||||
@Test
|
||||
public void testOnBlockedStatusChanged_dataSaver() throws Exception {
|
||||
try {
|
||||
// Enable restrict background
|
||||
setRestrictBackground(true);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, true);
|
||||
|
||||
// Add to whitelist
|
||||
addRestrictBackgroundWhitelist(mUid);
|
||||
assertBackgroundNetworkAccess(true);
|
||||
mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, false);
|
||||
|
||||
// Remove from whitelist
|
||||
removeRestrictBackgroundWhitelist(mUid);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, true);
|
||||
} finally {
|
||||
mMeterednessConfiguration.resetNetworkMeteredness();
|
||||
}
|
||||
|
||||
// Set to non-metered network
|
||||
mMeterednessConfiguration.configureNetworkMeteredness(false);
|
||||
mTestNetworkCallback.expectCapabilitiesCallbackEventually(mNetwork,
|
||||
true /* hasCapability */, NET_CAPABILITY_NOT_METERED);
|
||||
try {
|
||||
assertBackgroundNetworkAccess(true);
|
||||
mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, false);
|
||||
|
||||
// Disable restrict background, should not trigger callback
|
||||
setRestrictBackground(false);
|
||||
assertBackgroundNetworkAccess(true);
|
||||
} finally {
|
||||
mMeterednessConfiguration.resetNetworkMeteredness();
|
||||
}
|
||||
}
|
||||
|
||||
@RequiredProperties({BATTERY_SAVER_MODE})
|
||||
@Test
|
||||
public void testOnBlockedStatusChanged_powerSaver() throws Exception {
|
||||
try {
|
||||
// Enable Power Saver
|
||||
setBatterySaverMode(true);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, true);
|
||||
|
||||
// Disable Power Saver
|
||||
setBatterySaverMode(false);
|
||||
assertBackgroundNetworkAccess(true);
|
||||
mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, false);
|
||||
} finally {
|
||||
mMeterednessConfiguration.resetNetworkMeteredness();
|
||||
}
|
||||
|
||||
// Set to non-metered network
|
||||
mMeterednessConfiguration.configureNetworkMeteredness(false);
|
||||
mTestNetworkCallback.expectCapabilitiesCallbackEventually(mNetwork,
|
||||
true /* hasCapability */, NET_CAPABILITY_NOT_METERED);
|
||||
try {
|
||||
// Enable Power Saver
|
||||
setBatterySaverMode(true);
|
||||
assertBackgroundNetworkAccess(false);
|
||||
mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, true);
|
||||
|
||||
// Disable Power Saver
|
||||
setBatterySaverMode(false);
|
||||
assertBackgroundNetworkAccess(true);
|
||||
mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, false);
|
||||
} finally {
|
||||
mMeterednessConfiguration.resetNetworkMeteredness();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: 1. test against VPN lockdown.
|
||||
// 2. test against multiple networks.
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.cts.net.hostside;
|
||||
|
||||
import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner;
|
||||
|
||||
import org.junit.rules.RunRules;
|
||||
import org.junit.rules.TestRule;
|
||||
import org.junit.runners.model.FrameworkMethod;
|
||||
import org.junit.runners.model.InitializationError;
|
||||
import org.junit.runners.model.Statement;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Custom runner to allow dumping logs after a test failure before the @After methods get to run.
|
||||
*/
|
||||
public class NetworkPolicyTestRunner extends AndroidJUnit4ClassRunner {
|
||||
private TestRule mDumpOnFailureRule = new DumpOnFailureRule();
|
||||
|
||||
public NetworkPolicyTestRunner(Class<?> klass) throws InitializationError {
|
||||
super(klass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Statement methodInvoker(FrameworkMethod method, Object test) {
|
||||
return new RunRules(super.methodInvoker(method, test), List.of(mDumpOnFailureRule),
|
||||
describeChild(method));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.cts.net.hostside;
|
||||
|
||||
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
|
||||
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
|
||||
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED;
|
||||
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
|
||||
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
|
||||
|
||||
import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
|
||||
import static com.android.cts.net.hostside.AbstractRestrictBackgroundNetworkTestCase.TAG;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.Instrumentation;
|
||||
import android.content.Context;
|
||||
import android.location.LocationManager;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.ConnectivityManager.NetworkCallback;
|
||||
import android.net.Network;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.os.Process;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import com.android.compatibility.common.util.AppStandbyUtils;
|
||||
import com.android.compatibility.common.util.BatteryUtils;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
|
||||
public class NetworkPolicyTestUtils {
|
||||
|
||||
private static final int TIMEOUT_CHANGE_METEREDNESS_MS = 5000;
|
||||
|
||||
private static ConnectivityManager mCm;
|
||||
private static WifiManager mWm;
|
||||
|
||||
private static Boolean mBatterySaverSupported;
|
||||
private static Boolean mDataSaverSupported;
|
||||
private static Boolean mDozeModeSupported;
|
||||
private static Boolean mAppStandbySupported;
|
||||
|
||||
private NetworkPolicyTestUtils() {}
|
||||
|
||||
public static boolean isBatterySaverSupported() {
|
||||
if (mBatterySaverSupported == null) {
|
||||
mBatterySaverSupported = BatteryUtils.isBatterySaverSupported();
|
||||
}
|
||||
return mBatterySaverSupported;
|
||||
}
|
||||
|
||||
/**
|
||||
* As per CDD requirements, if the device doesn't support data saver mode then
|
||||
* ConnectivityManager.getRestrictBackgroundStatus() will always return
|
||||
* RESTRICT_BACKGROUND_STATUS_DISABLED. So, enable the data saver mode and check if
|
||||
* ConnectivityManager.getRestrictBackgroundStatus() for an app in background returns
|
||||
* RESTRICT_BACKGROUND_STATUS_DISABLED or not.
|
||||
*/
|
||||
public static boolean isDataSaverSupported() {
|
||||
if (mDataSaverSupported == null) {
|
||||
assertMyRestrictBackgroundStatus(RESTRICT_BACKGROUND_STATUS_DISABLED);
|
||||
try {
|
||||
setRestrictBackground(true);
|
||||
mDataSaverSupported = !isMyRestrictBackgroundStatus(
|
||||
RESTRICT_BACKGROUND_STATUS_DISABLED);
|
||||
} finally {
|
||||
setRestrictBackground(false);
|
||||
}
|
||||
}
|
||||
return mDataSaverSupported;
|
||||
}
|
||||
|
||||
public static boolean isDozeModeSupported() {
|
||||
if (mDozeModeSupported == null) {
|
||||
final String result = executeShellCommand("cmd deviceidle enabled deep");
|
||||
mDozeModeSupported = result.equals("1");
|
||||
}
|
||||
return mDozeModeSupported;
|
||||
}
|
||||
|
||||
public static boolean isAppStandbySupported() {
|
||||
if (mAppStandbySupported == null) {
|
||||
mAppStandbySupported = AppStandbyUtils.isAppStandbyEnabled();
|
||||
}
|
||||
return mAppStandbySupported;
|
||||
}
|
||||
|
||||
public static boolean isLowRamDevice() {
|
||||
final ActivityManager am = (ActivityManager) getContext().getSystemService(
|
||||
Context.ACTIVITY_SERVICE);
|
||||
return am.isLowRamDevice();
|
||||
}
|
||||
|
||||
public static boolean isLocationEnabled() {
|
||||
final LocationManager lm = (LocationManager) getContext().getSystemService(
|
||||
Context.LOCATION_SERVICE);
|
||||
return lm.isLocationEnabled();
|
||||
}
|
||||
|
||||
public static void setLocationEnabled(boolean enabled) {
|
||||
final LocationManager lm = (LocationManager) getContext().getSystemService(
|
||||
Context.LOCATION_SERVICE);
|
||||
lm.setLocationEnabledForUser(enabled, Process.myUserHandle());
|
||||
assertEquals("Couldn't change location enabled state", lm.isLocationEnabled(), enabled);
|
||||
Log.d(TAG, "Changed location enabled state to " + enabled);
|
||||
}
|
||||
|
||||
public static boolean isActiveNetworkMetered(boolean metered) {
|
||||
return getConnectivityManager().isActiveNetworkMetered() == metered;
|
||||
}
|
||||
|
||||
public static boolean canChangeActiveNetworkMeteredness() {
|
||||
final Network activeNetwork = getConnectivityManager().getActiveNetwork();
|
||||
final NetworkCapabilities networkCapabilities
|
||||
= getConnectivityManager().getNetworkCapabilities(activeNetwork);
|
||||
return networkCapabilities.hasTransport(TRANSPORT_WIFI);
|
||||
}
|
||||
|
||||
public static Pair<String, Boolean> setupMeteredNetwork(boolean metered) throws Exception {
|
||||
if (isActiveNetworkMetered(metered)) {
|
||||
return null;
|
||||
}
|
||||
final boolean isLocationEnabled = isLocationEnabled();
|
||||
try {
|
||||
if (!isLocationEnabled) {
|
||||
setLocationEnabled(true);
|
||||
}
|
||||
final String ssid = unquoteSSID(getWifiManager().getConnectionInfo().getSSID());
|
||||
assertNotEquals(WifiManager.UNKNOWN_SSID, ssid);
|
||||
setWifiMeteredStatus(ssid, metered);
|
||||
return Pair.create(ssid, !metered);
|
||||
} finally {
|
||||
// Reset the location enabled state
|
||||
if (!isLocationEnabled) {
|
||||
setLocationEnabled(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void resetMeteredNetwork(String ssid, boolean metered) throws Exception {
|
||||
setWifiMeteredStatus(ssid, metered);
|
||||
}
|
||||
|
||||
public static void setWifiMeteredStatus(String ssid, boolean metered) throws Exception {
|
||||
assertFalse("SSID should not be empty", TextUtils.isEmpty(ssid));
|
||||
final String cmd = "cmd netpolicy set metered-network " + ssid + " " + metered;
|
||||
executeShellCommand(cmd);
|
||||
assertWifiMeteredStatus(ssid, metered);
|
||||
assertActiveNetworkMetered(metered);
|
||||
}
|
||||
|
||||
public static void assertWifiMeteredStatus(String ssid, boolean expectedMeteredStatus) {
|
||||
final String result = executeShellCommand("cmd netpolicy list wifi-networks");
|
||||
final String expectedLine = ssid + ";" + expectedMeteredStatus;
|
||||
assertTrue("Expected line: " + expectedLine + "; Actual result: " + result,
|
||||
result.contains(expectedLine));
|
||||
}
|
||||
|
||||
// Copied from cts/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
|
||||
public static void assertActiveNetworkMetered(boolean expectedMeteredStatus) throws Exception {
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
final NetworkCallback networkCallback = new NetworkCallback() {
|
||||
@Override
|
||||
public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
|
||||
final boolean metered = !nc.hasCapability(NET_CAPABILITY_NOT_METERED);
|
||||
if (metered == expectedMeteredStatus) {
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
};
|
||||
// Registering a callback here guarantees onCapabilitiesChanged is called immediately
|
||||
// with the current setting. Therefore, if the setting has already been changed,
|
||||
// this method will return right away, and if not it will wait for the setting to change.
|
||||
getConnectivityManager().registerDefaultNetworkCallback(networkCallback);
|
||||
if (!latch.await(TIMEOUT_CHANGE_METEREDNESS_MS, TimeUnit.MILLISECONDS)) {
|
||||
fail("Timed out waiting for active network metered status to change to "
|
||||
+ expectedMeteredStatus + " ; network = "
|
||||
+ getConnectivityManager().getActiveNetwork());
|
||||
}
|
||||
getConnectivityManager().unregisterNetworkCallback(networkCallback);
|
||||
}
|
||||
|
||||
public static void setRestrictBackground(boolean enabled) {
|
||||
executeShellCommand("cmd netpolicy set restrict-background " + enabled);
|
||||
final String output = executeShellCommand("cmd netpolicy get restrict-background");
|
||||
final String expectedSuffix = enabled ? "enabled" : "disabled";
|
||||
assertTrue("output '" + output + "' should end with '" + expectedSuffix + "'",
|
||||
output.endsWith(expectedSuffix));
|
||||
}
|
||||
|
||||
public static boolean isMyRestrictBackgroundStatus(int expectedStatus) {
|
||||
final int actualStatus = getConnectivityManager().getRestrictBackgroundStatus();
|
||||
if (expectedStatus != actualStatus) {
|
||||
Log.d(TAG, "MyRestrictBackgroundStatus: "
|
||||
+ "Expected: " + restrictBackgroundValueToString(expectedStatus)
|
||||
+ "; Actual: " + restrictBackgroundValueToString(actualStatus));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Copied from cts/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
|
||||
private static String unquoteSSID(String ssid) {
|
||||
// SSID is returned surrounded by quotes if it can be decoded as UTF-8.
|
||||
// Otherwise it's guaranteed not to start with a quote.
|
||||
if (ssid.charAt(0) == '"') {
|
||||
return ssid.substring(1, ssid.length() - 1);
|
||||
} else {
|
||||
return ssid;
|
||||
}
|
||||
}
|
||||
|
||||
public static String restrictBackgroundValueToString(int status) {
|
||||
switch (status) {
|
||||
case RESTRICT_BACKGROUND_STATUS_DISABLED:
|
||||
return "DISABLED";
|
||||
case RESTRICT_BACKGROUND_STATUS_WHITELISTED:
|
||||
return "WHITELISTED";
|
||||
case RESTRICT_BACKGROUND_STATUS_ENABLED:
|
||||
return "ENABLED";
|
||||
default:
|
||||
return "UNKNOWN_STATUS_" + status;
|
||||
}
|
||||
}
|
||||
|
||||
public static String executeShellCommand(String command) {
|
||||
final String result = runShellCommand(command).trim();
|
||||
Log.d(TAG, "Output of '" + command + "': '" + result + "'");
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void assertMyRestrictBackgroundStatus(int expectedStatus) {
|
||||
final int actualStatus = getConnectivityManager().getRestrictBackgroundStatus();
|
||||
assertEquals(restrictBackgroundValueToString(expectedStatus),
|
||||
restrictBackgroundValueToString(actualStatus));
|
||||
}
|
||||
|
||||
public static ConnectivityManager getConnectivityManager() {
|
||||
if (mCm == null) {
|
||||
mCm = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
}
|
||||
return mCm;
|
||||
}
|
||||
|
||||
public static WifiManager getWifiManager() {
|
||||
if (mWm == null) {
|
||||
mWm = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE);
|
||||
}
|
||||
return mWm;
|
||||
}
|
||||
|
||||
public static Context getContext() {
|
||||
return getInstrumentation().getContext();
|
||||
}
|
||||
|
||||
public static Instrumentation getInstrumentation() {
|
||||
return InstrumentationRegistry.getInstrumentation();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.cts.net.hostside;
|
||||
|
||||
import static android.system.OsConstants.ICMP6_ECHO_REPLY;
|
||||
import static android.system.OsConstants.ICMP6_ECHO_REQUEST;
|
||||
import static android.system.OsConstants.ICMP_ECHO;
|
||||
import static android.system.OsConstants.ICMP_ECHOREPLY;
|
||||
|
||||
import android.system.ErrnoException;
|
||||
import android.system.Os;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.IOException;
|
||||
|
||||
public class PacketReflector extends Thread {
|
||||
|
||||
private static int IPV4_HEADER_LENGTH = 20;
|
||||
private static int IPV6_HEADER_LENGTH = 40;
|
||||
|
||||
private static int IPV4_ADDR_OFFSET = 12;
|
||||
private static int IPV6_ADDR_OFFSET = 8;
|
||||
private static int IPV4_ADDR_LENGTH = 4;
|
||||
private static int IPV6_ADDR_LENGTH = 16;
|
||||
|
||||
private static int IPV4_PROTO_OFFSET = 9;
|
||||
private static int IPV6_PROTO_OFFSET = 6;
|
||||
|
||||
private static final byte IPPROTO_ICMP = 1;
|
||||
private static final byte IPPROTO_TCP = 6;
|
||||
private static final byte IPPROTO_UDP = 17;
|
||||
private static final byte IPPROTO_ICMPV6 = 58;
|
||||
|
||||
private static int ICMP_HEADER_LENGTH = 8;
|
||||
private static int TCP_HEADER_LENGTH = 20;
|
||||
private static int UDP_HEADER_LENGTH = 8;
|
||||
|
||||
private static final byte ICMP_ECHO = 8;
|
||||
private static final byte ICMP_ECHOREPLY = 0;
|
||||
|
||||
private static String TAG = "PacketReflector";
|
||||
|
||||
private FileDescriptor mFd;
|
||||
private byte[] mBuf;
|
||||
|
||||
public PacketReflector(FileDescriptor fd, int mtu) {
|
||||
super("PacketReflector");
|
||||
mFd = fd;
|
||||
mBuf = new byte[mtu];
|
||||
}
|
||||
|
||||
private static void swapBytes(byte[] buf, int pos1, int pos2, int len) {
|
||||
for (int i = 0; i < len; i++) {
|
||||
byte b = buf[pos1 + i];
|
||||
buf[pos1 + i] = buf[pos2 + i];
|
||||
buf[pos2 + i] = b;
|
||||
}
|
||||
}
|
||||
|
||||
private static void swapAddresses(byte[] buf, int version) {
|
||||
int addrPos, addrLen;
|
||||
switch(version) {
|
||||
case 4:
|
||||
addrPos = IPV4_ADDR_OFFSET;
|
||||
addrLen = IPV4_ADDR_LENGTH;
|
||||
break;
|
||||
case 6:
|
||||
addrPos = IPV6_ADDR_OFFSET;
|
||||
addrLen = IPV6_ADDR_LENGTH;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
swapBytes(buf, addrPos, addrPos + addrLen, addrLen);
|
||||
}
|
||||
|
||||
// Reflect TCP packets: swap the source and destination addresses, but don't change the ports.
|
||||
// This is used by the test to "connect to itself" through the VPN.
|
||||
private void processTcpPacket(byte[] buf, int version, int len, int hdrLen) {
|
||||
if (len < hdrLen + TCP_HEADER_LENGTH) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Swap src and dst IP addresses.
|
||||
swapAddresses(buf, version);
|
||||
|
||||
// Send the packet back.
|
||||
writePacket(buf, len);
|
||||
}
|
||||
|
||||
// Echo UDP packets: swap source and destination addresses, and source and destination ports.
|
||||
// This is used by the test to check that the bytes it sends are echoed back.
|
||||
private void processUdpPacket(byte[] buf, int version, int len, int hdrLen) {
|
||||
if (len < hdrLen + UDP_HEADER_LENGTH) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Swap src and dst IP addresses.
|
||||
swapAddresses(buf, version);
|
||||
|
||||
// Swap dst and src ports.
|
||||
int portOffset = hdrLen;
|
||||
swapBytes(buf, portOffset, portOffset + 2, 2);
|
||||
|
||||
// Send the packet back.
|
||||
writePacket(buf, len);
|
||||
}
|
||||
|
||||
private void processIcmpPacket(byte[] buf, int version, int len, int hdrLen) {
|
||||
if (len < hdrLen + ICMP_HEADER_LENGTH) {
|
||||
return;
|
||||
}
|
||||
|
||||
byte type = buf[hdrLen];
|
||||
if (!(version == 4 && type == ICMP_ECHO) &&
|
||||
!(version == 6 && type == (byte) ICMP6_ECHO_REQUEST)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Save the ping packet we received.
|
||||
byte[] request = buf.clone();
|
||||
|
||||
// Swap src and dst IP addresses, and send the packet back.
|
||||
// This effectively pings the device to see if it replies.
|
||||
swapAddresses(buf, version);
|
||||
writePacket(buf, len);
|
||||
|
||||
// The device should have replied, and buf should now contain a ping response.
|
||||
int received = readPacket(buf);
|
||||
if (received != len) {
|
||||
Log.i(TAG, "Reflecting ping did not result in ping response: " +
|
||||
"read=" + received + " expected=" + len);
|
||||
return;
|
||||
}
|
||||
|
||||
byte replyType = buf[hdrLen];
|
||||
if ((type == ICMP_ECHO && replyType != ICMP_ECHOREPLY)
|
||||
|| (type == (byte) ICMP6_ECHO_REQUEST && replyType != (byte) ICMP6_ECHO_REPLY)) {
|
||||
Log.i(TAG, "Received unexpected ICMP reply: original " + type
|
||||
+ ", reply " + replyType);
|
||||
return;
|
||||
}
|
||||
|
||||
// Compare the response we got with the original packet.
|
||||
// The only thing that should have changed are addresses, type and checksum.
|
||||
// Overwrite them with the received bytes and see if the packet is otherwise identical.
|
||||
request[hdrLen] = buf[hdrLen]; // Type
|
||||
request[hdrLen + 2] = buf[hdrLen + 2]; // Checksum byte 1.
|
||||
request[hdrLen + 3] = buf[hdrLen + 3]; // Checksum byte 2.
|
||||
|
||||
// Since Linux kernel 4.2, net.ipv6.auto_flowlabels is set by default, and therefore
|
||||
// the request and reply may have different IPv6 flow label: ignore that as well.
|
||||
if (version == 6) {
|
||||
request[1] = (byte)(request[1] & 0xf0 | buf[1] & 0x0f);
|
||||
request[2] = buf[2];
|
||||
request[3] = buf[3];
|
||||
}
|
||||
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (buf[i] != request[i]) {
|
||||
Log.i(TAG, "Received non-matching packet when expecting ping response.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Now swap the addresses again and reflect the packet. This sends a ping reply.
|
||||
swapAddresses(buf, version);
|
||||
writePacket(buf, len);
|
||||
}
|
||||
|
||||
private void writePacket(byte[] buf, int len) {
|
||||
try {
|
||||
Os.write(mFd, buf, 0, len);
|
||||
} catch (ErrnoException|IOException e) {
|
||||
Log.e(TAG, "Error writing packet: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private int readPacket(byte[] buf) {
|
||||
int len;
|
||||
try {
|
||||
len = Os.read(mFd, buf, 0, buf.length);
|
||||
} catch (ErrnoException|IOException e) {
|
||||
Log.e(TAG, "Error reading packet: " + e.getMessage());
|
||||
len = -1;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
// Reads one packet from our mFd, and possibly writes the packet back.
|
||||
private void processPacket() {
|
||||
int len = readPacket(mBuf);
|
||||
if (len < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
int version = mBuf[0] >> 4;
|
||||
int addrPos, protoPos, hdrLen, addrLen;
|
||||
if (version == 4) {
|
||||
hdrLen = IPV4_HEADER_LENGTH;
|
||||
protoPos = IPV4_PROTO_OFFSET;
|
||||
addrPos = IPV4_ADDR_OFFSET;
|
||||
addrLen = IPV4_ADDR_LENGTH;
|
||||
} else if (version == 6) {
|
||||
hdrLen = IPV6_HEADER_LENGTH;
|
||||
protoPos = IPV6_PROTO_OFFSET;
|
||||
addrPos = IPV6_ADDR_OFFSET;
|
||||
addrLen = IPV6_ADDR_LENGTH;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (len < hdrLen) {
|
||||
return;
|
||||
}
|
||||
|
||||
byte proto = mBuf[protoPos];
|
||||
switch (proto) {
|
||||
case IPPROTO_ICMP:
|
||||
case IPPROTO_ICMPV6:
|
||||
processIcmpPacket(mBuf, version, len, hdrLen);
|
||||
break;
|
||||
case IPPROTO_TCP:
|
||||
processTcpPacket(mBuf, version, len, hdrLen);
|
||||
break;
|
||||
case IPPROTO_UDP:
|
||||
processUdpPacket(mBuf, version, len, hdrLen);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void run() {
|
||||
Log.i(TAG, "PacketReflector starting fd=" + mFd + " valid=" + mFd.valid());
|
||||
while (!interrupted() && mFd.valid()) {
|
||||
processPacket();
|
||||
}
|
||||
Log.i(TAG, "PacketReflector exiting fd=" + mFd + " valid=" + mFd.valid());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.cts.net.hostside;
|
||||
|
||||
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.canChangeActiveNetworkMeteredness;
|
||||
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isActiveNetworkMetered;
|
||||
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isAppStandbySupported;
|
||||
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isBatterySaverSupported;
|
||||
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isDataSaverSupported;
|
||||
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isDozeModeSupported;
|
||||
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isLowRamDevice;
|
||||
|
||||
public enum Property {
|
||||
BATTERY_SAVER_MODE(1 << 0) {
|
||||
public boolean isSupported() { return isBatterySaverSupported(); }
|
||||
},
|
||||
|
||||
DATA_SAVER_MODE(1 << 1) {
|
||||
public boolean isSupported() { return isDataSaverSupported(); }
|
||||
},
|
||||
|
||||
NO_DATA_SAVER_MODE(~DATA_SAVER_MODE.getValue()) {
|
||||
public boolean isSupported() { return !isDataSaverSupported(); }
|
||||
},
|
||||
|
||||
DOZE_MODE(1 << 2) {
|
||||
public boolean isSupported() { return isDozeModeSupported(); }
|
||||
},
|
||||
|
||||
APP_STANDBY_MODE(1 << 3) {
|
||||
public boolean isSupported() { return isAppStandbySupported(); }
|
||||
},
|
||||
|
||||
NOT_LOW_RAM_DEVICE(1 << 4) {
|
||||
public boolean isSupported() { return !isLowRamDevice(); }
|
||||
},
|
||||
|
||||
METERED_NETWORK(1 << 5) {
|
||||
public boolean isSupported() {
|
||||
return isActiveNetworkMetered(true) || canChangeActiveNetworkMeteredness();
|
||||
}
|
||||
},
|
||||
|
||||
NON_METERED_NETWORK(~METERED_NETWORK.getValue()) {
|
||||
public boolean isSupported() {
|
||||
return isActiveNetworkMetered(false) || canChangeActiveNetworkMeteredness();
|
||||
}
|
||||
};
|
||||
|
||||
private int mValue;
|
||||
|
||||
Property(int value) { mValue = value; }
|
||||
|
||||
public int getValue() { return mValue; }
|
||||
|
||||
abstract boolean isSupported();
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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.cts.net.hostside;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.os.ConditionVariable;
|
||||
import android.os.IBinder;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.RemoteException;
|
||||
import android.system.ErrnoException;
|
||||
import android.system.Os;
|
||||
|
||||
import com.android.cts.net.hostside.IRemoteSocketFactory;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.IOException;
|
||||
|
||||
public class RemoteSocketFactoryClient {
|
||||
private static final int TIMEOUT_MS = 5000;
|
||||
private static final String PACKAGE = RemoteSocketFactoryClient.class.getPackage().getName();
|
||||
private static final String APP2_PACKAGE = PACKAGE + ".app2";
|
||||
private static final String SERVICE_NAME = APP2_PACKAGE + ".RemoteSocketFactoryService";
|
||||
|
||||
private Context mContext;
|
||||
private ServiceConnection mServiceConnection;
|
||||
private IRemoteSocketFactory mService;
|
||||
|
||||
public RemoteSocketFactoryClient(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
public void bind() {
|
||||
if (mService != null) {
|
||||
throw new IllegalStateException("Already bound");
|
||||
}
|
||||
|
||||
final ConditionVariable cv = new ConditionVariable();
|
||||
mServiceConnection = new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||
mService = IRemoteSocketFactory.Stub.asInterface(service);
|
||||
cv.open();
|
||||
}
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
mService = null;
|
||||
}
|
||||
};
|
||||
|
||||
final Intent intent = new Intent();
|
||||
intent.setComponent(new ComponentName(APP2_PACKAGE, SERVICE_NAME));
|
||||
mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
|
||||
cv.block(TIMEOUT_MS);
|
||||
if (mService == null) {
|
||||
throw new IllegalStateException(
|
||||
"Could not bind to RemoteSocketFactory service after " + TIMEOUT_MS + "ms");
|
||||
}
|
||||
}
|
||||
|
||||
public void unbind() {
|
||||
if (mService != null) {
|
||||
mContext.unbindService(mServiceConnection);
|
||||
}
|
||||
}
|
||||
|
||||
public FileDescriptor openSocketFd(String host, int port, int timeoutMs)
|
||||
throws RemoteException, ErrnoException, IOException {
|
||||
// Dup the filedescriptor so ParcelFileDescriptor's finalizer doesn't garbage collect it
|
||||
// and cause our fd to become invalid. http://b/35927643 .
|
||||
ParcelFileDescriptor pfd = mService.openSocketFd(host, port, timeoutMs);
|
||||
FileDescriptor fd = Os.dup(pfd.getFileDescriptor());
|
||||
pfd.close();
|
||||
return fd;
|
||||
}
|
||||
|
||||
public String getPackageName() throws RemoteException {
|
||||
return mService.getPackageName();
|
||||
}
|
||||
|
||||
public int getUid() throws RemoteException {
|
||||
return mService.getUid();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.cts.net.hostside;
|
||||
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.ElementType.TYPE;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RUNTIME)
|
||||
@Target({METHOD, TYPE})
|
||||
@Inherited
|
||||
public @interface RequiredProperties {
|
||||
Property[] value();
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.cts.net.hostside;
|
||||
|
||||
import static com.android.cts.net.hostside.AbstractRestrictBackgroundNetworkTestCase.TAG;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.compatibility.common.util.BeforeAfterRule;
|
||||
|
||||
import org.junit.Assume;
|
||||
import org.junit.runner.Description;
|
||||
import org.junit.runners.model.Statement;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
||||
public class RequiredPropertiesRule extends BeforeAfterRule {
|
||||
|
||||
private static ArraySet<Property> mRequiredProperties;
|
||||
|
||||
@Override
|
||||
public void onBefore(Statement base, Description description) {
|
||||
mRequiredProperties = getAllRequiredProperties(description);
|
||||
|
||||
final String testName = description.getClassName() + "#" + description.getMethodName();
|
||||
assertTestIsValid(testName, mRequiredProperties);
|
||||
Log.i(TAG, "Running test " + testName + " with required properties: "
|
||||
+ propertiesToString(mRequiredProperties));
|
||||
}
|
||||
|
||||
private ArraySet<Property> getAllRequiredProperties(Description description) {
|
||||
final ArraySet<Property> allRequiredProperties = new ArraySet<>();
|
||||
RequiredProperties requiredProperties = description.getAnnotation(RequiredProperties.class);
|
||||
if (requiredProperties != null) {
|
||||
Collections.addAll(allRequiredProperties, requiredProperties.value());
|
||||
}
|
||||
|
||||
for (Class<?> clazz = description.getTestClass();
|
||||
clazz != null; clazz = clazz.getSuperclass()) {
|
||||
requiredProperties = clazz.getDeclaredAnnotation(RequiredProperties.class);
|
||||
if (requiredProperties == null) {
|
||||
continue;
|
||||
}
|
||||
for (Property requiredProperty : requiredProperties.value()) {
|
||||
for (Property p : Property.values()) {
|
||||
if (p.getValue() == ~requiredProperty.getValue()
|
||||
&& allRequiredProperties.contains(p)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
allRequiredProperties.add(requiredProperty);
|
||||
}
|
||||
}
|
||||
return allRequiredProperties;
|
||||
}
|
||||
|
||||
private void assertTestIsValid(String testName, ArraySet<Property> requiredProperies) {
|
||||
if (requiredProperies == null) {
|
||||
return;
|
||||
}
|
||||
final ArrayList<Property> unsupportedProperties = new ArrayList<>();
|
||||
for (Property property : requiredProperies) {
|
||||
if (!property.isSupported()) {
|
||||
unsupportedProperties.add(property);
|
||||
}
|
||||
}
|
||||
Assume.assumeTrue("Unsupported properties: "
|
||||
+ propertiesToString(unsupportedProperties), unsupportedProperties.isEmpty());
|
||||
}
|
||||
|
||||
public static ArraySet<Property> getRequiredProperties() {
|
||||
return mRequiredProperties;
|
||||
}
|
||||
|
||||
private static String propertiesToString(Iterable<Property> properties) {
|
||||
return "[" + TextUtils.join(",", properties) + "]";
|
||||
}
|
||||
}
|
||||
1165
tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
Executable file
1165
tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
Executable file
File diff suppressed because it is too large
Load Diff
29
tests/cts/hostside/app2/Android.bp
Normal file
29
tests/cts/hostside/app2/Android.bp
Normal file
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
android_test_helper_app {
|
||||
name: "CtsHostsideNetworkTestsApp2",
|
||||
defaults: ["cts_support_defaults"],
|
||||
sdk_version: "current",
|
||||
static_libs: ["CtsHostsideNetworkTestsAidl"],
|
||||
srcs: ["src/**/*.java"],
|
||||
// Tag this module as a cts test artifact
|
||||
test_suites: [
|
||||
"cts",
|
||||
"general-tests",
|
||||
],
|
||||
certificate: ":cts-net-app",
|
||||
}
|
||||
55
tests/cts/hostside/app2/AndroidManifest.xml
Normal file
55
tests/cts/hostside/app2/AndroidManifest.xml
Normal file
@@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.android.cts.net.hostside.app2" >
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<!--
|
||||
This application is used to listen to RESTRICT_BACKGROUND_CHANGED intents and store
|
||||
them in a shared preferences which is then read by the test app. These broadcasts are
|
||||
handled by 2 listeners, one defined the manifest and another dynamically registered by
|
||||
a service.
|
||||
|
||||
The manifest-defined listener also handles ordered broadcasts used to share data with the
|
||||
test app.
|
||||
|
||||
This application also provides a service, RemoteSocketFactoryService, that the test app can
|
||||
use to open sockets to remote hosts as a different user ID.
|
||||
-->
|
||||
<application android:usesCleartextTraffic="true">
|
||||
<activity android:name=".MyActivity" android:exported="true"/>
|
||||
<service android:name=".MyService" android:exported="true"/>
|
||||
<service android:name=".MyForegroundService" android:exported="true"/>
|
||||
<service android:name=".RemoteSocketFactoryService" android:exported="true"/>
|
||||
|
||||
<receiver android:name=".MyBroadcastReceiver" >
|
||||
<intent-filter>
|
||||
<action android:name="android.net.conn.RESTRICT_BACKGROUND_CHANGED" />
|
||||
<action android:name="com.android.cts.net.hostside.app2.action.GET_COUNTERS" />
|
||||
<action android:name="com.android.cts.net.hostside.app2.action.GET_RESTRICT_BACKGROUND_STATUS" />
|
||||
<action android:name="com.android.cts.net.hostside.app2.action.CHECK_NETWORK" />
|
||||
<action android:name="com.android.cts.net.hostside.app2.action.SEND_NOTIFICATION" />
|
||||
<action android:name="com.android.cts.net.hostside.app2.action.SHOW_TOAST" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
BIN
tests/cts/hostside/app2/res/drawable/ic_notification.png
Normal file
BIN
tests/cts/hostside/app2/res/drawable/ic_notification.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* 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.cts.net.hostside.app2;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.cts.net.hostside.INetworkStateObserver;
|
||||
|
||||
public final class Common {
|
||||
|
||||
static final String TAG = "CtsNetApp2";
|
||||
|
||||
// Constants below must match values defined on app's
|
||||
// AbstractRestrictBackgroundNetworkTestCase.java
|
||||
static final String MANIFEST_RECEIVER = "ManifestReceiver";
|
||||
static final String DYNAMIC_RECEIVER = "DynamicReceiver";
|
||||
|
||||
static final String ACTION_RECEIVER_READY =
|
||||
"com.android.cts.net.hostside.app2.action.RECEIVER_READY";
|
||||
static final String ACTION_FINISH_ACTIVITY =
|
||||
"com.android.cts.net.hostside.app2.action.FINISH_ACTIVITY";
|
||||
static final String ACTION_SHOW_TOAST =
|
||||
"com.android.cts.net.hostside.app2.action.SHOW_TOAST";
|
||||
|
||||
static final String NOTIFICATION_TYPE_CONTENT = "CONTENT";
|
||||
static final String NOTIFICATION_TYPE_DELETE = "DELETE";
|
||||
static final String NOTIFICATION_TYPE_FULL_SCREEN = "FULL_SCREEN";
|
||||
static final String NOTIFICATION_TYPE_BUNDLE = "BUNDLE";
|
||||
static final String NOTIFICATION_TYPE_ACTION = "ACTION";
|
||||
static final String NOTIFICATION_TYPE_ACTION_BUNDLE = "ACTION_BUNDLE";
|
||||
static final String NOTIFICATION_TYPE_ACTION_REMOTE_INPUT = "ACTION_REMOTE_INPUT";
|
||||
|
||||
static final String TEST_PKG = "com.android.cts.net.hostside";
|
||||
static final String KEY_NETWORK_STATE_OBSERVER = TEST_PKG + ".observer";
|
||||
|
||||
static int getUid(Context context) {
|
||||
final String packageName = context.getPackageName();
|
||||
try {
|
||||
return context.getPackageManager().getPackageUid(packageName, 0);
|
||||
} catch (NameNotFoundException e) {
|
||||
throw new IllegalStateException("Could not get UID for " + packageName, e);
|
||||
}
|
||||
}
|
||||
|
||||
static void notifyNetworkStateObserver(Context context, Intent intent) {
|
||||
if (intent == null) {
|
||||
return;
|
||||
}
|
||||
final Bundle extras = intent.getExtras();
|
||||
if (extras == null) {
|
||||
return;
|
||||
}
|
||||
final INetworkStateObserver observer = INetworkStateObserver.Stub.asInterface(
|
||||
extras.getBinder(KEY_NETWORK_STATE_OBSERVER));
|
||||
if (observer != null) {
|
||||
try {
|
||||
if (!observer.isForeground()) {
|
||||
Log.e(TAG, "App didn't come to foreground");
|
||||
observer.onNetworkStateChecked(null);
|
||||
return;
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Error occurred while reading the proc state: " + e);
|
||||
}
|
||||
AsyncTask.execute(() -> {
|
||||
try {
|
||||
observer.onNetworkStateChecked(
|
||||
MyBroadcastReceiver.checkNetworkStatus(context));
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Error occurred while notifying the observer: " + e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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.cts.net.hostside.app2;
|
||||
|
||||
import static com.android.cts.net.hostside.app2.Common.ACTION_FINISH_ACTIVITY;
|
||||
import static com.android.cts.net.hostside.app2.Common.TAG;
|
||||
import static com.android.cts.net.hostside.app2.Common.TEST_PKG;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.cts.net.hostside.INetworkStateObserver;
|
||||
|
||||
/**
|
||||
* Activity used to bring process to foreground.
|
||||
*/
|
||||
public class MyActivity extends Activity {
|
||||
|
||||
private BroadcastReceiver finishCommandReceiver = null;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
Log.d(TAG, "MyActivity.onCreate()");
|
||||
Common.notifyNetworkStateObserver(this, getIntent());
|
||||
finishCommandReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Log.d(TAG, "Finishing MyActivity");
|
||||
MyActivity.this.finish();
|
||||
}
|
||||
};
|
||||
registerReceiver(finishCommandReceiver, new IntentFilter(ACTION_FINISH_ACTIVITY));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
if (finishCommandReceiver != null) {
|
||||
unregisterReceiver(finishCommandReceiver);
|
||||
}
|
||||
super.finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
Log.d(TAG, "MyActivity.onStart()");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
Log.d(TAG, "MyActivity.onDestroy()");
|
||||
super.onDestroy();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
/*
|
||||
* 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.cts.net.hostside.app2;
|
||||
|
||||
import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
|
||||
|
||||
import static com.android.cts.net.hostside.app2.Common.ACTION_RECEIVER_READY;
|
||||
import static com.android.cts.net.hostside.app2.Common.ACTION_SHOW_TOAST;
|
||||
import static com.android.cts.net.hostside.app2.Common.MANIFEST_RECEIVER;
|
||||
import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_ACTION;
|
||||
import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_ACTION_BUNDLE;
|
||||
import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_ACTION_REMOTE_INPUT;
|
||||
import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_BUNDLE;
|
||||
import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_CONTENT;
|
||||
import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_DELETE;
|
||||
import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_FULL_SCREEN;
|
||||
import static com.android.cts.net.hostside.app2.Common.TAG;
|
||||
import static com.android.cts.net.hostside.app2.Common.getUid;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.Notification.Action;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.RemoteInput;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* Receiver used to:
|
||||
* <ol>
|
||||
* <li>Count number of {@code RESTRICT_BACKGROUND_CHANGED} broadcasts received.
|
||||
* <li>Show a toast.
|
||||
* </ol>
|
||||
*/
|
||||
public class MyBroadcastReceiver extends BroadcastReceiver {
|
||||
|
||||
private static final int NETWORK_TIMEOUT_MS = 5 * 1000;
|
||||
|
||||
private final String mName;
|
||||
|
||||
public MyBroadcastReceiver() {
|
||||
this(MANIFEST_RECEIVER);
|
||||
}
|
||||
|
||||
MyBroadcastReceiver(String name) {
|
||||
Log.d(TAG, "Constructing MyBroadcastReceiver named " + name);
|
||||
mName = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Log.d(TAG, "onReceive() for " + mName + ": " + intent);
|
||||
final String action = intent.getAction();
|
||||
switch (action) {
|
||||
case ACTION_RESTRICT_BACKGROUND_CHANGED:
|
||||
increaseCounter(context, action);
|
||||
break;
|
||||
case ACTION_RECEIVER_READY:
|
||||
final String message = mName + " is ready to rumble";
|
||||
Log.d(TAG, message);
|
||||
setResultData(message);
|
||||
break;
|
||||
case ACTION_SHOW_TOAST:
|
||||
showToast(context);
|
||||
break;
|
||||
default:
|
||||
Log.e(TAG, "received unexpected action: " + action);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[MyBroadcastReceiver: mName=" + mName + "]";
|
||||
}
|
||||
|
||||
private void increaseCounter(Context context, String action) {
|
||||
final SharedPreferences prefs = context.getApplicationContext()
|
||||
.getSharedPreferences(mName, Context.MODE_PRIVATE);
|
||||
final int value = prefs.getInt(action, 0) + 1;
|
||||
Log.d(TAG, "increaseCounter('" + action + "'): setting '" + mName + "' to " + value);
|
||||
prefs.edit().putInt(action, value).apply();
|
||||
}
|
||||
|
||||
static int getCounter(Context context, String action, String receiverName) {
|
||||
final SharedPreferences prefs = context.getSharedPreferences(receiverName,
|
||||
Context.MODE_PRIVATE);
|
||||
final int value = prefs.getInt(action, 0);
|
||||
Log.d(TAG, "getCounter('" + action + "', '" + receiverName + "'): " + value);
|
||||
return value;
|
||||
}
|
||||
|
||||
static String getRestrictBackgroundStatus(Context context) {
|
||||
final ConnectivityManager cm = (ConnectivityManager) context
|
||||
.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
final int apiStatus = cm.getRestrictBackgroundStatus();
|
||||
Log.d(TAG, "getRestrictBackgroundStatus: returning " + apiStatus);
|
||||
return String.valueOf(apiStatus);
|
||||
}
|
||||
|
||||
private static final String NETWORK_STATUS_TEMPLATE = "%s|%s|%s|%s|%s";
|
||||
/**
|
||||
* Checks whether the network is available and return a string which can then be send as a
|
||||
* result data for the ordered broadcast.
|
||||
*
|
||||
* <p>
|
||||
* The string has the following format:
|
||||
*
|
||||
* <p><pre><code>
|
||||
* NetinfoState|NetinfoDetailedState|RealConnectionCheck|RealConnectionCheckDetails|Netinfo
|
||||
* </code></pre>
|
||||
*
|
||||
* <p>Where:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@code NetinfoState}: enum value of {@link NetworkInfo.State}.
|
||||
* <li>{@code NetinfoDetailedState}: enum value of {@link NetworkInfo.DetailedState}.
|
||||
* <li>{@code RealConnectionCheck}: boolean value of a real connection check (i.e., an attempt
|
||||
* to access an external website.
|
||||
* <li>{@code RealConnectionCheckDetails}: if HTTP output core or exception string of the real
|
||||
* connection attempt
|
||||
* <li>{@code Netinfo}: string representation of the {@link NetworkInfo}.
|
||||
* </ul>
|
||||
*
|
||||
* For example, if the connection was established fine, the result would be something like:
|
||||
* <p><pre><code>
|
||||
* CONNECTED|CONNECTED|true|200|[type: WIFI[], state: CONNECTED/CONNECTED, reason: ...]
|
||||
* </code></pre>
|
||||
*
|
||||
*/
|
||||
// TODO: now that it uses Binder, it counl return a Bundle with the data parts instead...
|
||||
static String checkNetworkStatus(Context context) {
|
||||
final ConnectivityManager cm =
|
||||
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
// TODO: connect to a hostside server instead
|
||||
final String address = "http://example.com";
|
||||
final NetworkInfo networkInfo = cm.getActiveNetworkInfo();
|
||||
Log.d(TAG, "Running checkNetworkStatus() on thread "
|
||||
+ Thread.currentThread().getName() + " for UID " + getUid(context)
|
||||
+ "\n\tactiveNetworkInfo: " + networkInfo + "\n\tURL: " + address);
|
||||
boolean checkStatus = false;
|
||||
String checkDetails = "N/A";
|
||||
try {
|
||||
final URL url = new URL(address);
|
||||
final HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setReadTimeout(NETWORK_TIMEOUT_MS);
|
||||
conn.setConnectTimeout(NETWORK_TIMEOUT_MS / 2);
|
||||
conn.setRequestMethod("GET");
|
||||
conn.setDoInput(true);
|
||||
conn.connect();
|
||||
final int response = conn.getResponseCode();
|
||||
checkStatus = true;
|
||||
checkDetails = "HTTP response for " + address + ": " + response;
|
||||
} catch (Exception e) {
|
||||
checkStatus = false;
|
||||
checkDetails = "Exception getting " + address + ": " + e;
|
||||
}
|
||||
Log.d(TAG, checkDetails);
|
||||
final String state, detailedState;
|
||||
if (networkInfo != null) {
|
||||
state = networkInfo.getState().name();
|
||||
detailedState = networkInfo.getDetailedState().name();
|
||||
} else {
|
||||
state = detailedState = "null";
|
||||
}
|
||||
final String status = String.format(NETWORK_STATUS_TEMPLATE, state, detailedState,
|
||||
Boolean.valueOf(checkStatus), checkDetails, networkInfo);
|
||||
Log.d(TAG, "Offering " + status);
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a system notification containing actions with pending intents to launch the app's
|
||||
* main activitiy or service.
|
||||
*/
|
||||
static void sendNotification(Context context, String channelId, int notificationId,
|
||||
String notificationType ) {
|
||||
Log.d(TAG, "sendNotification: id=" + notificationId + ", type=" + notificationType);
|
||||
final Intent serviceIntent = new Intent(context, MyService.class);
|
||||
final PendingIntent pendingIntent = PendingIntent.getService(context, 0, serviceIntent,
|
||||
notificationId);
|
||||
final Bundle bundle = new Bundle();
|
||||
bundle.putCharSequence("parcelable", "I am not");
|
||||
|
||||
final Notification.Builder builder = new Notification.Builder(context, channelId)
|
||||
.setSmallIcon(R.drawable.ic_notification);
|
||||
|
||||
Action action = null;
|
||||
switch (notificationType) {
|
||||
case NOTIFICATION_TYPE_CONTENT:
|
||||
builder
|
||||
.setContentTitle("Light, Cameras...")
|
||||
.setContentIntent(pendingIntent);
|
||||
break;
|
||||
case NOTIFICATION_TYPE_DELETE:
|
||||
builder.setDeleteIntent(pendingIntent);
|
||||
break;
|
||||
case NOTIFICATION_TYPE_FULL_SCREEN:
|
||||
builder.setFullScreenIntent(pendingIntent, true);
|
||||
break;
|
||||
case NOTIFICATION_TYPE_BUNDLE:
|
||||
bundle.putParcelable("Magnum P.I. (Pending Intent)", pendingIntent);
|
||||
builder.setExtras(bundle);
|
||||
break;
|
||||
case NOTIFICATION_TYPE_ACTION:
|
||||
action = new Action.Builder(
|
||||
R.drawable.ic_notification, "ACTION", pendingIntent)
|
||||
.build();
|
||||
builder.addAction(action);
|
||||
break;
|
||||
case NOTIFICATION_TYPE_ACTION_BUNDLE:
|
||||
bundle.putParcelable("Magnum A.P.I. (Action Pending Intent)", pendingIntent);
|
||||
action = new Action.Builder(
|
||||
R.drawable.ic_notification, "ACTION WITH BUNDLE", null)
|
||||
.addExtras(bundle)
|
||||
.build();
|
||||
builder.addAction(action);
|
||||
break;
|
||||
case NOTIFICATION_TYPE_ACTION_REMOTE_INPUT:
|
||||
bundle.putParcelable("Magnum R.I. (Remote Input)", null);
|
||||
final RemoteInput remoteInput = new RemoteInput.Builder("RI")
|
||||
.addExtras(bundle)
|
||||
.build();
|
||||
action = new Action.Builder(
|
||||
R.drawable.ic_notification, "ACTION WITH REMOTE INPUT", pendingIntent)
|
||||
.addRemoteInput(remoteInput)
|
||||
.build();
|
||||
builder.addAction(action);
|
||||
break;
|
||||
default:
|
||||
Log.e(TAG, "Unknown notification type: " + notificationType);
|
||||
return;
|
||||
}
|
||||
|
||||
final Notification notification = builder.build();
|
||||
((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE))
|
||||
.notify(notificationId, notification);
|
||||
}
|
||||
|
||||
private void showToast(Context context) {
|
||||
Toast.makeText(context, "Toast from CTS test", Toast.LENGTH_SHORT).show();
|
||||
setResultData("Shown");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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.cts.net.hostside.app2;
|
||||
|
||||
import static com.android.cts.net.hostside.app2.Common.TAG;
|
||||
import static com.android.cts.net.hostside.app2.Common.TEST_PKG;
|
||||
|
||||
import android.R;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.cts.net.hostside.INetworkStateObserver;
|
||||
|
||||
/**
|
||||
* Service used to change app state to FOREGROUND_SERVICE.
|
||||
*/
|
||||
public class MyForegroundService extends Service {
|
||||
private static final String NOTIFICATION_CHANNEL_ID = "cts/MyForegroundService";
|
||||
private static final int FLAG_START_FOREGROUND = 1;
|
||||
private static final int FLAG_STOP_FOREGROUND = 2;
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
Log.v(TAG, "MyForegroundService.onStartCommand(): " + intent);
|
||||
NotificationManager notificationManager = getSystemService(NotificationManager.class);
|
||||
notificationManager.createNotificationChannel(new NotificationChannel(
|
||||
NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_ID,
|
||||
NotificationManager.IMPORTANCE_DEFAULT));
|
||||
switch (intent.getFlags()) {
|
||||
case FLAG_START_FOREGROUND:
|
||||
Log.d(TAG, "Starting foreground");
|
||||
startForeground(42, new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.ic_dialog_alert) // any icon is fine
|
||||
.build());
|
||||
Common.notifyNetworkStateObserver(this, intent);
|
||||
break;
|
||||
case FLAG_STOP_FOREGROUND:
|
||||
Log.d(TAG, "Stopping foreground");
|
||||
stopForeground(true);
|
||||
break;
|
||||
default:
|
||||
Log.wtf(TAG, "Invalid flag on intent " + intent);
|
||||
}
|
||||
return START_STICKY;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
* 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.cts.net.hostside.app2;
|
||||
|
||||
import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
|
||||
|
||||
import static com.android.cts.net.hostside.app2.Common.ACTION_RECEIVER_READY;
|
||||
import static com.android.cts.net.hostside.app2.Common.DYNAMIC_RECEIVER;
|
||||
import static com.android.cts.net.hostside.app2.Common.TAG;
|
||||
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.Network;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.NetworkRequest;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.cts.net.hostside.IMyService;
|
||||
import com.android.cts.net.hostside.INetworkCallback;
|
||||
|
||||
/**
|
||||
* Service used to dynamically register a broadcast receiver.
|
||||
*/
|
||||
public class MyService extends Service {
|
||||
private static final String NOTIFICATION_CHANNEL_ID = "MyService";
|
||||
|
||||
ConnectivityManager mCm;
|
||||
|
||||
private MyBroadcastReceiver mReceiver;
|
||||
private ConnectivityManager.NetworkCallback mNetworkCallback;
|
||||
|
||||
// TODO: move MyBroadcast static functions here - they were kept there to make git diff easier.
|
||||
|
||||
private IMyService.Stub mBinder =
|
||||
new IMyService.Stub() {
|
||||
|
||||
@Override
|
||||
public void registerBroadcastReceiver() {
|
||||
if (mReceiver != null) {
|
||||
Log.d(TAG, "receiver already registered: " + mReceiver);
|
||||
return;
|
||||
}
|
||||
final Context context = getApplicationContext();
|
||||
mReceiver = new MyBroadcastReceiver(DYNAMIC_RECEIVER);
|
||||
context.registerReceiver(mReceiver, new IntentFilter(ACTION_RECEIVER_READY));
|
||||
context.registerReceiver(mReceiver,
|
||||
new IntentFilter(ACTION_RESTRICT_BACKGROUND_CHANGED));
|
||||
Log.d(TAG, "receiver registered");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCounters(String receiverName, String action) {
|
||||
return MyBroadcastReceiver.getCounter(getApplicationContext(), action, receiverName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String checkNetworkStatus() {
|
||||
return MyBroadcastReceiver.checkNetworkStatus(getApplicationContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRestrictBackgroundStatus() {
|
||||
return MyBroadcastReceiver.getRestrictBackgroundStatus(getApplicationContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendNotification(int notificationId, String notificationType) {
|
||||
MyBroadcastReceiver .sendNotification(getApplicationContext(), NOTIFICATION_CHANNEL_ID,
|
||||
notificationId, notificationType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerNetworkCallback(INetworkCallback cb) {
|
||||
if (mNetworkCallback != null) {
|
||||
Log.d(TAG, "unregister previous network callback: " + mNetworkCallback);
|
||||
unregisterNetworkCallback();
|
||||
}
|
||||
Log.d(TAG, "registering network callback");
|
||||
|
||||
mNetworkCallback = new ConnectivityManager.NetworkCallback() {
|
||||
@Override
|
||||
public void onBlockedStatusChanged(Network network, boolean blocked) {
|
||||
try {
|
||||
cb.onBlockedStatusChanged(network, blocked);
|
||||
} catch (RemoteException e) {
|
||||
Log.d(TAG, "Cannot send onBlockedStatusChanged: " + e);
|
||||
unregisterNetworkCallback();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAvailable(Network network) {
|
||||
try {
|
||||
cb.onAvailable(network);
|
||||
} catch (RemoteException e) {
|
||||
Log.d(TAG, "Cannot send onAvailable: " + e);
|
||||
unregisterNetworkCallback();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLost(Network network) {
|
||||
try {
|
||||
cb.onLost(network);
|
||||
} catch (RemoteException e) {
|
||||
Log.d(TAG, "Cannot send onLost: " + e);
|
||||
unregisterNetworkCallback();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCapabilitiesChanged(Network network, NetworkCapabilities cap) {
|
||||
try {
|
||||
cb.onCapabilitiesChanged(network, cap);
|
||||
} catch (RemoteException e) {
|
||||
Log.d(TAG, "Cannot send onCapabilitiesChanged: " + e);
|
||||
unregisterNetworkCallback();
|
||||
}
|
||||
}
|
||||
};
|
||||
mCm.registerNetworkCallback(makeWifiNetworkRequest(), mNetworkCallback);
|
||||
try {
|
||||
cb.asBinder().linkToDeath(() -> unregisterNetworkCallback(), 0);
|
||||
} catch (RemoteException e) {
|
||||
unregisterNetworkCallback();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterNetworkCallback() {
|
||||
Log.d(TAG, "unregistering network callback");
|
||||
if (mNetworkCallback != null) {
|
||||
mCm.unregisterNetworkCallback(mNetworkCallback);
|
||||
mNetworkCallback = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private NetworkRequest makeWifiNetworkRequest() {
|
||||
return new NetworkRequest.Builder()
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return mBinder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
final Context context = getApplicationContext();
|
||||
((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE))
|
||||
.createNotificationChannel(new NotificationChannel(NOTIFICATION_CHANNEL_ID,
|
||||
NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_DEFAULT));
|
||||
mCm = (ConnectivityManager) getApplicationContext()
|
||||
.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
final Context context = getApplicationContext();
|
||||
((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE))
|
||||
.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
|
||||
if (mReceiver != null) {
|
||||
Log.d(TAG, "onDestroy(): unregistering " + mReceiver);
|
||||
getApplicationContext().unregisterReceiver(mReceiver);
|
||||
}
|
||||
|
||||
super.onDestroy();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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.cts.net.hostside.app2;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.Process;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.cts.net.hostside.IRemoteSocketFactory;
|
||||
|
||||
import java.net.Socket;
|
||||
|
||||
|
||||
public class RemoteSocketFactoryService extends Service {
|
||||
|
||||
private static final String TAG = RemoteSocketFactoryService.class.getSimpleName();
|
||||
|
||||
private IRemoteSocketFactory.Stub mBinder = new IRemoteSocketFactory.Stub() {
|
||||
@Override
|
||||
public ParcelFileDescriptor openSocketFd(String host, int port, int timeoutMs) {
|
||||
try {
|
||||
Socket s = new Socket(host, port);
|
||||
s.setSoTimeout(timeoutMs);
|
||||
return ParcelFileDescriptor.fromSocket(s);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPackageName() {
|
||||
return RemoteSocketFactoryService.this.getPackageName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getUid() {
|
||||
return Process.myUid();
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return mBinder;
|
||||
}
|
||||
}
|
||||
4
tests/cts/hostside/certs/Android.bp
Normal file
4
tests/cts/hostside/certs/Android.bp
Normal file
@@ -0,0 +1,4 @@
|
||||
android_app_certificate {
|
||||
name: "cts-net-app",
|
||||
certificate: "cts-net-app",
|
||||
}
|
||||
2
tests/cts/hostside/certs/README
Normal file
2
tests/cts/hostside/certs/README
Normal file
@@ -0,0 +1,2 @@
|
||||
# Generated with:
|
||||
development/tools/make_key cts-net-app '/CN=cts-net-app'
|
||||
BIN
tests/cts/hostside/certs/cts-net-app.pk8
Normal file
BIN
tests/cts/hostside/certs/cts-net-app.pk8
Normal file
Binary file not shown.
19
tests/cts/hostside/certs/cts-net-app.x509.pem
Normal file
19
tests/cts/hostside/certs/cts-net-app.x509.pem
Normal file
@@ -0,0 +1,19 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDAjCCAeqgAwIBAgIJAMhWwIIqr1r6MA0GCSqGSIb3DQEBCwUAMBYxFDASBgNV
|
||||
BAMMC2N0cy1uZXQtYXBwMB4XDTE4MDYyMDAyMjAwN1oXDTQ1MTEwNTAyMjAwN1ow
|
||||
FjEUMBIGA1UEAwwLY3RzLW5ldC1hcHAwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
|
||||
ggEKAoIBAQDefOayWQss1E+FQIONK6IhlXhe0BEyHshIrnPOOmuCPa/Svfbnmziy
|
||||
hr1KTjaQ3ET/mGShwlt6AUti7nKx9aB71IJp5mSBuwW62A8jvN3yNOo45YV8+n1o
|
||||
TrEoMWMf7hQmoOSqaSJ+VFuVms/kPSEh99okDgHCej6rsEkEcDoh6pJajQyUYDwR
|
||||
SNAF8SrqCDhqFbZW/LWedvuikCUlNtzuv7/GrcLcsiWEfHv7UOBKpMjLo9BhD1XF
|
||||
IefnxImcBQrQGMnE9TLixBiEeX5yauLgbZuxBqD/zsI2TH1FjxTeuJan83kLbqqH
|
||||
FgyvPaUjwckAdQPyom7ZUYFnBc0LQ9xzAgMBAAGjUzBRMB0GA1UdDgQWBBRZrBEw
|
||||
tAB2WNXj8dQ7ZOuJ34kY5DAfBgNVHSMEGDAWgBRZrBEwtAB2WNXj8dQ7ZOuJ34kY
|
||||
5DAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQDeI9AnLW6l/39y
|
||||
z96w/ldxZVFPzBRiFIsJsPHVyXlD5vUHZv/ju2jFn8TZSZR5TK0bzCEoVLp34Sho
|
||||
bbS0magP82yIvCRibyoyD+TDNnZkNJwjYnikE+/oyshTSQtpkn/rDA+0Y09BUC1E
|
||||
N2I6bV9pTXLFg7oah2FmqPRPzhgeYUKENgOQkrrjUCn6y0i/k374n7aftzdniSIz
|
||||
2kCRVEeN9gws6CnoMPx0vr32v/JVuPV6zfdJYadgj/eFRyTNE4msd9kE82Wc46eU
|
||||
YiI+LuXZ3ZMUNWGY7MK2pOUUS52JsBQ3K235dA5WaU4x8OBlY/WkNYX/eLbNs5jj
|
||||
FzLmhZZ1
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.cts.net;
|
||||
public class HostsideNetworkCallbackTests extends HostsideNetworkTestCase {
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
uninstallPackage(TEST_APP2_PKG, false);
|
||||
installPackage(TEST_APP2_APK);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tearDown() throws Exception {
|
||||
super.tearDown();
|
||||
uninstallPackage(TEST_APP2_PKG, true);
|
||||
}
|
||||
|
||||
public void testOnBlockedStatusChanged_dataSaver() throws Exception {
|
||||
runDeviceTests(TEST_PKG,
|
||||
TEST_PKG + ".NetworkCallbackTest", "testOnBlockedStatusChanged_dataSaver");
|
||||
}
|
||||
|
||||
public void testOnBlockedStatusChanged_powerSaver() throws Exception {
|
||||
runDeviceTests(TEST_PKG,
|
||||
TEST_PKG + ".NetworkCallbackTest", "testOnBlockedStatusChanged_powerSaver");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* 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.cts.net;
|
||||
|
||||
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
|
||||
import com.android.ddmlib.Log;
|
||||
import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
|
||||
import com.android.ddmlib.testrunner.TestResult.TestStatus;
|
||||
import com.android.tradefed.build.IBuildInfo;
|
||||
import com.android.tradefed.device.DeviceNotAvailableException;
|
||||
import com.android.tradefed.result.CollectingTestListener;
|
||||
import com.android.tradefed.result.TestDescription;
|
||||
import com.android.tradefed.result.TestResult;
|
||||
import com.android.tradefed.result.TestRunResult;
|
||||
import com.android.tradefed.testtype.DeviceTestCase;
|
||||
import com.android.tradefed.testtype.IAbi;
|
||||
import com.android.tradefed.testtype.IAbiReceiver;
|
||||
import com.android.tradefed.testtype.IBuildReceiver;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
abstract class HostsideNetworkTestCase extends DeviceTestCase implements IAbiReceiver,
|
||||
IBuildReceiver {
|
||||
protected static final boolean DEBUG = false;
|
||||
protected static final String TAG = "HostsideNetworkTests";
|
||||
protected static final String TEST_PKG = "com.android.cts.net.hostside";
|
||||
protected static final String TEST_APK = "CtsHostsideNetworkTestsApp.apk";
|
||||
protected static final String TEST_APP2_PKG = "com.android.cts.net.hostside.app2";
|
||||
protected static final String TEST_APP2_APK = "CtsHostsideNetworkTestsApp2.apk";
|
||||
|
||||
private IAbi mAbi;
|
||||
private IBuildInfo mCtsBuild;
|
||||
|
||||
@Override
|
||||
public void setAbi(IAbi abi) {
|
||||
mAbi = abi;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBuild(IBuildInfo buildInfo) {
|
||||
mCtsBuild = buildInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
assertNotNull(mAbi);
|
||||
assertNotNull(mCtsBuild);
|
||||
|
||||
uninstallPackage(TEST_PKG, false);
|
||||
installPackage(TEST_APK);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tearDown() throws Exception {
|
||||
super.tearDown();
|
||||
|
||||
uninstallPackage(TEST_PKG, true);
|
||||
}
|
||||
|
||||
protected void installPackage(String apk) throws FileNotFoundException,
|
||||
DeviceNotAvailableException {
|
||||
CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
|
||||
assertNull(getDevice().installPackage(buildHelper.getTestFile(apk),
|
||||
false /* reinstall */, true /* grantPermissions */));
|
||||
}
|
||||
|
||||
protected void uninstallPackage(String packageName, boolean shouldSucceed)
|
||||
throws DeviceNotAvailableException {
|
||||
final String result = getDevice().uninstallPackage(packageName);
|
||||
if (shouldSucceed) {
|
||||
assertNull("uninstallPackage(" + packageName + ") failed: " + result, result);
|
||||
}
|
||||
}
|
||||
|
||||
protected void assertPackageUninstalled(String packageName) throws DeviceNotAvailableException,
|
||||
InterruptedException {
|
||||
final String command = "cmd package list packages " + packageName;
|
||||
final int max_tries = 5;
|
||||
for (int i = 1; i <= max_tries; i++) {
|
||||
final String result = runCommand(command);
|
||||
if (result.trim().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
// 'list packages' filters by substring, so we need to iterate with the results
|
||||
// and check one by one, otherwise 'com.android.cts.net.hostside' could return
|
||||
// 'com.android.cts.net.hostside.app2'
|
||||
boolean found = false;
|
||||
for (String line : result.split("[\\r\\n]+")) {
|
||||
if (line.endsWith(packageName)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
return;
|
||||
}
|
||||
i++;
|
||||
Log.v(TAG, "Package " + packageName + " not uninstalled yet (" + result
|
||||
+ "); sleeping 1s before polling again");
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
fail("Package '" + packageName + "' not uinstalled after " + max_tries + " seconds");
|
||||
}
|
||||
|
||||
protected void runDeviceTests(String packageName, String testClassName)
|
||||
throws DeviceNotAvailableException {
|
||||
runDeviceTests(packageName, testClassName, null);
|
||||
}
|
||||
|
||||
protected void runDeviceTests(String packageName, String testClassName, String methodName)
|
||||
throws DeviceNotAvailableException {
|
||||
RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(packageName,
|
||||
"androidx.test.runner.AndroidJUnitRunner", getDevice().getIDevice());
|
||||
|
||||
if (testClassName != null) {
|
||||
if (methodName != null) {
|
||||
testRunner.setMethodName(testClassName, methodName);
|
||||
} else {
|
||||
testRunner.setClassName(testClassName);
|
||||
}
|
||||
}
|
||||
|
||||
final CollectingTestListener listener = new CollectingTestListener();
|
||||
getDevice().runInstrumentationTests(testRunner, listener);
|
||||
|
||||
final TestRunResult result = listener.getCurrentRunResults();
|
||||
if (result.isRunFailure()) {
|
||||
throw new AssertionError("Failed to successfully run device tests for "
|
||||
+ result.getName() + ": " + result.getRunFailureMessage());
|
||||
}
|
||||
|
||||
if (result.hasFailedTests()) {
|
||||
// build a meaningful error message
|
||||
StringBuilder errorBuilder = new StringBuilder("on-device tests failed:\n");
|
||||
for (Map.Entry<TestDescription, TestResult> resultEntry :
|
||||
result.getTestResults().entrySet()) {
|
||||
if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
|
||||
errorBuilder.append(resultEntry.getKey().toString());
|
||||
errorBuilder.append(":\n");
|
||||
errorBuilder.append(resultEntry.getValue().getStackTrace());
|
||||
}
|
||||
}
|
||||
throw new AssertionError(errorBuilder.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private static final Pattern UID_PATTERN =
|
||||
Pattern.compile(".*userId=([0-9]+)$", Pattern.MULTILINE);
|
||||
|
||||
protected int getUid(String packageName) throws DeviceNotAvailableException {
|
||||
final String output = runCommand("dumpsys package " + packageName);
|
||||
final Matcher matcher = UID_PATTERN.matcher(output);
|
||||
while (matcher.find()) {
|
||||
final String match = matcher.group(1);
|
||||
return Integer.parseInt(match);
|
||||
}
|
||||
throw new RuntimeException("Did not find regexp '" + UID_PATTERN + "' on adb output\n"
|
||||
+ output);
|
||||
}
|
||||
|
||||
protected String runCommand(String command) throws DeviceNotAvailableException {
|
||||
Log.d(TAG, "Command: '" + command + "'");
|
||||
final String output = getDevice().executeShellCommand(command);
|
||||
if (DEBUG) Log.v(TAG, "Output: " + output.trim());
|
||||
return output;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,377 @@
|
||||
/*
|
||||
* 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.cts.net;
|
||||
|
||||
import com.android.ddmlib.Log;
|
||||
import com.android.tradefed.device.DeviceNotAvailableException;
|
||||
|
||||
public class HostsideRestrictBackgroundNetworkTests extends HostsideNetworkTestCase {
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
uninstallPackage(TEST_APP2_PKG, false);
|
||||
installPackage(TEST_APP2_APK);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tearDown() throws Exception {
|
||||
super.tearDown();
|
||||
|
||||
uninstallPackage(TEST_APP2_PKG, true);
|
||||
}
|
||||
|
||||
/**************************
|
||||
* Data Saver Mode tests. *
|
||||
**************************/
|
||||
|
||||
public void testDataSaverMode_disabled() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".DataSaverModeTest",
|
||||
"testGetRestrictBackgroundStatus_disabled");
|
||||
}
|
||||
|
||||
public void testDataSaverMode_whitelisted() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".DataSaverModeTest",
|
||||
"testGetRestrictBackgroundStatus_whitelisted");
|
||||
}
|
||||
|
||||
public void testDataSaverMode_enabled() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".DataSaverModeTest",
|
||||
"testGetRestrictBackgroundStatus_enabled");
|
||||
}
|
||||
|
||||
public void testDataSaverMode_blacklisted() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".DataSaverModeTest",
|
||||
"testGetRestrictBackgroundStatus_blacklisted");
|
||||
}
|
||||
|
||||
public void testDataSaverMode_reinstall() throws Exception {
|
||||
final int oldUid = getUid(TEST_APP2_PKG);
|
||||
|
||||
// Make sure whitelist is revoked when package is removed
|
||||
addRestrictBackgroundWhitelist(oldUid);
|
||||
|
||||
uninstallPackage(TEST_APP2_PKG, true);
|
||||
assertPackageUninstalled(TEST_APP2_PKG);
|
||||
assertRestrictBackgroundWhitelist(oldUid, false);
|
||||
|
||||
installPackage(TEST_APP2_APK);
|
||||
final int newUid = getUid(TEST_APP2_PKG);
|
||||
assertRestrictBackgroundWhitelist(oldUid, false);
|
||||
assertRestrictBackgroundWhitelist(newUid, false);
|
||||
}
|
||||
|
||||
public void testDataSaverMode_requiredWhitelistedPackages() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".DataSaverModeTest",
|
||||
"testGetRestrictBackgroundStatus_requiredWhitelistedPackages");
|
||||
}
|
||||
|
||||
public void testDataSaverMode_broadcastNotSentOnUnsupportedDevices() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".DataSaverModeTest",
|
||||
"testBroadcastNotSentOnUnsupportedDevices");
|
||||
}
|
||||
|
||||
/*****************************
|
||||
* Battery Saver Mode tests. *
|
||||
*****************************/
|
||||
|
||||
public void testBatterySaverModeMetered_disabled() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".BatterySaverModeMeteredTest",
|
||||
"testBackgroundNetworkAccess_disabled");
|
||||
}
|
||||
|
||||
public void testBatterySaverModeMetered_whitelisted() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".BatterySaverModeMeteredTest",
|
||||
"testBackgroundNetworkAccess_whitelisted");
|
||||
}
|
||||
|
||||
public void testBatterySaverModeMetered_enabled() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".BatterySaverModeMeteredTest",
|
||||
"testBackgroundNetworkAccess_enabled");
|
||||
}
|
||||
|
||||
public void testBatterySaverMode_reinstall() throws Exception {
|
||||
if (!isDozeModeEnabled()) {
|
||||
Log.w(TAG, "testBatterySaverMode_reinstall() skipped because device does not support "
|
||||
+ "Doze Mode");
|
||||
return;
|
||||
}
|
||||
|
||||
addPowerSaveModeWhitelist(TEST_APP2_PKG);
|
||||
|
||||
uninstallPackage(TEST_APP2_PKG, true);
|
||||
assertPackageUninstalled(TEST_APP2_PKG);
|
||||
assertPowerSaveModeWhitelist(TEST_APP2_PKG, false);
|
||||
|
||||
installPackage(TEST_APP2_APK);
|
||||
assertPowerSaveModeWhitelist(TEST_APP2_PKG, false);
|
||||
}
|
||||
|
||||
public void testBatterySaverModeNonMetered_disabled() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".BatterySaverModeNonMeteredTest",
|
||||
"testBackgroundNetworkAccess_disabled");
|
||||
}
|
||||
|
||||
public void testBatterySaverModeNonMetered_whitelisted() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".BatterySaverModeNonMeteredTest",
|
||||
"testBackgroundNetworkAccess_whitelisted");
|
||||
}
|
||||
|
||||
public void testBatterySaverModeNonMetered_enabled() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".BatterySaverModeNonMeteredTest",
|
||||
"testBackgroundNetworkAccess_enabled");
|
||||
}
|
||||
|
||||
/*******************
|
||||
* App idle tests. *
|
||||
*******************/
|
||||
|
||||
public void testAppIdleMetered_disabled() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
|
||||
"testBackgroundNetworkAccess_disabled");
|
||||
}
|
||||
|
||||
public void testAppIdleMetered_whitelisted() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
|
||||
"testBackgroundNetworkAccess_whitelisted");
|
||||
}
|
||||
|
||||
public void testAppIdleMetered_tempWhitelisted() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
|
||||
"testBackgroundNetworkAccess_tempWhitelisted");
|
||||
}
|
||||
|
||||
public void testAppIdleMetered_enabled() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
|
||||
"testBackgroundNetworkAccess_enabled");
|
||||
}
|
||||
|
||||
public void testAppIdleMetered_idleWhitelisted() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
|
||||
"testAppIdleNetworkAccess_idleWhitelisted");
|
||||
}
|
||||
|
||||
// TODO: currently power-save mode and idle uses the same whitelist, so this test would be
|
||||
// redundant (as it would be testing the same as testBatterySaverMode_reinstall())
|
||||
// public void testAppIdle_reinstall() throws Exception {
|
||||
// }
|
||||
|
||||
public void testAppIdleNonMetered_disabled() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
|
||||
"testBackgroundNetworkAccess_disabled");
|
||||
}
|
||||
|
||||
public void testAppIdleNonMetered_whitelisted() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
|
||||
"testBackgroundNetworkAccess_whitelisted");
|
||||
}
|
||||
|
||||
public void testAppIdleNonMetered_tempWhitelisted() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
|
||||
"testBackgroundNetworkAccess_tempWhitelisted");
|
||||
}
|
||||
|
||||
public void testAppIdleNonMetered_enabled() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
|
||||
"testBackgroundNetworkAccess_enabled");
|
||||
}
|
||||
|
||||
public void testAppIdleNonMetered_idleWhitelisted() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
|
||||
"testAppIdleNetworkAccess_idleWhitelisted");
|
||||
}
|
||||
|
||||
public void testAppIdleNonMetered_whenCharging() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
|
||||
"testAppIdleNetworkAccess_whenCharging");
|
||||
}
|
||||
|
||||
public void testAppIdleMetered_whenCharging() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
|
||||
"testAppIdleNetworkAccess_whenCharging");
|
||||
}
|
||||
|
||||
public void testAppIdle_toast() throws Exception {
|
||||
// Check that showing a toast doesn't bring an app out of standby
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
|
||||
"testAppIdle_toast");
|
||||
}
|
||||
|
||||
/********************
|
||||
* Doze Mode tests. *
|
||||
********************/
|
||||
|
||||
public void testDozeModeMetered_disabled() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".DozeModeMeteredTest",
|
||||
"testBackgroundNetworkAccess_disabled");
|
||||
}
|
||||
|
||||
public void testDozeModeMetered_whitelisted() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".DozeModeMeteredTest",
|
||||
"testBackgroundNetworkAccess_whitelisted");
|
||||
}
|
||||
|
||||
public void testDozeModeMetered_enabled() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".DozeModeMeteredTest",
|
||||
"testBackgroundNetworkAccess_enabled");
|
||||
}
|
||||
|
||||
public void testDozeModeMetered_enabledButWhitelistedOnNotificationAction() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".DozeModeMeteredTest",
|
||||
"testBackgroundNetworkAccess_enabledButWhitelistedOnNotificationAction");
|
||||
}
|
||||
|
||||
// TODO: currently power-save mode and idle uses the same whitelist, so this test would be
|
||||
// redundant (as it would be testing the same as testBatterySaverMode_reinstall())
|
||||
// public void testDozeMode_reinstall() throws Exception {
|
||||
// }
|
||||
|
||||
public void testDozeModeNonMetered_disabled() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".DozeModeNonMeteredTest",
|
||||
"testBackgroundNetworkAccess_disabled");
|
||||
}
|
||||
|
||||
public void testDozeModeNonMetered_whitelisted() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".DozeModeNonMeteredTest",
|
||||
"testBackgroundNetworkAccess_whitelisted");
|
||||
}
|
||||
|
||||
public void testDozeModeNonMetered_enabled() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".DozeModeNonMeteredTest",
|
||||
"testBackgroundNetworkAccess_enabled");
|
||||
}
|
||||
|
||||
public void testDozeModeNonMetered_enabledButWhitelistedOnNotificationAction()
|
||||
throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".DozeModeNonMeteredTest",
|
||||
"testBackgroundNetworkAccess_enabledButWhitelistedOnNotificationAction");
|
||||
}
|
||||
|
||||
/**********************
|
||||
* Mixed modes tests. *
|
||||
**********************/
|
||||
|
||||
public void testDataAndBatterySaverModes_meteredNetwork() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".MixedModesTest",
|
||||
"testDataAndBatterySaverModes_meteredNetwork");
|
||||
}
|
||||
|
||||
public void testDataAndBatterySaverModes_nonMeteredNetwork() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".MixedModesTest",
|
||||
"testDataAndBatterySaverModes_nonMeteredNetwork");
|
||||
}
|
||||
|
||||
public void testDozeAndBatterySaverMode_powerSaveWhitelists() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".MixedModesTest",
|
||||
"testDozeAndBatterySaverMode_powerSaveWhitelists");
|
||||
}
|
||||
|
||||
public void testDozeAndAppIdle_powerSaveWhitelists() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".MixedModesTest",
|
||||
"testDozeAndAppIdle_powerSaveWhitelists");
|
||||
}
|
||||
|
||||
public void testAppIdleAndDoze_tempPowerSaveWhitelists() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".MixedModesTest",
|
||||
"testAppIdleAndDoze_tempPowerSaveWhitelists");
|
||||
}
|
||||
|
||||
public void testAppIdleAndBatterySaver_tempPowerSaveWhitelists() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".MixedModesTest",
|
||||
"testAppIdleAndBatterySaver_tempPowerSaveWhitelists");
|
||||
}
|
||||
|
||||
public void testDozeAndAppIdle_appIdleWhitelist() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".MixedModesTest",
|
||||
"testDozeAndAppIdle_appIdleWhitelist");
|
||||
}
|
||||
|
||||
public void testAppIdleAndDoze_tempPowerSaveAndAppIdleWhitelists() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".MixedModesTest",
|
||||
"testAppIdleAndDoze_tempPowerSaveAndAppIdleWhitelists");
|
||||
}
|
||||
|
||||
public void testAppIdleAndBatterySaver_tempPowerSaveAndAppIdleWhitelists() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".MixedModesTest",
|
||||
"testAppIdleAndBatterySaver_tempPowerSaveAndAppIdleWhitelists");
|
||||
}
|
||||
|
||||
/*******************
|
||||
* Helper methods. *
|
||||
*******************/
|
||||
|
||||
private void assertRestrictBackgroundWhitelist(int uid, boolean expected) throws Exception {
|
||||
final int max_tries = 5;
|
||||
boolean actual = false;
|
||||
for (int i = 1; i <= max_tries; i++) {
|
||||
final String output = runCommand("cmd netpolicy list restrict-background-whitelist ");
|
||||
actual = output.contains(Integer.toString(uid));
|
||||
if (expected == actual) {
|
||||
return;
|
||||
}
|
||||
Log.v(TAG, "whitelist check for uid " + uid + " doesn't match yet (expected "
|
||||
+ expected + ", got " + actual + "); sleeping 1s before polling again");
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
fail("whitelist check for uid " + uid + " failed: expected "
|
||||
+ expected + ", got " + actual);
|
||||
}
|
||||
|
||||
private void assertPowerSaveModeWhitelist(String packageName, boolean expected)
|
||||
throws Exception {
|
||||
// TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
|
||||
// need to use netpolicy for whitelisting
|
||||
assertDelayedCommand("dumpsys deviceidle whitelist =" + packageName,
|
||||
Boolean.toString(expected));
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts the result of a command, wait and re-running it a couple times if necessary.
|
||||
*/
|
||||
private void assertDelayedCommand(String command, String expectedResult)
|
||||
throws InterruptedException, DeviceNotAvailableException {
|
||||
final int maxTries = 5;
|
||||
for (int i = 1; i <= maxTries; i++) {
|
||||
final String result = runCommand(command).trim();
|
||||
if (result.equals(expectedResult)) return;
|
||||
Log.v(TAG, "Command '" + command + "' returned '" + result + " instead of '"
|
||||
+ expectedResult + "' on attempt #; sleeping 1s before polling again");
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
fail("Command '" + command + "' did not return '" + expectedResult + "' after " + maxTries
|
||||
+ " attempts");
|
||||
}
|
||||
|
||||
protected void addRestrictBackgroundWhitelist(int uid) throws Exception {
|
||||
runCommand("cmd netpolicy add restrict-background-whitelist " + uid);
|
||||
assertRestrictBackgroundWhitelist(uid, true);
|
||||
}
|
||||
|
||||
private void addPowerSaveModeWhitelist(String packageName) throws Exception {
|
||||
Log.i(TAG, "Adding package " + packageName + " to power-save-mode whitelist");
|
||||
// TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
|
||||
// need to use netpolicy for whitelisting
|
||||
runCommand("dumpsys deviceidle whitelist +" + packageName);
|
||||
assertPowerSaveModeWhitelist(packageName, true);
|
||||
}
|
||||
|
||||
protected boolean isDozeModeEnabled() throws Exception {
|
||||
final String result = runCommand("cmd deviceidle enabled deep").trim();
|
||||
return result.equals("1");
|
||||
}
|
||||
}
|
||||
103
tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java
Normal file
103
tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java
Normal file
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* 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.cts.net;
|
||||
|
||||
public class HostsideVpnTests extends HostsideNetworkTestCase {
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
uninstallPackage(TEST_APP2_PKG, false);
|
||||
installPackage(TEST_APP2_APK);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tearDown() throws Exception {
|
||||
super.tearDown();
|
||||
|
||||
uninstallPackage(TEST_APP2_PKG, true);
|
||||
}
|
||||
|
||||
public void testDefault() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testDefault");
|
||||
}
|
||||
|
||||
public void testAppAllowed() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testAppAllowed");
|
||||
}
|
||||
|
||||
public void testAppDisallowed() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testAppDisallowed");
|
||||
}
|
||||
|
||||
public void testGetConnectionOwnerUidSecurity() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testGetConnectionOwnerUidSecurity");
|
||||
}
|
||||
|
||||
public void testSetProxy() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testSetProxy");
|
||||
}
|
||||
|
||||
public void testSetProxyDisallowedApps() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testSetProxyDisallowedApps");
|
||||
}
|
||||
|
||||
public void testNoProxy() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testNoProxy");
|
||||
}
|
||||
|
||||
public void testBindToNetworkWithProxy() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testBindToNetworkWithProxy");
|
||||
}
|
||||
|
||||
public void testVpnMeterednessWithNoUnderlyingNetwork() throws Exception {
|
||||
runDeviceTests(
|
||||
TEST_PKG, TEST_PKG + ".VpnTest", "testVpnMeterednessWithNoUnderlyingNetwork");
|
||||
}
|
||||
|
||||
public void testVpnMeterednessWithNullUnderlyingNetwork() throws Exception {
|
||||
runDeviceTests(
|
||||
TEST_PKG, TEST_PKG + ".VpnTest", "testVpnMeterednessWithNullUnderlyingNetwork");
|
||||
}
|
||||
|
||||
public void testVpnMeterednessWithNonNullUnderlyingNetwork() throws Exception {
|
||||
runDeviceTests(
|
||||
TEST_PKG, TEST_PKG + ".VpnTest", "testVpnMeterednessWithNonNullUnderlyingNetwork");
|
||||
}
|
||||
|
||||
public void testAlwaysMeteredVpnWithNullUnderlyingNetwork() throws Exception {
|
||||
runDeviceTests(
|
||||
TEST_PKG, TEST_PKG + ".VpnTest", "testAlwaysMeteredVpnWithNullUnderlyingNetwork");
|
||||
}
|
||||
|
||||
public void testAlwaysMeteredVpnWithNonNullUnderlyingNetwork() throws Exception {
|
||||
runDeviceTests(
|
||||
TEST_PKG,
|
||||
TEST_PKG + ".VpnTest",
|
||||
"testAlwaysMeteredVpnWithNonNullUnderlyingNetwork");
|
||||
}
|
||||
|
||||
public void testB141603906() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testB141603906");
|
||||
}
|
||||
|
||||
public void testDownloadWithDownloadManagerDisallowed() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest",
|
||||
"testDownloadWithDownloadManagerDisallowed");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.cts.net;
|
||||
|
||||
import com.android.tradefed.device.DeviceNotAvailableException;
|
||||
import com.android.tradefed.device.ITestDevice;
|
||||
import com.android.tradefed.invoker.TestInformation;
|
||||
import com.android.tradefed.log.LogUtil;
|
||||
import com.android.tradefed.targetprep.ITargetPreparer;
|
||||
|
||||
public class NetworkPolicyTestsPreparer implements ITargetPreparer {
|
||||
private ITestDevice mDevice;
|
||||
private boolean mOriginalAirplaneModeEnabled;
|
||||
private String mOriginalAppStandbyEnabled;
|
||||
private String mOriginalBatteryStatsConstants;
|
||||
private final static String KEY_STABLE_CHARGING_DELAY_MS = "battery_charged_delay_ms";
|
||||
private final static int DESIRED_STABLE_CHARGING_DELAY_MS = 0;
|
||||
|
||||
@Override
|
||||
public void setUp(TestInformation testInformation) throws DeviceNotAvailableException {
|
||||
mDevice = testInformation.getDevice();
|
||||
mOriginalAppStandbyEnabled = getAppStandbyEnabled();
|
||||
setAppStandbyEnabled("1");
|
||||
LogUtil.CLog.d("Original app_standby_enabled: " + mOriginalAppStandbyEnabled);
|
||||
|
||||
mOriginalBatteryStatsConstants = getBatteryStatsConstants();
|
||||
setBatteryStatsConstants(
|
||||
KEY_STABLE_CHARGING_DELAY_MS + "=" + DESIRED_STABLE_CHARGING_DELAY_MS);
|
||||
LogUtil.CLog.d("Original battery_saver_constants: " + mOriginalBatteryStatsConstants);
|
||||
|
||||
mOriginalAirplaneModeEnabled = getAirplaneModeEnabled();
|
||||
// Turn off airplane mode in case another test left the device in that state.
|
||||
setAirplaneModeEnabled(false);
|
||||
LogUtil.CLog.d("Original airplane mode state: " + mOriginalAirplaneModeEnabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tearDown(TestInformation testInformation, Throwable e)
|
||||
throws DeviceNotAvailableException {
|
||||
setAirplaneModeEnabled(mOriginalAirplaneModeEnabled);
|
||||
setAppStandbyEnabled(mOriginalAppStandbyEnabled);
|
||||
setBatteryStatsConstants(mOriginalBatteryStatsConstants);
|
||||
}
|
||||
|
||||
private void setAirplaneModeEnabled(boolean enable) throws DeviceNotAvailableException {
|
||||
executeCmd("cmd connectivity airplane-mode " + (enable ? "enable" : "disable"));
|
||||
}
|
||||
|
||||
private boolean getAirplaneModeEnabled() throws DeviceNotAvailableException {
|
||||
return "enabled".equals(executeCmd("cmd connectivity airplane-mode").trim());
|
||||
}
|
||||
|
||||
private void setAppStandbyEnabled(String appStandbyEnabled) throws DeviceNotAvailableException {
|
||||
if ("null".equals(appStandbyEnabled)) {
|
||||
executeCmd("settings delete global app_standby_enabled");
|
||||
} else {
|
||||
executeCmd("settings put global app_standby_enabled " + appStandbyEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
private String getAppStandbyEnabled() throws DeviceNotAvailableException {
|
||||
return executeCmd("settings get global app_standby_enabled").trim();
|
||||
}
|
||||
|
||||
private void setBatteryStatsConstants(String batteryStatsConstants)
|
||||
throws DeviceNotAvailableException {
|
||||
executeCmd("settings put global battery_stats_constants \"" + batteryStatsConstants + "\"");
|
||||
}
|
||||
|
||||
private String getBatteryStatsConstants() throws DeviceNotAvailableException {
|
||||
return executeCmd("settings get global battery_stats_constants");
|
||||
}
|
||||
|
||||
private String executeCmd(String cmd) throws DeviceNotAvailableException {
|
||||
final String output = mDevice.executeShellCommand(cmd).trim();
|
||||
LogUtil.CLog.d("Output for '%s': %s", cmd, output);
|
||||
return output;
|
||||
}
|
||||
}
|
||||
169
tests/cts/hostside/src/com/android/cts/net/ProcNetTest.java
Normal file
169
tests/cts/hostside/src/com/android/cts/net/ProcNetTest.java
Normal file
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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 android.security.cts;
|
||||
|
||||
import com.android.tradefed.build.IBuildInfo;
|
||||
import com.android.tradefed.device.ITestDevice;
|
||||
import com.android.tradefed.testtype.DeviceTestCase;
|
||||
import com.android.tradefed.testtype.IBuildReceiver;
|
||||
import com.android.tradefed.testtype.IDeviceTest;
|
||||
|
||||
import java.lang.Integer;
|
||||
import java.lang.String;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Host-side tests for values in /proc/net.
|
||||
*
|
||||
* These tests analyze /proc/net to verify that certain networking properties are correct.
|
||||
*/
|
||||
public class ProcNetTest extends DeviceTestCase implements IBuildReceiver, IDeviceTest {
|
||||
private static final String SPI_TIMEOUT_SYSCTL = "/proc/sys/net/core/xfrm_acq_expires";
|
||||
private static final int MIN_ACQ_EXPIRES = 3600;
|
||||
// Global sysctls. Must be present and set to 1.
|
||||
private static final String[] GLOBAL_SYSCTLS = {
|
||||
"/proc/sys/net/ipv4/fwmark_reflect",
|
||||
"/proc/sys/net/ipv6/fwmark_reflect",
|
||||
"/proc/sys/net/ipv4/tcp_fwmark_accept",
|
||||
};
|
||||
|
||||
// Per-interface IPv6 autoconf sysctls.
|
||||
private static final String IPV6_SYSCTL_DIR = "/proc/sys/net/ipv6/conf";
|
||||
private static final String AUTOCONF_SYSCTL = "accept_ra_rt_table";
|
||||
|
||||
// Expected values for MIN|MAX_PLEN.
|
||||
private static final String ACCEPT_RA_RT_INFO_MIN_PLEN_STRING = "accept_ra_rt_info_min_plen";
|
||||
private static final int ACCEPT_RA_RT_INFO_MIN_PLEN_VALUE = 48;
|
||||
private static final String ACCEPT_RA_RT_INFO_MAX_PLEN_STRING = "accept_ra_rt_info_max_plen";
|
||||
private static final int ACCEPT_RA_RT_INFO_MAX_PLEN_VALUE = 64;
|
||||
// Expected values for RFC 7559 router soliciations.
|
||||
// Maximum number of router solicitations to send. -1 means no limit.
|
||||
private static final int IPV6_WIFI_ROUTER_SOLICITATIONS = -1;
|
||||
private ITestDevice mDevice;
|
||||
private IBuildInfo mBuild;
|
||||
private String[] mSysctlDirs;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void setBuild(IBuildInfo build) {
|
||||
mBuild = build;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void setDevice(ITestDevice device) {
|
||||
super.setDevice(device);
|
||||
mDevice = device;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
mSysctlDirs = getSysctlDirs();
|
||||
}
|
||||
|
||||
private String[] getSysctlDirs() throws Exception {
|
||||
String interfaceDirs[] = mDevice.executeAdbCommand("shell", "ls", "-1",
|
||||
IPV6_SYSCTL_DIR).split("\n");
|
||||
List<String> interfaceDirsList = new ArrayList<String>(Arrays.asList(interfaceDirs));
|
||||
interfaceDirsList.remove("all");
|
||||
interfaceDirsList.remove("lo");
|
||||
return interfaceDirsList.toArray(new String[interfaceDirsList.size()]);
|
||||
}
|
||||
|
||||
|
||||
protected void assertLess(String sysctl, int a, int b) {
|
||||
assertTrue("value of " + sysctl + ": expected < " + b + " but was: " + a, a < b);
|
||||
}
|
||||
|
||||
protected void assertAtLeast(String sysctl, int a, int b) {
|
||||
assertTrue("value of " + sysctl + ": expected >= " + b + " but was: " + a, a >= b);
|
||||
}
|
||||
|
||||
public int readIntFromPath(String path) throws Exception {
|
||||
String mode = mDevice.executeAdbCommand("shell", "stat", "-c", "%a", path).trim();
|
||||
String user = mDevice.executeAdbCommand("shell", "stat", "-c", "%u", path).trim();
|
||||
String group = mDevice.executeAdbCommand("shell", "stat", "-c", "%g", path).trim();
|
||||
assertEquals(mode, "644");
|
||||
assertEquals(user, "0");
|
||||
assertEquals(group, "0");
|
||||
return Integer.parseInt(mDevice.executeAdbCommand("shell", "cat", path).trim());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that SPI default timeouts are overridden, and set to a reasonable length of time
|
||||
*/
|
||||
public void testMinAcqExpires() throws Exception {
|
||||
int value = readIntFromPath(SPI_TIMEOUT_SYSCTL);
|
||||
assertAtLeast(SPI_TIMEOUT_SYSCTL, value, MIN_ACQ_EXPIRES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the sysctls for multinetwork kernel features are present and
|
||||
* enabled.
|
||||
*/
|
||||
public void testProcSysctls() throws Exception {
|
||||
for (String sysctl : GLOBAL_SYSCTLS) {
|
||||
int value = readIntFromPath(sysctl);
|
||||
assertEquals(sysctl, 1, value);
|
||||
}
|
||||
|
||||
for (String interfaceDir : mSysctlDirs) {
|
||||
String path = IPV6_SYSCTL_DIR + "/" + interfaceDir + "/" + AUTOCONF_SYSCTL;
|
||||
int value = readIntFromPath(path);
|
||||
assertLess(path, value, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that accept_ra_rt_info_{min,max}_plen exists and is set to the expected value
|
||||
*/
|
||||
public void testAcceptRaRtInfoMinMaxPlen() throws Exception {
|
||||
for (String interfaceDir : mSysctlDirs) {
|
||||
String path = IPV6_SYSCTL_DIR + "/" + interfaceDir + "/" + "accept_ra_rt_info_min_plen";
|
||||
int value = readIntFromPath(path);
|
||||
assertEquals(path, value, ACCEPT_RA_RT_INFO_MIN_PLEN_VALUE);
|
||||
path = IPV6_SYSCTL_DIR + "/" + interfaceDir + "/" + "accept_ra_rt_info_max_plen";
|
||||
value = readIntFromPath(path);
|
||||
assertEquals(path, value, ACCEPT_RA_RT_INFO_MAX_PLEN_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that router_solicitations exists and is set to the expected value
|
||||
* and verify that router_solicitation_max_interval exists and is in an acceptable interval.
|
||||
*/
|
||||
public void testRouterSolicitations() throws Exception {
|
||||
for (String interfaceDir : mSysctlDirs) {
|
||||
String path = IPV6_SYSCTL_DIR + "/" + interfaceDir + "/" + "router_solicitations";
|
||||
int value = readIntFromPath(path);
|
||||
assertEquals(IPV6_WIFI_ROUTER_SOLICITATIONS, value);
|
||||
path = IPV6_SYSCTL_DIR + "/" + interfaceDir + "/" + "router_solicitation_max_interval";
|
||||
int interval = readIntFromPath(path);
|
||||
final int lowerBoundSec = 15 * 60;
|
||||
final int upperBoundSec = 60 * 60;
|
||||
assertTrue(lowerBoundSec <= interval);
|
||||
assertTrue(interval <= upperBoundSec);
|
||||
}
|
||||
}
|
||||
}
|
||||
84
tests/cts/net/Android.bp
Normal file
84
tests/cts/net/Android.bp
Normal file
@@ -0,0 +1,84 @@
|
||||
// Copyright (C) 2008 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.
|
||||
|
||||
java_defaults {
|
||||
name: "CtsNetTestCasesDefaults",
|
||||
defaults: ["cts_defaults"],
|
||||
|
||||
// Include both the 32 and 64 bit versions
|
||||
compile_multilib: "both",
|
||||
|
||||
libs: [
|
||||
"voip-common",
|
||||
"android.test.base",
|
||||
],
|
||||
|
||||
jni_libs: [
|
||||
"libcts_jni",
|
||||
"libnativedns_jni",
|
||||
"libnativemultinetwork_jni",
|
||||
"libnativehelper_compat_libc++",
|
||||
],
|
||||
|
||||
srcs: [
|
||||
"src/**/*.java",
|
||||
"src/**/*.kt",
|
||||
],
|
||||
jarjar_rules: "jarjar-rules-shared.txt",
|
||||
static_libs: [
|
||||
"FrameworksNetCommonTests",
|
||||
"TestNetworkStackLib",
|
||||
"core-tests-support",
|
||||
"cts-net-utils",
|
||||
"ctstestrunner-axt",
|
||||
"junit",
|
||||
"junit-params",
|
||||
"net-utils-framework-common",
|
||||
"truth-prebuilt",
|
||||
],
|
||||
|
||||
// uncomment when b/13249961 is fixed
|
||||
// sdk_version: "current",
|
||||
platform_apis: true,
|
||||
}
|
||||
|
||||
// Networking CTS tests for development and release. These tests always target the platform SDK
|
||||
// version, and are subject to all the restrictions appropriate to that version. Before SDK
|
||||
// finalization, these tests have a min_sdk_version of 10000, and cannot be installed on release
|
||||
// devices.
|
||||
android_test {
|
||||
name: "CtsNetTestCases",
|
||||
defaults: ["CtsNetTestCasesDefaults"],
|
||||
test_suites: [
|
||||
"cts",
|
||||
"general-tests",
|
||||
],
|
||||
test_config_template: "AndroidTestTemplate.xml",
|
||||
}
|
||||
|
||||
// Networking CTS tests that target the latest released SDK. These tests can be installed on release
|
||||
// devices at any point in the Android release cycle and are useful for qualifying mainline modules
|
||||
// on release devices.
|
||||
android_test {
|
||||
name: "CtsNetTestCasesLatestSdk",
|
||||
defaults: ["CtsNetTestCasesDefaults"],
|
||||
jni_uses_sdk_apis: true,
|
||||
min_sdk_version: "29",
|
||||
target_sdk_version: "30",
|
||||
test_suites: [
|
||||
"general-tests",
|
||||
"mts",
|
||||
],
|
||||
test_config_template: "AndroidTestTemplate.xml",
|
||||
}
|
||||
57
tests/cts/net/AndroidManifest.xml
Normal file
57
tests/cts/net/AndroidManifest.xml
Normal file
@@ -0,0 +1,57 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
* Copyright (C) 2007 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.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="android.net.cts"
|
||||
android:targetSandboxVersion="2">
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||||
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS" />
|
||||
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
|
||||
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
|
||||
|
||||
<!-- This test also uses signature permissions through adopting the shell identity.
|
||||
The permissions acquired that way include (probably not exhaustive) :
|
||||
android.permission.MANAGE_TEST_NETWORKS
|
||||
-->
|
||||
|
||||
<application android:usesCleartextTraffic="true">
|
||||
<uses-library android:name="android.test.runner" />
|
||||
<uses-library android:name="org.apache.http.legacy" android:required="false" />
|
||||
</application>
|
||||
|
||||
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
|
||||
android:targetPackage="android.net.cts"
|
||||
android:label="CTS tests of android.net">
|
||||
<meta-data android:name="listener"
|
||||
android:value="com.android.cts.runner.CtsTestRunListener" />
|
||||
</instrumentation>
|
||||
|
||||
</manifest>
|
||||
|
||||
35
tests/cts/net/AndroidTestTemplate.xml
Normal file
35
tests/cts/net/AndroidTestTemplate.xml
Normal file
@@ -0,0 +1,35 @@
|
||||
<!-- Copyright (C) 2015 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.
|
||||
-->
|
||||
<configuration description="Test config for {MODULE}">
|
||||
<option name="test-suite-tag" value="cts" />
|
||||
<option name="config-descriptor:metadata" key="component" value="networking" />
|
||||
<option name="config-descriptor:metadata" key="token" value="SIM_CARD" />
|
||||
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
|
||||
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
|
||||
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
|
||||
|
||||
<option name="config-descriptor:metadata" key="mainline-param" value="CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex" />
|
||||
<option name="not-shardable" value="true" />
|
||||
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
|
||||
<option name="cleanup-apks" value="true" />
|
||||
<option name="test-file-name" value="{MODULE}.apk" />
|
||||
</target_preparer>
|
||||
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
|
||||
<option name="package" value="android.net.cts" />
|
||||
<option name="runtime-hint" value="9m4s" />
|
||||
<option name="hidden-api-checks" value="false" />
|
||||
<option name="isolated-storage" value="false" />
|
||||
</test>
|
||||
</configuration>
|
||||
3
tests/cts/net/OWNERS
Normal file
3
tests/cts/net/OWNERS
Normal file
@@ -0,0 +1,3 @@
|
||||
# Bug component: 31808
|
||||
lorenzo@google.com
|
||||
satk@google.com
|
||||
23
tests/cts/net/TEST_MAPPING
Normal file
23
tests/cts/net/TEST_MAPPING
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
// TODO: move to mainline-presubmit once supported
|
||||
"presubmit": [
|
||||
{
|
||||
"name": "CtsNetTestCasesLatestSdk",
|
||||
"options": [
|
||||
{
|
||||
"exclude-annotation": "com.android.testutils.SkipPresubmit"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"mainline-presubmit": [
|
||||
{
|
||||
"name": "CtsNetTestCasesLatestSdk[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
|
||||
"options": [
|
||||
{
|
||||
"exclude-annotation": "com.android.testutils.SkipPresubmit"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
51
tests/cts/net/api23Test/Android.bp
Normal file
51
tests/cts/net/api23Test/Android.bp
Normal file
@@ -0,0 +1,51 @@
|
||||
// Copyright (C) 2019 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.
|
||||
|
||||
android_test {
|
||||
name: "CtsNetApi23TestCases",
|
||||
defaults: ["cts_defaults"],
|
||||
|
||||
// Include both the 32 and 64 bit versions
|
||||
compile_multilib: "both",
|
||||
|
||||
libs: [
|
||||
"android.test.base",
|
||||
],
|
||||
|
||||
srcs: [
|
||||
"src/**/*.java",
|
||||
"src/**/*.kt",
|
||||
],
|
||||
|
||||
static_libs: [
|
||||
"core-tests-support",
|
||||
"compatibility-device-util-axt",
|
||||
"cts-net-utils",
|
||||
"ctstestrunner-axt",
|
||||
"ctstestserver",
|
||||
"mockwebserver",
|
||||
"junit",
|
||||
"junit-params",
|
||||
"truth-prebuilt",
|
||||
],
|
||||
|
||||
platform_apis: true,
|
||||
|
||||
// Tag this module as a cts test artifact
|
||||
test_suites: [
|
||||
"cts",
|
||||
"general-tests",
|
||||
],
|
||||
|
||||
}
|
||||
45
tests/cts/net/api23Test/AndroidManifest.xml
Normal file
45
tests/cts/net/api23Test/AndroidManifest.xml
Normal file
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
* Copyright (C) 2019 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.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="android.net.cts.api23test">
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application android:usesCleartextTraffic="true">
|
||||
<uses-library android:name="android.test.runner" />
|
||||
|
||||
<receiver android:name=".ConnectivityReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
</application>
|
||||
|
||||
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
|
||||
android:targetPackage="android.net.cts.api23test"
|
||||
android:label="CTS tests of android.net">
|
||||
<meta-data android:name="listener"
|
||||
android:value="com.android.cts.runner.CtsTestRunListener" />
|
||||
</instrumentation>
|
||||
</manifest>
|
||||
|
||||
31
tests/cts/net/api23Test/AndroidTest.xml
Normal file
31
tests/cts/net/api23Test/AndroidTest.xml
Normal file
@@ -0,0 +1,31 @@
|
||||
<!-- Copyright (C) 2019 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.
|
||||
-->
|
||||
<configuration description="Config for CTS Net API23 test cases">
|
||||
<option name="test-suite-tag" value="cts" />
|
||||
<option name="config-descriptor:metadata" key="component" value="networking" />
|
||||
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
|
||||
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
|
||||
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
|
||||
<option name="not-shardable" value="true" />
|
||||
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
|
||||
<option name="cleanup-apks" value="true" />
|
||||
<option name="test-file-name" value="CtsNetApi23TestCases.apk" />
|
||||
<option name="test-file-name" value="CtsNetTestAppForApi23.apk" />
|
||||
</target_preparer>
|
||||
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
|
||||
<option name="package" value="android.net.cts.api23test" />
|
||||
<option name="hidden-api-checks" value="false" />
|
||||
</test>
|
||||
</configuration>
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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 android.net.cts.api23test;
|
||||
|
||||
import static android.content.pm.PackageManager.FEATURE_WIFI;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.cts.util.CtsNetUtils;
|
||||
import android.os.Looper;
|
||||
import android.test.AndroidTestCase;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class ConnectivityManagerApi23Test extends AndroidTestCase {
|
||||
private static final String TAG = ConnectivityManagerApi23Test.class.getSimpleName();
|
||||
private static final int SEND_BROADCAST_TIMEOUT = 30000;
|
||||
// Intent string to get the number of wifi CONNECTIVITY_ACTION callbacks the test app has seen
|
||||
public static final String GET_WIFI_CONNECTIVITY_ACTION_COUNT =
|
||||
"android.net.cts.appForApi23.getWifiConnectivityActionCount";
|
||||
// Action sent to ConnectivityActionReceiver when a network callback is sent via PendingIntent.
|
||||
|
||||
private Context mContext;
|
||||
private PackageManager mPackageManager;
|
||||
private CtsNetUtils mCtsNetUtils;
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
Looper.prepare();
|
||||
mContext = getContext();
|
||||
mPackageManager = mContext.getPackageManager();
|
||||
mCtsNetUtils = new CtsNetUtils(mContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests reporting of connectivity changed.
|
||||
*/
|
||||
public void testConnectivityChanged_manifestRequestOnly_shouldNotReceiveIntent() {
|
||||
if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
|
||||
Log.i(TAG, "testConnectivityChanged_manifestRequestOnly_shouldNotReceiveIntent cannot execute unless device supports WiFi");
|
||||
return;
|
||||
}
|
||||
ConnectivityReceiver.prepare();
|
||||
|
||||
mCtsNetUtils.toggleWifi();
|
||||
|
||||
// The connectivity broadcast has been sent; push through a terminal broadcast
|
||||
// to wait for in the receive to confirm it didn't see the connectivity change.
|
||||
Intent finalIntent = new Intent(ConnectivityReceiver.FINAL_ACTION);
|
||||
finalIntent.setClass(mContext, ConnectivityReceiver.class);
|
||||
mContext.sendBroadcast(finalIntent);
|
||||
assertFalse(ConnectivityReceiver.waitForBroadcast());
|
||||
}
|
||||
|
||||
public void testConnectivityChanged_manifestRequestOnlyPreN_shouldReceiveIntent()
|
||||
throws InterruptedException {
|
||||
if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
|
||||
Log.i(TAG, "testConnectivityChanged_manifestRequestOnlyPreN_shouldReceiveIntent cannot"
|
||||
+ "execute unless device supports WiFi");
|
||||
return;
|
||||
}
|
||||
mContext.startActivity(new Intent()
|
||||
.setComponent(new ComponentName("android.net.cts.appForApi23",
|
||||
"android.net.cts.appForApi23.ConnectivityListeningActivity"))
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
|
||||
Thread.sleep(200);
|
||||
|
||||
mCtsNetUtils.toggleWifi();
|
||||
|
||||
Intent getConnectivityCount = new Intent(GET_WIFI_CONNECTIVITY_ACTION_COUNT);
|
||||
assertEquals(2, sendOrderedBroadcastAndReturnResultCode(
|
||||
getConnectivityCount, SEND_BROADCAST_TIMEOUT));
|
||||
}
|
||||
|
||||
public void testConnectivityChanged_whenRegistered_shouldReceiveIntent() {
|
||||
if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
|
||||
Log.i(TAG, "testConnectivityChanged_whenRegistered_shouldReceiveIntent cannot execute unless device supports WiFi");
|
||||
return;
|
||||
}
|
||||
ConnectivityReceiver.prepare();
|
||||
ConnectivityReceiver receiver = new ConnectivityReceiver();
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
|
||||
mContext.registerReceiver(receiver, filter);
|
||||
|
||||
mCtsNetUtils.toggleWifi();
|
||||
Intent finalIntent = new Intent(ConnectivityReceiver.FINAL_ACTION);
|
||||
finalIntent.setClass(mContext, ConnectivityReceiver.class);
|
||||
mContext.sendBroadcast(finalIntent);
|
||||
|
||||
assertTrue(ConnectivityReceiver.waitForBroadcast());
|
||||
}
|
||||
|
||||
private int sendOrderedBroadcastAndReturnResultCode(
|
||||
Intent intent, int timeoutMs) throws InterruptedException {
|
||||
final LinkedBlockingQueue<Integer> result = new LinkedBlockingQueue<>(1);
|
||||
mContext.sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
result.offer(getResultCode());
|
||||
}
|
||||
}, null, 0, null, null);
|
||||
|
||||
Integer resultCode = result.poll(timeoutMs, TimeUnit.MILLISECONDS);
|
||||
assertNotNull("Timed out (more than " + timeoutMs +
|
||||
" milliseconds) waiting for result code for broadcast", resultCode);
|
||||
return resultCode;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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 android.net.cts.api23test;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class ConnectivityReceiver extends BroadcastReceiver {
|
||||
static boolean sReceivedConnectivity;
|
||||
static boolean sReceivedFinal;
|
||||
static CountDownLatch sLatch;
|
||||
|
||||
static void prepare() {
|
||||
synchronized (ConnectivityReceiver.class) {
|
||||
sReceivedConnectivity = sReceivedFinal = false;
|
||||
sLatch = new CountDownLatch(1);
|
||||
}
|
||||
}
|
||||
|
||||
static boolean waitForBroadcast() {
|
||||
try {
|
||||
sLatch.await(30, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
synchronized (ConnectivityReceiver.class) {
|
||||
sLatch = null;
|
||||
if (!sReceivedFinal) {
|
||||
throw new IllegalStateException("Never received final broadcast");
|
||||
}
|
||||
return sReceivedConnectivity;
|
||||
}
|
||||
}
|
||||
|
||||
static final String FINAL_ACTION = "android.net.cts.action.FINAL";
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Log.i("ConnectivityReceiver", "Received: " + intent.getAction());
|
||||
if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
|
||||
sReceivedConnectivity = true;
|
||||
} else if (FINAL_ACTION.equals(intent.getAction())) {
|
||||
sReceivedFinal = true;
|
||||
if (sLatch != null) {
|
||||
sLatch.countDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
32
tests/cts/net/appForApi23/Android.bp
Normal file
32
tests/cts/net/appForApi23/Android.bp
Normal file
@@ -0,0 +1,32 @@
|
||||
// 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.
|
||||
|
||||
android_test {
|
||||
name: "CtsNetTestAppForApi23",
|
||||
defaults: ["cts_defaults"],
|
||||
|
||||
// Include both the 32 and 64 bit versions
|
||||
compile_multilib: "both",
|
||||
|
||||
srcs: ["src/**/*.java"],
|
||||
|
||||
sdk_version: "23",
|
||||
|
||||
// Tag this module as a cts test artifact
|
||||
test_suites: [
|
||||
"cts",
|
||||
"general-tests",
|
||||
],
|
||||
|
||||
}
|
||||
47
tests/cts/net/appForApi23/AndroidManifest.xml
Normal file
47
tests/cts/net/appForApi23/AndroidManifest.xml
Normal file
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
* 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.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="android.net.cts.appForApi23">
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application>
|
||||
<receiver android:name=".ConnectivityReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.net.cts.appForApi23.getWifiConnectivityActionCount" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<activity android:name=".ConnectivityListeningActivity"
|
||||
android:label="ConnectivityListeningActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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 android.net.cts.appForApi23;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
// Stub activity used to start the app
|
||||
public class ConnectivityListeningActivity extends Activity {
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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 android.net.cts.appForApi23;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.ConnectivityManager;
|
||||
|
||||
public class ConnectivityReceiver extends BroadcastReceiver {
|
||||
public static String GET_WIFI_CONNECTIVITY_ACTION_COUNT =
|
||||
"android.net.cts.appForApi23.getWifiConnectivityActionCount";
|
||||
|
||||
private static int sWifiConnectivityActionCount = 0;
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
|
||||
int networkType = intent.getIntExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, 0);
|
||||
if (networkType == ConnectivityManager.TYPE_WIFI) {
|
||||
sWifiConnectivityActionCount++;
|
||||
}
|
||||
}
|
||||
if (GET_WIFI_CONNECTIVITY_ACTION_COUNT.equals(intent.getAction())) {
|
||||
setResultCode(sWifiConnectivityActionCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
/*
|
||||
** Copyright (C) 2018 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.
|
||||
*/
|
||||
-->
|
||||
<!-- This test config file is for NetworkWatchlistTest tests -->
|
||||
<watchlist-config>
|
||||
<sha256-domain>
|
||||
</sha256-domain>
|
||||
<sha256-ip>
|
||||
</sha256-ip>
|
||||
<crc32-domain>
|
||||
</crc32-domain>
|
||||
<crc32-ip>
|
||||
</crc32-ip>
|
||||
</watchlist-config>
|
||||
34
tests/cts/net/assets/network_watchlist_config_for_test.xml
Normal file
34
tests/cts/net/assets/network_watchlist_config_for_test.xml
Normal file
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
/*
|
||||
** Copyright (C) 2018 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.
|
||||
*/
|
||||
-->
|
||||
<!-- This test config file just contains some random hashes for testing
|
||||
ConnectivityManager.getWatchlistConfigHash() -->
|
||||
<watchlist-config>
|
||||
<sha256-domain>
|
||||
<hash>F0905DA7549614957B449034C281EF7BDEFDBC2B6E050AD1E78D6DE18FBD0D5F</hash>
|
||||
</sha256-domain>
|
||||
<sha256-ip>
|
||||
<hash>18DD41C9F2E8E4879A1575FB780514EF33CF6E1F66578C4AE7CCA31F49B9F2EC</hash>
|
||||
</sha256-ip>
|
||||
<crc32-domain>
|
||||
<hash>AAAAAAAA</hash>
|
||||
</crc32-domain>
|
||||
<crc32-ip>
|
||||
<hash>BBBBBBBB</hash>
|
||||
</crc32-ip>
|
||||
</watchlist-config>
|
||||
2
tests/cts/net/jarjar-rules-shared.txt
Normal file
2
tests/cts/net/jarjar-rules-shared.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
# Module library in frameworks/libs/net
|
||||
rule com.android.net.module.util.** android.net.cts.util.@1
|
||||
51
tests/cts/net/jni/Android.bp
Normal file
51
tests/cts/net/jni/Android.bp
Normal file
@@ -0,0 +1,51 @@
|
||||
// Copyright (C) 2013 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.
|
||||
|
||||
cc_library_shared {
|
||||
name: "libnativedns_jni",
|
||||
|
||||
srcs: ["NativeDnsJni.c"],
|
||||
sdk_version: "current",
|
||||
|
||||
shared_libs: [
|
||||
"libnativehelper_compat_libc++",
|
||||
"liblog",
|
||||
],
|
||||
stl: "libc++_static",
|
||||
|
||||
cflags: [
|
||||
"-Wall",
|
||||
"-Werror",
|
||||
"-Wno-unused-parameter",
|
||||
],
|
||||
|
||||
}
|
||||
|
||||
cc_library_shared {
|
||||
name: "libnativemultinetwork_jni",
|
||||
|
||||
srcs: ["NativeMultinetworkJni.cpp"],
|
||||
sdk_version: "current",
|
||||
cflags: [
|
||||
"-Wall",
|
||||
"-Werror",
|
||||
"-Wno-format",
|
||||
],
|
||||
shared_libs: [
|
||||
"libandroid",
|
||||
"libnativehelper_compat_libc++",
|
||||
"liblog",
|
||||
],
|
||||
stl: "libc++_static",
|
||||
}
|
||||
181
tests/cts/net/jni/NativeDnsJni.c
Normal file
181
tests/cts/net/jni/NativeDnsJni.c
Normal file
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* Copyright (C) 2010 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.
|
||||
*/
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <jni.h>
|
||||
#include <netdb.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <android/log.h>
|
||||
|
||||
#define LOG_TAG "NativeDns-JNI"
|
||||
#define LOGD(fmt, ...) \
|
||||
__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, fmt, ##__VA_ARGS__)
|
||||
|
||||
const char *GoogleDNSIpV4Address="8.8.8.8";
|
||||
const char *GoogleDNSIpV4Address2="8.8.4.4";
|
||||
const char *GoogleDNSIpV6Address="2001:4860:4860::8888";
|
||||
const char *GoogleDNSIpV6Address2="2001:4860:4860::8844";
|
||||
|
||||
JNIEXPORT jboolean Java_android_net_cts_DnsTest_testNativeDns(JNIEnv* env, jclass class)
|
||||
{
|
||||
const char *node = "www.google.com";
|
||||
char *service = NULL;
|
||||
struct addrinfo *answer;
|
||||
|
||||
int res = getaddrinfo(node, service, NULL, &answer);
|
||||
LOGD("getaddrinfo(www.google.com) gave res=%d (%s)", res, gai_strerror(res));
|
||||
if (res != 0) return JNI_FALSE;
|
||||
|
||||
// check for v4 & v6
|
||||
{
|
||||
int foundv4 = 0;
|
||||
int foundv6 = 0;
|
||||
struct addrinfo *current = answer;
|
||||
while (current != NULL) {
|
||||
char buf[256];
|
||||
if (current->ai_addr->sa_family == AF_INET) {
|
||||
inet_ntop(current->ai_family, &((struct sockaddr_in *)current->ai_addr)->sin_addr,
|
||||
buf, sizeof(buf));
|
||||
foundv4 = 1;
|
||||
LOGD(" %s", buf);
|
||||
} else if (current->ai_addr->sa_family == AF_INET6) {
|
||||
inet_ntop(current->ai_family, &((struct sockaddr_in6 *)current->ai_addr)->sin6_addr,
|
||||
buf, sizeof(buf));
|
||||
foundv6 = 1;
|
||||
LOGD(" %s", buf);
|
||||
}
|
||||
current = current->ai_next;
|
||||
}
|
||||
|
||||
freeaddrinfo(answer);
|
||||
answer = NULL;
|
||||
if (foundv4 != 1 && foundv6 != 1) {
|
||||
LOGD("getaddrinfo(www.google.com) didn't find either v4 or v6 address");
|
||||
return JNI_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
node = "ipv6.google.com";
|
||||
res = getaddrinfo(node, service, NULL, &answer);
|
||||
LOGD("getaddrinfo(ipv6.google.com) gave res=%d", res);
|
||||
if (res != 0) return JNI_FALSE;
|
||||
|
||||
{
|
||||
int foundv4 = 0;
|
||||
int foundv6 = 0;
|
||||
struct addrinfo *current = answer;
|
||||
while (current != NULL) {
|
||||
char buf[256];
|
||||
if (current->ai_addr->sa_family == AF_INET) {
|
||||
inet_ntop(current->ai_family, &((struct sockaddr_in *)current->ai_addr)->sin_addr,
|
||||
buf, sizeof(buf));
|
||||
LOGD(" %s", buf);
|
||||
foundv4 = 1;
|
||||
} else if (current->ai_addr->sa_family == AF_INET6) {
|
||||
inet_ntop(current->ai_family, &((struct sockaddr_in6 *)current->ai_addr)->sin6_addr,
|
||||
buf, sizeof(buf));
|
||||
LOGD(" %s", buf);
|
||||
foundv6 = 1;
|
||||
}
|
||||
current = current->ai_next;
|
||||
}
|
||||
|
||||
freeaddrinfo(answer);
|
||||
answer = NULL;
|
||||
if (foundv4 == 1 || foundv6 != 1) {
|
||||
LOGD("getaddrinfo(ipv6.google.com) didn't find only v6");
|
||||
return JNI_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
// getnameinfo
|
||||
struct sockaddr_in sa4;
|
||||
sa4.sin_family = AF_INET;
|
||||
sa4.sin_port = 0;
|
||||
inet_pton(AF_INET, GoogleDNSIpV4Address, &(sa4.sin_addr));
|
||||
|
||||
struct sockaddr_in6 sa6;
|
||||
sa6.sin6_family = AF_INET6;
|
||||
sa6.sin6_port = 0;
|
||||
sa6.sin6_flowinfo = 0;
|
||||
sa6.sin6_scope_id = 0;
|
||||
inet_pton(AF_INET6, GoogleDNSIpV6Address2, &(sa6.sin6_addr));
|
||||
|
||||
char buf[NI_MAXHOST];
|
||||
int flags = NI_NAMEREQD;
|
||||
|
||||
res = getnameinfo((const struct sockaddr*)&sa4, sizeof(sa4), buf, sizeof(buf), NULL, 0, flags);
|
||||
if (res != 0) {
|
||||
LOGD("getnameinfo(%s (GoogleDNS) ) gave error %d (%s)", GoogleDNSIpV4Address, res,
|
||||
gai_strerror(res));
|
||||
return JNI_FALSE;
|
||||
}
|
||||
if (strstr(buf, "google.com") == NULL && strstr(buf, "dns.google") == NULL) {
|
||||
LOGD("getnameinfo(%s (GoogleDNS) ) didn't return google.com or dns.google: %s",
|
||||
GoogleDNSIpV4Address, buf);
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
memset(buf, 0, sizeof(buf));
|
||||
res = getnameinfo((const struct sockaddr*)&sa6, sizeof(sa6), buf, sizeof(buf), NULL, 0, flags);
|
||||
if (res != 0) {
|
||||
LOGD("getnameinfo(%s (GoogleDNS) ) gave error %d (%s)", GoogleDNSIpV6Address2,
|
||||
res, gai_strerror(res));
|
||||
return JNI_FALSE;
|
||||
}
|
||||
if (strstr(buf, "google.com") == NULL && strstr(buf, "dns.google") == NULL) {
|
||||
LOGD("getnameinfo(%s (GoogleDNS) ) didn't return google.com or dns.google: %s",
|
||||
GoogleDNSIpV6Address2, buf);
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
// gethostbyname
|
||||
struct hostent *my_hostent = gethostbyname("www.youtube.com");
|
||||
if (my_hostent == NULL) {
|
||||
LOGD("gethostbyname(www.youtube.com) gave null response");
|
||||
return JNI_FALSE;
|
||||
}
|
||||
if ((my_hostent->h_addr_list == NULL) || (*my_hostent->h_addr_list == NULL)) {
|
||||
LOGD("gethostbyname(www.youtube.com) gave 0 addresses");
|
||||
return JNI_FALSE;
|
||||
}
|
||||
{
|
||||
char **current = my_hostent->h_addr_list;
|
||||
while (*current != NULL) {
|
||||
char buf[256];
|
||||
inet_ntop(my_hostent->h_addrtype, *current, buf, sizeof(buf));
|
||||
LOGD("gethostbyname(www.youtube.com) gave %s", buf);
|
||||
current++;
|
||||
}
|
||||
}
|
||||
|
||||
// gethostbyaddr
|
||||
char addr6[16];
|
||||
inet_pton(AF_INET6, GoogleDNSIpV6Address, addr6);
|
||||
my_hostent = gethostbyaddr(addr6, sizeof(addr6), AF_INET6);
|
||||
if (my_hostent == NULL) {
|
||||
LOGD("gethostbyaddr(%s (GoogleDNS) ) gave null response", GoogleDNSIpV6Address);
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
LOGD("gethostbyaddr(%s (GoogleDNS) ) gave %s for name", GoogleDNSIpV6Address,
|
||||
my_hostent->h_name ? my_hostent->h_name : "null");
|
||||
|
||||
if (my_hostent->h_name == NULL) return JNI_FALSE;
|
||||
return JNI_TRUE;
|
||||
}
|
||||
515
tests/cts/net/jni/NativeMultinetworkJni.cpp
Normal file
515
tests/cts/net/jni/NativeMultinetworkJni.cpp
Normal file
@@ -0,0 +1,515 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.
|
||||
*/
|
||||
|
||||
|
||||
#define LOG_TAG "MultinetworkApiTest"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <arpa/nameser.h>
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <jni.h>
|
||||
#include <netdb.h>
|
||||
#include <poll.h> /* poll */
|
||||
#include <resolv.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <android/log.h>
|
||||
#include <android/multinetwork.h>
|
||||
#include <nativehelper/JNIHelp.h>
|
||||
|
||||
#define LOGD(fmt, ...) \
|
||||
__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, fmt, ##__VA_ARGS__)
|
||||
|
||||
#define EXPECT_GE(env, actual, expected, msg) \
|
||||
do { \
|
||||
if (actual < expected) { \
|
||||
jniThrowExceptionFmt(env, "java/lang/AssertionError", \
|
||||
"%s:%d: %s EXPECT_GE: expected %d, got %d", \
|
||||
__FILE__, __LINE__, msg, expected, actual); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define EXPECT_GT(env, actual, expected, msg) \
|
||||
do { \
|
||||
if (actual <= expected) { \
|
||||
jniThrowExceptionFmt(env, "java/lang/AssertionError", \
|
||||
"%s:%d: %s EXPECT_GT: expected %d, got %d", \
|
||||
__FILE__, __LINE__, msg, expected, actual); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define EXPECT_EQ(env, expected, actual, msg) \
|
||||
do { \
|
||||
if (actual != expected) { \
|
||||
jniThrowExceptionFmt(env, "java/lang/AssertionError", \
|
||||
"%s:%d: %s EXPECT_EQ: expected %d, got %d", \
|
||||
__FILE__, __LINE__, msg, expected, actual); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
static const int MAXPACKET = 8 * 1024;
|
||||
static const int TIMEOUT_MS = 15000;
|
||||
static const char kHostname[] = "connectivitycheck.android.com";
|
||||
static const char kNxDomainName[] = "test1-nx.metric.gstatic.com";
|
||||
static const char kGoogleName[] = "www.google.com";
|
||||
|
||||
int makeQuery(const char* name, int qtype, uint8_t* buf, size_t buflen) {
|
||||
return res_mkquery(ns_o_query, name, ns_c_in, qtype, NULL, 0, NULL, buf, buflen);
|
||||
}
|
||||
|
||||
int getAsyncResponse(JNIEnv* env, int fd, int timeoutMs, int* rcode, uint8_t* buf, size_t bufLen) {
|
||||
struct pollfd wait_fd = { .fd = fd, .events = POLLIN };
|
||||
|
||||
poll(&wait_fd, 1, timeoutMs);
|
||||
if (wait_fd.revents & POLLIN) {
|
||||
int n = android_res_nresult(fd, rcode, buf, bufLen);
|
||||
// Verify that android_res_nresult() closed the fd
|
||||
char dummy;
|
||||
EXPECT_EQ(env, -1, read(fd, &dummy, sizeof(dummy)), "res_nresult check for closing fd");
|
||||
EXPECT_EQ(env, EBADF, errno, "res_nresult check for errno");
|
||||
return n;
|
||||
}
|
||||
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
int extractIpAddressAnswers(uint8_t* buf, size_t bufLen, int family) {
|
||||
ns_msg handle;
|
||||
if (ns_initparse((const uint8_t*) buf, bufLen, &handle) < 0) {
|
||||
return -errno;
|
||||
}
|
||||
const int ancount = ns_msg_count(handle, ns_s_an);
|
||||
// Answer count = 0 is valid(e.g. response of query with root)
|
||||
if (!ancount) {
|
||||
return 0;
|
||||
}
|
||||
ns_rr rr;
|
||||
bool hasValidAns = false;
|
||||
for (int i = 0; i < ancount; i++) {
|
||||
if (ns_parserr(&handle, ns_s_an, i, &rr) < 0) {
|
||||
// If there is no valid answer, test will fail.
|
||||
continue;
|
||||
}
|
||||
const uint8_t* rdata = ns_rr_rdata(rr);
|
||||
char buffer[INET6_ADDRSTRLEN];
|
||||
if (inet_ntop(family, (const char*) rdata, buffer, sizeof(buffer)) == NULL) {
|
||||
return -errno;
|
||||
}
|
||||
hasValidAns = true;
|
||||
}
|
||||
return hasValidAns ? 0 : -EBADMSG;
|
||||
}
|
||||
|
||||
int expectAnswersValid(JNIEnv* env, int fd, int family, int expectedRcode) {
|
||||
int rcode = -1;
|
||||
uint8_t buf[MAXPACKET] = {};
|
||||
int res = getAsyncResponse(env, fd, TIMEOUT_MS, &rcode, buf, MAXPACKET);
|
||||
if (res < 0) {
|
||||
return res;
|
||||
}
|
||||
|
||||
EXPECT_EQ(env, expectedRcode, rcode, "rcode is not expected");
|
||||
|
||||
if (expectedRcode == ns_r_noerror && res > 0) {
|
||||
return extractIpAddressAnswers(buf, res, family);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int expectAnswersNotValid(JNIEnv* env, int fd, int expectedErrno) {
|
||||
int rcode = -1;
|
||||
uint8_t buf[MAXPACKET] = {};
|
||||
int res = getAsyncResponse(env, fd, TIMEOUT_MS, &rcode, buf, MAXPACKET);
|
||||
if (res != expectedErrno) {
|
||||
LOGD("res:%d, expectedErrno = %d", res, expectedErrno);
|
||||
return (res > 0) ? -EREMOTEIO : res;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void Java_android_net_cts_MultinetworkApiTest_runResNqueryCheck(
|
||||
JNIEnv* env, jclass, jlong nethandle) {
|
||||
net_handle_t handle = (net_handle_t) nethandle;
|
||||
|
||||
// V4
|
||||
int fd = android_res_nquery(handle, kHostname, ns_c_in, ns_t_a, 0);
|
||||
EXPECT_GE(env, fd, 0, "v4 res_nquery");
|
||||
EXPECT_EQ(env, 0, expectAnswersValid(env, fd, AF_INET, ns_r_noerror),
|
||||
"v4 res_nquery check answers");
|
||||
|
||||
// V6
|
||||
fd = android_res_nquery(handle, kHostname, ns_c_in, ns_t_aaaa, 0);
|
||||
EXPECT_GE(env, fd, 0, "v6 res_nquery");
|
||||
EXPECT_EQ(env, 0, expectAnswersValid(env, fd, AF_INET, ns_r_noerror),
|
||||
"v6 res_nquery check answers");
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void Java_android_net_cts_MultinetworkApiTest_runResNsendCheck(
|
||||
JNIEnv* env, jclass, jlong nethandle) {
|
||||
net_handle_t handle = (net_handle_t) nethandle;
|
||||
// V4
|
||||
uint8_t buf1[MAXPACKET] = {};
|
||||
|
||||
int len1 = makeQuery(kGoogleName, ns_t_a, buf1, sizeof(buf1));
|
||||
EXPECT_GT(env, len1, 0, "v4 res_mkquery 1st");
|
||||
|
||||
uint8_t buf2[MAXPACKET] = {};
|
||||
int len2 = makeQuery(kHostname, ns_t_a, buf2, sizeof(buf2));
|
||||
EXPECT_GT(env, len2, 0, "v4 res_mkquery 2nd");
|
||||
|
||||
int fd1 = android_res_nsend(handle, buf1, len1, 0);
|
||||
EXPECT_GE(env, fd1, 0, "v4 res_nsend 1st");
|
||||
int fd2 = android_res_nsend(handle, buf2, len2, 0);
|
||||
EXPECT_GE(env, fd2, 0, "v4 res_nsend 2nd");
|
||||
|
||||
EXPECT_EQ(env, 0, expectAnswersValid(env, fd2, AF_INET, ns_r_noerror),
|
||||
"v4 res_nsend 2nd check answers");
|
||||
EXPECT_EQ(env, 0, expectAnswersValid(env, fd1, AF_INET, ns_r_noerror),
|
||||
"v4 res_nsend 1st check answers");
|
||||
|
||||
// V6
|
||||
memset(buf1, 0, sizeof(buf1));
|
||||
memset(buf2, 0, sizeof(buf2));
|
||||
len1 = makeQuery(kGoogleName, ns_t_aaaa, buf1, sizeof(buf1));
|
||||
EXPECT_GT(env, len1, 0, "v6 res_mkquery 1st");
|
||||
len2 = makeQuery(kHostname, ns_t_aaaa, buf2, sizeof(buf2));
|
||||
EXPECT_GT(env, len2, 0, "v6 res_mkquery 2nd");
|
||||
|
||||
fd1 = android_res_nsend(handle, buf1, len1, 0);
|
||||
EXPECT_GE(env, fd1, 0, "v6 res_nsend 1st");
|
||||
fd2 = android_res_nsend(handle, buf2, len2, 0);
|
||||
EXPECT_GE(env, fd2, 0, "v6 res_nsend 2nd");
|
||||
|
||||
EXPECT_EQ(env, 0, expectAnswersValid(env, fd2, AF_INET6, ns_r_noerror),
|
||||
"v6 res_nsend 2nd check answers");
|
||||
EXPECT_EQ(env, 0, expectAnswersValid(env, fd1, AF_INET6, ns_r_noerror),
|
||||
"v6 res_nsend 1st check answers");
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void Java_android_net_cts_MultinetworkApiTest_runResNnxDomainCheck(
|
||||
JNIEnv* env, jclass, jlong nethandle) {
|
||||
net_handle_t handle = (net_handle_t) nethandle;
|
||||
|
||||
// res_nquery V4 NXDOMAIN
|
||||
int fd = android_res_nquery(handle, kNxDomainName, ns_c_in, ns_t_a, 0);
|
||||
EXPECT_GE(env, fd, 0, "v4 res_nquery NXDOMAIN");
|
||||
EXPECT_EQ(env, 0, expectAnswersValid(env, fd, AF_INET, ns_r_nxdomain),
|
||||
"v4 res_nquery NXDOMAIN check answers");
|
||||
|
||||
// res_nquery V6 NXDOMAIN
|
||||
fd = android_res_nquery(handle, kNxDomainName, ns_c_in, ns_t_aaaa, 0);
|
||||
EXPECT_GE(env, fd, 0, "v6 res_nquery NXDOMAIN");
|
||||
EXPECT_EQ(env, 0, expectAnswersValid(env, fd, AF_INET6, ns_r_nxdomain),
|
||||
"v6 res_nquery NXDOMAIN check answers");
|
||||
|
||||
uint8_t buf[MAXPACKET] = {};
|
||||
// res_nsend V4 NXDOMAIN
|
||||
int len = makeQuery(kNxDomainName, ns_t_a, buf, sizeof(buf));
|
||||
EXPECT_GT(env, len, 0, "v4 res_mkquery NXDOMAIN");
|
||||
fd = android_res_nsend(handle, buf, len, 0);
|
||||
EXPECT_GE(env, fd, 0, "v4 res_nsend NXDOMAIN");
|
||||
EXPECT_EQ(env, 0, expectAnswersValid(env, fd, AF_INET, ns_r_nxdomain),
|
||||
"v4 res_nsend NXDOMAIN check answers");
|
||||
|
||||
// res_nsend V6 NXDOMAIN
|
||||
memset(buf, 0, sizeof(buf));
|
||||
len = makeQuery(kNxDomainName, ns_t_aaaa, buf, sizeof(buf));
|
||||
EXPECT_GT(env, len, 0, "v6 res_mkquery NXDOMAIN");
|
||||
fd = android_res_nsend(handle, buf, len, 0);
|
||||
EXPECT_GE(env, fd, 0, "v6 res_nsend NXDOMAIN");
|
||||
EXPECT_EQ(env, 0, expectAnswersValid(env, fd, AF_INET6, ns_r_nxdomain),
|
||||
"v6 res_nsend NXDOMAIN check answers");
|
||||
}
|
||||
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void Java_android_net_cts_MultinetworkApiTest_runResNcancelCheck(
|
||||
JNIEnv* env, jclass, jlong nethandle) {
|
||||
net_handle_t handle = (net_handle_t) nethandle;
|
||||
|
||||
int fd = android_res_nquery(handle, kGoogleName, ns_c_in, ns_t_a, 0);
|
||||
errno = 0;
|
||||
android_res_cancel(fd);
|
||||
int err = errno;
|
||||
EXPECT_EQ(env, 0, err, "res_cancel");
|
||||
// DO NOT call cancel or result with the same fd more than once,
|
||||
// otherwise it will hit fdsan double-close fd.
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void Java_android_net_cts_MultinetworkApiTest_runResNapiMalformedCheck(
|
||||
JNIEnv* env, jclass, jlong nethandle) {
|
||||
net_handle_t handle = (net_handle_t) nethandle;
|
||||
|
||||
// It is the equivalent of "dig . a", Query with an empty name.
|
||||
int fd = android_res_nquery(handle, "", ns_c_in, ns_t_a, 0);
|
||||
EXPECT_GE(env, fd, 0, "res_nquery root");
|
||||
EXPECT_EQ(env, 0, expectAnswersValid(env, fd, AF_INET, ns_r_noerror),
|
||||
"res_nquery root check answers");
|
||||
|
||||
// Label limit 63
|
||||
std::string exceedingLabelQuery = "www." + std::string(70, 'g') + ".com";
|
||||
// Name limit 255
|
||||
std::string exceedingDomainQuery = "www." + std::string(255, 'g') + ".com";
|
||||
|
||||
fd = android_res_nquery(handle, exceedingLabelQuery.c_str(), ns_c_in, ns_t_a, 0);
|
||||
EXPECT_EQ(env, -EMSGSIZE, fd, "res_nquery exceedingLabelQuery");
|
||||
fd = android_res_nquery(handle, exceedingDomainQuery.c_str(), ns_c_in, ns_t_aaaa, 0);
|
||||
EXPECT_EQ(env, -EMSGSIZE, fd, "res_nquery exceedingDomainQuery");
|
||||
|
||||
uint8_t buf[10] = {};
|
||||
// empty BLOB
|
||||
fd = android_res_nsend(handle, buf, 10, 0);
|
||||
EXPECT_GE(env, fd, 0, "res_nsend empty BLOB");
|
||||
EXPECT_EQ(env, 0, expectAnswersNotValid(env, fd, -EINVAL),
|
||||
"res_nsend empty BLOB check answers");
|
||||
|
||||
uint8_t largeBuf[2 * MAXPACKET] = {};
|
||||
// A buffer larger than 8KB
|
||||
fd = android_res_nsend(handle, largeBuf, sizeof(largeBuf), 0);
|
||||
EXPECT_EQ(env, -EMSGSIZE, fd, "res_nsend buffer larger than 8KB");
|
||||
|
||||
// 5000 bytes filled with 0. This returns EMSGSIZE because FrameworkListener limits the size of
|
||||
// commands to 4096 bytes.
|
||||
fd = android_res_nsend(handle, largeBuf, 5000, 0);
|
||||
EXPECT_EQ(env, -EMSGSIZE, fd, "res_nsend 5000 bytes filled with 0");
|
||||
|
||||
// 500 bytes filled with 0
|
||||
fd = android_res_nsend(handle, largeBuf, 500, 0);
|
||||
EXPECT_GE(env, fd, 0, "res_nsend 500 bytes filled with 0");
|
||||
EXPECT_EQ(env, 0, expectAnswersNotValid(env, fd, -EINVAL),
|
||||
"res_nsend 500 bytes filled with 0 check answers");
|
||||
|
||||
// 5000 bytes filled with 0xFF
|
||||
uint8_t ffBuf[5001] = {};
|
||||
memset(ffBuf, 0xFF, sizeof(ffBuf));
|
||||
ffBuf[5000] = '\0';
|
||||
fd = android_res_nsend(handle, ffBuf, sizeof(ffBuf), 0);
|
||||
EXPECT_EQ(env, -EMSGSIZE, fd, "res_nsend 5000 bytes filled with 0xFF");
|
||||
|
||||
// 500 bytes filled with 0xFF
|
||||
ffBuf[500] = '\0';
|
||||
fd = android_res_nsend(handle, ffBuf, 501, 0);
|
||||
EXPECT_GE(env, fd, 0, "res_nsend 500 bytes filled with 0xFF");
|
||||
EXPECT_EQ(env, 0, expectAnswersNotValid(env, fd, -EINVAL),
|
||||
"res_nsend 500 bytes filled with 0xFF check answers");
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jint Java_android_net_cts_MultinetworkApiTest_runGetaddrinfoCheck(
|
||||
JNIEnv*, jclass, jlong nethandle) {
|
||||
net_handle_t handle = (net_handle_t) nethandle;
|
||||
struct addrinfo *res = NULL;
|
||||
|
||||
errno = 0;
|
||||
int rval = android_getaddrinfofornetwork(handle, kHostname, NULL, NULL, &res);
|
||||
const int saved_errno = errno;
|
||||
freeaddrinfo(res);
|
||||
|
||||
LOGD("android_getaddrinfofornetwork(%" PRIu64 ", %s) returned rval=%d errno=%d",
|
||||
handle, kHostname, rval, saved_errno);
|
||||
return rval == 0 ? 0 : -saved_errno;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jint Java_android_net_cts_MultinetworkApiTest_runSetprocnetwork(
|
||||
JNIEnv*, jclass, jlong nethandle) {
|
||||
net_handle_t handle = (net_handle_t) nethandle;
|
||||
|
||||
errno = 0;
|
||||
int rval = android_setprocnetwork(handle);
|
||||
const int saved_errno = errno;
|
||||
LOGD("android_setprocnetwork(%" PRIu64 ") returned rval=%d errno=%d",
|
||||
handle, rval, saved_errno);
|
||||
return rval == 0 ? 0 : -saved_errno;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jint Java_android_net_cts_MultinetworkApiTest_runSetsocknetwork(
|
||||
JNIEnv*, jclass, jlong nethandle) {
|
||||
net_handle_t handle = (net_handle_t) nethandle;
|
||||
|
||||
errno = 0;
|
||||
int fd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if (fd < 0) {
|
||||
LOGD("socket() failed, errno=%d", errno);
|
||||
return -errno;
|
||||
}
|
||||
|
||||
errno = 0;
|
||||
int rval = android_setsocknetwork(handle, fd);
|
||||
const int saved_errno = errno;
|
||||
LOGD("android_setprocnetwork(%" PRIu64 ", %d) returned rval=%d errno=%d",
|
||||
handle, fd, rval, saved_errno);
|
||||
close(fd);
|
||||
return rval == 0 ? 0 : -saved_errno;
|
||||
}
|
||||
|
||||
// Use sizeof("x") - 1 because we need a compile-time constant, and strlen("x")
|
||||
// isn't guaranteed to fold to a constant.
|
||||
static const int kSockaddrStrLen = INET6_ADDRSTRLEN + sizeof("[]:65535") - 1;
|
||||
|
||||
void sockaddr_ntop(const struct sockaddr *sa, socklen_t salen, char *dst, const size_t size) {
|
||||
char addrstr[INET6_ADDRSTRLEN];
|
||||
char portstr[sizeof("65535")];
|
||||
char buf[kSockaddrStrLen+1];
|
||||
|
||||
int ret = getnameinfo(sa, salen,
|
||||
addrstr, sizeof(addrstr),
|
||||
portstr, sizeof(portstr),
|
||||
NI_NUMERICHOST | NI_NUMERICSERV);
|
||||
if (ret == 0) {
|
||||
snprintf(buf, sizeof(buf),
|
||||
(sa->sa_family == AF_INET6) ? "[%s]:%s" : "%s:%s",
|
||||
addrstr, portstr);
|
||||
} else {
|
||||
sprintf(buf, "???");
|
||||
}
|
||||
|
||||
strlcpy(dst, buf, size);
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jint Java_android_net_cts_MultinetworkApiTest_runDatagramCheck(
|
||||
JNIEnv*, jclass, jlong nethandle) {
|
||||
const struct addrinfo kHints = {
|
||||
.ai_flags = AI_ADDRCONFIG,
|
||||
.ai_family = AF_UNSPEC,
|
||||
.ai_socktype = SOCK_DGRAM,
|
||||
.ai_protocol = IPPROTO_UDP,
|
||||
};
|
||||
struct addrinfo *res = NULL;
|
||||
net_handle_t handle = (net_handle_t) nethandle;
|
||||
|
||||
static const char kPort[] = "443";
|
||||
int rval = android_getaddrinfofornetwork(handle, kHostname, kPort, &kHints, &res);
|
||||
if (rval != 0) {
|
||||
LOGD("android_getaddrinfofornetwork(%llu, %s) returned rval=%d errno=%d",
|
||||
handle, kHostname, rval, errno);
|
||||
freeaddrinfo(res);
|
||||
return -errno;
|
||||
}
|
||||
|
||||
// Rely upon getaddrinfo sorting the best destination to the front.
|
||||
int fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
|
||||
if (fd < 0) {
|
||||
LOGD("socket(%d, %d, %d) failed, errno=%d",
|
||||
res->ai_family, res->ai_socktype, res->ai_protocol, errno);
|
||||
freeaddrinfo(res);
|
||||
return -errno;
|
||||
}
|
||||
|
||||
rval = android_setsocknetwork(handle, fd);
|
||||
LOGD("android_setprocnetwork(%llu, %d) returned rval=%d errno=%d",
|
||||
handle, fd, rval, errno);
|
||||
if (rval != 0) {
|
||||
close(fd);
|
||||
freeaddrinfo(res);
|
||||
return -errno;
|
||||
}
|
||||
|
||||
char addrstr[kSockaddrStrLen+1];
|
||||
sockaddr_ntop(res->ai_addr, res->ai_addrlen, addrstr, sizeof(addrstr));
|
||||
LOGD("Attempting connect() to %s ...", addrstr);
|
||||
|
||||
rval = connect(fd, res->ai_addr, res->ai_addrlen);
|
||||
if (rval != 0) {
|
||||
close(fd);
|
||||
freeaddrinfo(res);
|
||||
return -errno;
|
||||
}
|
||||
freeaddrinfo(res);
|
||||
|
||||
struct sockaddr_storage src_addr;
|
||||
socklen_t src_addrlen = sizeof(src_addr);
|
||||
if (getsockname(fd, (struct sockaddr *)&src_addr, &src_addrlen) != 0) {
|
||||
close(fd);
|
||||
return -errno;
|
||||
}
|
||||
sockaddr_ntop((const struct sockaddr *)&src_addr, sizeof(src_addr), addrstr, sizeof(addrstr));
|
||||
LOGD("... from %s", addrstr);
|
||||
|
||||
// Don't let reads or writes block indefinitely.
|
||||
const struct timeval timeo = { 2, 0 }; // 2 seconds
|
||||
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo));
|
||||
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeo, sizeof(timeo));
|
||||
|
||||
// For reference see:
|
||||
// https://datatracker.ietf.org/doc/html/draft-ietf-quic-invariants
|
||||
uint8_t quic_packet[1200] = {
|
||||
0xc0, // long header
|
||||
0xaa, 0xda, 0xca, 0xca, // reserved-space version number
|
||||
0x08, // destination connection ID length
|
||||
0, 0, 0, 0, 0, 0, 0, 0, // 64bit connection ID
|
||||
0x00, // source connection ID length
|
||||
};
|
||||
|
||||
arc4random_buf(quic_packet + 6, 8); // random connection ID
|
||||
|
||||
uint8_t response[1500];
|
||||
ssize_t sent, rcvd;
|
||||
static const int MAX_RETRIES = 5;
|
||||
int i, errnum = 0;
|
||||
|
||||
for (i = 0; i < MAX_RETRIES; i++) {
|
||||
sent = send(fd, quic_packet, sizeof(quic_packet), 0);
|
||||
if (sent < (ssize_t)sizeof(quic_packet)) {
|
||||
errnum = errno;
|
||||
LOGD("send(QUIC packet) returned sent=%zd, errno=%d", sent, errnum);
|
||||
close(fd);
|
||||
return -errnum;
|
||||
}
|
||||
|
||||
rcvd = recv(fd, response, sizeof(response), 0);
|
||||
if (rcvd > 0) {
|
||||
break;
|
||||
} else {
|
||||
errnum = errno;
|
||||
LOGD("[%d/%d] recv(QUIC response) returned rcvd=%zd, errno=%d",
|
||||
i + 1, MAX_RETRIES, rcvd, errnum);
|
||||
}
|
||||
}
|
||||
if (rcvd < 15) {
|
||||
LOGD("QUIC UDP %s: sent=%zd but rcvd=%zd, errno=%d", kPort, sent, rcvd, errnum);
|
||||
if (rcvd <= 0) {
|
||||
LOGD("Does this network block UDP port %s?", kPort);
|
||||
}
|
||||
close(fd);
|
||||
return -EPROTO;
|
||||
}
|
||||
|
||||
int conn_id_cmp = memcmp(quic_packet + 6, response + 7, 8);
|
||||
if (conn_id_cmp != 0) {
|
||||
LOGD("sent and received connection IDs do not match");
|
||||
close(fd);
|
||||
return -EPROTO;
|
||||
}
|
||||
|
||||
// TODO: Replace this quick 'n' dirty test with proper QUIC-capable code.
|
||||
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
41
tests/cts/net/native/dns/Android.bp
Normal file
41
tests/cts/net/native/dns/Android.bp
Normal file
@@ -0,0 +1,41 @@
|
||||
cc_defaults {
|
||||
name: "dns_async_defaults",
|
||||
|
||||
cflags: [
|
||||
"-fstack-protector-all",
|
||||
"-g",
|
||||
"-Wall",
|
||||
"-Wextra",
|
||||
"-Werror",
|
||||
"-Wnullable-to-nonnull-conversion",
|
||||
"-Wsign-compare",
|
||||
"-Wthread-safety",
|
||||
"-Wunused-parameter",
|
||||
],
|
||||
srcs: [
|
||||
"NativeDnsAsyncTest.cpp",
|
||||
],
|
||||
shared_libs: [
|
||||
"libandroid",
|
||||
"liblog",
|
||||
"libutils",
|
||||
],
|
||||
}
|
||||
|
||||
cc_test {
|
||||
name: "CtsNativeNetDnsTestCases",
|
||||
defaults: ["dns_async_defaults"],
|
||||
multilib: {
|
||||
lib32: {
|
||||
suffix: "32",
|
||||
},
|
||||
lib64: {
|
||||
suffix: "64",
|
||||
},
|
||||
},
|
||||
test_suites: [
|
||||
"cts",
|
||||
"general-tests",
|
||||
"mts",
|
||||
],
|
||||
}
|
||||
32
tests/cts/net/native/dns/AndroidTest.xml
Normal file
32
tests/cts/net/native/dns/AndroidTest.xml
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright 2018 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.
|
||||
-->
|
||||
<configuration description="Config for CTS Native Network dns test cases">
|
||||
<option name="test-suite-tag" value="cts" />
|
||||
<option name="config-descriptor:metadata" key="component" value="networking" />
|
||||
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
|
||||
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
|
||||
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
|
||||
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
|
||||
<option name="cleanup" value="true" />
|
||||
<option name="push" value="CtsNativeNetDnsTestCases->/data/local/tmp/CtsNativeNetDnsTestCases" />
|
||||
<option name="append-bitness" value="true" />
|
||||
</target_preparer>
|
||||
<test class="com.android.tradefed.testtype.GTest" >
|
||||
<option name="native-test-device-path" value="/data/local/tmp" />
|
||||
<option name="module-name" value="CtsNativeNetDnsTestCases" />
|
||||
<option name="runtime-hint" value="1m" />
|
||||
</test>
|
||||
</configuration>
|
||||
257
tests/cts/net/native/dns/NativeDnsAsyncTest.cpp
Normal file
257
tests/cts/net/native/dns/NativeDnsAsyncTest.cpp
Normal file
@@ -0,0 +1,257 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.
|
||||
*/
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <arpa/nameser.h>
|
||||
#include <error.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <inttypes.h>
|
||||
#include <netinet/in.h>
|
||||
#include <poll.h> /* poll */
|
||||
#include <resolv.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include <android/multinetwork.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace {
|
||||
constexpr int MAXPACKET = 8 * 1024;
|
||||
constexpr int PTON_MAX = 16;
|
||||
constexpr int TIMEOUT_MS = 10000;
|
||||
|
||||
int getAsyncResponse(int fd, int timeoutMs, int* rcode, uint8_t* buf, size_t bufLen) {
|
||||
struct pollfd wait_fd[1];
|
||||
wait_fd[0].fd = fd;
|
||||
wait_fd[0].events = POLLIN;
|
||||
short revents;
|
||||
int ret;
|
||||
ret = poll(wait_fd, 1, timeoutMs);
|
||||
revents = wait_fd[0].revents;
|
||||
if (revents & POLLIN) {
|
||||
int n = android_res_nresult(fd, rcode, buf, bufLen);
|
||||
// Verify that android_res_nresult() closed the fd
|
||||
char dummy;
|
||||
EXPECT_EQ(-1, read(fd, &dummy, sizeof dummy));
|
||||
EXPECT_EQ(EBADF, errno);
|
||||
return n;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::vector<std::string> extractIpAddressAnswers(uint8_t* buf, size_t bufLen, int ipType) {
|
||||
ns_msg handle;
|
||||
if (ns_initparse((const uint8_t*) buf, bufLen, &handle) < 0) {
|
||||
return {};
|
||||
}
|
||||
const int ancount = ns_msg_count(handle, ns_s_an);
|
||||
ns_rr rr;
|
||||
std::vector<std::string> answers;
|
||||
for (int i = 0; i < ancount; i++) {
|
||||
if (ns_parserr(&handle, ns_s_an, i, &rr) < 0) {
|
||||
continue;
|
||||
}
|
||||
const uint8_t* rdata = ns_rr_rdata(rr);
|
||||
char buffer[INET6_ADDRSTRLEN];
|
||||
if (inet_ntop(ipType, (const char*) rdata, buffer, sizeof(buffer))) {
|
||||
answers.push_back(buffer);
|
||||
}
|
||||
}
|
||||
return answers;
|
||||
}
|
||||
|
||||
void expectAnswersValid(int fd, int ipType, int expectedRcode) {
|
||||
int rcode = -1;
|
||||
uint8_t buf[MAXPACKET] = {};
|
||||
int res = getAsyncResponse(fd, TIMEOUT_MS, &rcode, buf, MAXPACKET);
|
||||
EXPECT_GE(res, 0);
|
||||
EXPECT_EQ(rcode, expectedRcode);
|
||||
|
||||
if (expectedRcode == ns_r_noerror) {
|
||||
auto answers = extractIpAddressAnswers(buf, res, ipType);
|
||||
EXPECT_GE(answers.size(), 0U);
|
||||
for (auto &answer : answers) {
|
||||
char pton[PTON_MAX];
|
||||
EXPECT_EQ(1, inet_pton(ipType, answer.c_str(), pton));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void expectAnswersNotValid(int fd, int expectedErrno) {
|
||||
int rcode = -1;
|
||||
uint8_t buf[MAXPACKET] = {};
|
||||
int res = getAsyncResponse(fd, TIMEOUT_MS, &rcode, buf, MAXPACKET);
|
||||
EXPECT_EQ(expectedErrno, res);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST (NativeDnsAsyncTest, Async_Query) {
|
||||
// V4
|
||||
int fd1 = android_res_nquery(
|
||||
NETWORK_UNSPECIFIED, "www.google.com", ns_c_in, ns_t_a, 0);
|
||||
EXPECT_GE(fd1, 0);
|
||||
int fd2 = android_res_nquery(
|
||||
NETWORK_UNSPECIFIED, "www.youtube.com", ns_c_in, ns_t_a, 0);
|
||||
EXPECT_GE(fd2, 0);
|
||||
expectAnswersValid(fd2, AF_INET, ns_r_noerror);
|
||||
expectAnswersValid(fd1, AF_INET, ns_r_noerror);
|
||||
|
||||
// V6
|
||||
fd1 = android_res_nquery(
|
||||
NETWORK_UNSPECIFIED, "www.google.com", ns_c_in, ns_t_aaaa, 0);
|
||||
EXPECT_GE(fd1, 0);
|
||||
fd2 = android_res_nquery(
|
||||
NETWORK_UNSPECIFIED, "www.youtube.com", ns_c_in, ns_t_aaaa, 0);
|
||||
EXPECT_GE(fd2, 0);
|
||||
expectAnswersValid(fd2, AF_INET6, ns_r_noerror);
|
||||
expectAnswersValid(fd1, AF_INET6, ns_r_noerror);
|
||||
}
|
||||
|
||||
TEST (NativeDnsAsyncTest, Async_Send) {
|
||||
// V4
|
||||
uint8_t buf1[MAXPACKET] = {};
|
||||
int len1 = res_mkquery(ns_o_query, "www.googleapis.com",
|
||||
ns_c_in, ns_t_a, nullptr, 0, nullptr, buf1, sizeof(buf1));
|
||||
EXPECT_GT(len1, 0);
|
||||
|
||||
uint8_t buf2[MAXPACKET] = {};
|
||||
int len2 = res_mkquery(ns_o_query, "play.googleapis.com",
|
||||
ns_c_in, ns_t_a, nullptr, 0, nullptr, buf2, sizeof(buf2));
|
||||
EXPECT_GT(len2, 0);
|
||||
|
||||
int fd1 = android_res_nsend(NETWORK_UNSPECIFIED, buf1, len1, 0);
|
||||
EXPECT_GE(fd1, 0);
|
||||
int fd2 = android_res_nsend(NETWORK_UNSPECIFIED, buf2, len2, 0);
|
||||
EXPECT_GE(fd2, 0);
|
||||
|
||||
expectAnswersValid(fd2, AF_INET, ns_r_noerror);
|
||||
expectAnswersValid(fd1, AF_INET, ns_r_noerror);
|
||||
|
||||
// V6
|
||||
memset(buf1, 0, sizeof(buf1));
|
||||
memset(buf2, 0, sizeof(buf2));
|
||||
len1 = res_mkquery(ns_o_query, "www.googleapis.com",
|
||||
ns_c_in, ns_t_aaaa, nullptr, 0, nullptr, buf1, sizeof(buf1));
|
||||
EXPECT_GT(len1, 0);
|
||||
len2 = res_mkquery(ns_o_query, "play.googleapis.com",
|
||||
ns_c_in, ns_t_aaaa, nullptr, 0, nullptr, buf2, sizeof(buf2));
|
||||
EXPECT_GT(len2, 0);
|
||||
|
||||
fd1 = android_res_nsend(NETWORK_UNSPECIFIED, buf1, len1, 0);
|
||||
EXPECT_GE(fd1, 0);
|
||||
fd2 = android_res_nsend(NETWORK_UNSPECIFIED, buf2, len2, 0);
|
||||
EXPECT_GE(fd2, 0);
|
||||
|
||||
expectAnswersValid(fd2, AF_INET6, ns_r_noerror);
|
||||
expectAnswersValid(fd1, AF_INET6, ns_r_noerror);
|
||||
}
|
||||
|
||||
TEST (NativeDnsAsyncTest, Async_NXDOMAIN) {
|
||||
uint8_t buf[MAXPACKET] = {};
|
||||
int len = res_mkquery(ns_o_query, "test1-nx.metric.gstatic.com",
|
||||
ns_c_in, ns_t_a, nullptr, 0, nullptr, buf, sizeof(buf));
|
||||
EXPECT_GT(len, 0);
|
||||
int fd1 = android_res_nsend(NETWORK_UNSPECIFIED, buf, len, ANDROID_RESOLV_NO_CACHE_LOOKUP);
|
||||
EXPECT_GE(fd1, 0);
|
||||
|
||||
len = res_mkquery(ns_o_query, "test2-nx.metric.gstatic.com",
|
||||
ns_c_in, ns_t_a, nullptr, 0, nullptr, buf, sizeof(buf));
|
||||
EXPECT_GT(len, 0);
|
||||
int fd2 = android_res_nsend(NETWORK_UNSPECIFIED, buf, len, ANDROID_RESOLV_NO_CACHE_LOOKUP);
|
||||
EXPECT_GE(fd2, 0);
|
||||
|
||||
expectAnswersValid(fd2, AF_INET, ns_r_nxdomain);
|
||||
expectAnswersValid(fd1, AF_INET, ns_r_nxdomain);
|
||||
|
||||
fd1 = android_res_nquery(
|
||||
NETWORK_UNSPECIFIED, "test3-nx.metric.gstatic.com",
|
||||
ns_c_in, ns_t_aaaa, ANDROID_RESOLV_NO_CACHE_LOOKUP);
|
||||
EXPECT_GE(fd1, 0);
|
||||
fd2 = android_res_nquery(
|
||||
NETWORK_UNSPECIFIED, "test4-nx.metric.gstatic.com",
|
||||
ns_c_in, ns_t_aaaa, ANDROID_RESOLV_NO_CACHE_LOOKUP);
|
||||
EXPECT_GE(fd2, 0);
|
||||
expectAnswersValid(fd2, AF_INET6, ns_r_nxdomain);
|
||||
expectAnswersValid(fd1, AF_INET6, ns_r_nxdomain);
|
||||
}
|
||||
|
||||
TEST (NativeDnsAsyncTest, Async_Cancel) {
|
||||
int fd = android_res_nquery(
|
||||
NETWORK_UNSPECIFIED, "www.google.com", ns_c_in, ns_t_a, 0);
|
||||
errno = 0;
|
||||
android_res_cancel(fd);
|
||||
int err = errno;
|
||||
EXPECT_EQ(err, 0);
|
||||
// DO NOT call cancel or result with the same fd more than once,
|
||||
// otherwise it will hit fdsan double-close fd.
|
||||
}
|
||||
|
||||
TEST (NativeDnsAsyncTest, Async_Query_MALFORMED) {
|
||||
// Empty string to create BLOB and query, we will get empty result and rcode = 0
|
||||
// on DNSTLS.
|
||||
int fd = android_res_nquery(
|
||||
NETWORK_UNSPECIFIED, "", ns_c_in, ns_t_a, 0);
|
||||
EXPECT_GE(fd, 0);
|
||||
expectAnswersValid(fd, AF_INET, ns_r_noerror);
|
||||
|
||||
std::string exceedingLabelQuery = "www." + std::string(70, 'g') + ".com";
|
||||
std::string exceedingDomainQuery = "www." + std::string(255, 'g') + ".com";
|
||||
|
||||
fd = android_res_nquery(NETWORK_UNSPECIFIED,
|
||||
exceedingLabelQuery.c_str(), ns_c_in, ns_t_a, 0);
|
||||
EXPECT_EQ(-EMSGSIZE, fd);
|
||||
fd = android_res_nquery(NETWORK_UNSPECIFIED,
|
||||
exceedingDomainQuery.c_str(), ns_c_in, ns_t_a, 0);
|
||||
EXPECT_EQ(-EMSGSIZE, fd);
|
||||
}
|
||||
|
||||
TEST (NativeDnsAsyncTest, Async_Send_MALFORMED) {
|
||||
uint8_t buf[10] = {};
|
||||
// empty BLOB
|
||||
int fd = android_res_nsend(NETWORK_UNSPECIFIED, buf, 10, 0);
|
||||
EXPECT_GE(fd, 0);
|
||||
expectAnswersNotValid(fd, -EINVAL);
|
||||
|
||||
std::vector<uint8_t> largeBuf(2 * MAXPACKET, 0);
|
||||
// A buffer larger than 8KB
|
||||
fd = android_res_nsend(
|
||||
NETWORK_UNSPECIFIED, largeBuf.data(), largeBuf.size(), 0);
|
||||
EXPECT_EQ(-EMSGSIZE, fd);
|
||||
|
||||
// 5000 bytes filled with 0. This returns EMSGSIZE because FrameworkListener limits the size of
|
||||
// commands to 4096 bytes.
|
||||
fd = android_res_nsend(NETWORK_UNSPECIFIED, largeBuf.data(), 5000, 0);
|
||||
EXPECT_EQ(-EMSGSIZE, fd);
|
||||
|
||||
// 500 bytes filled with 0
|
||||
fd = android_res_nsend(NETWORK_UNSPECIFIED, largeBuf.data(), 500, 0);
|
||||
EXPECT_GE(fd, 0);
|
||||
expectAnswersNotValid(fd, -EINVAL);
|
||||
|
||||
// 5000 bytes filled with 0xFF
|
||||
std::vector<uint8_t> ffBuf(5000, 0xFF);
|
||||
fd = android_res_nsend(
|
||||
NETWORK_UNSPECIFIED, ffBuf.data(), ffBuf.size(), 0);
|
||||
EXPECT_EQ(-EMSGSIZE, fd);
|
||||
|
||||
// 500 bytes filled with 0xFF
|
||||
fd = android_res_nsend(NETWORK_UNSPECIFIED, ffBuf.data(), 500, 0);
|
||||
EXPECT_GE(fd, 0);
|
||||
expectAnswersNotValid(fd, -EINVAL);
|
||||
}
|
||||
53
tests/cts/net/native/qtaguid/Android.bp
Normal file
53
tests/cts/net/native/qtaguid/Android.bp
Normal file
@@ -0,0 +1,53 @@
|
||||
// Copyright (C) 2017 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.
|
||||
|
||||
// Build the unit tests.
|
||||
|
||||
cc_test {
|
||||
name: "CtsNativeNetTestCases",
|
||||
|
||||
compile_multilib: "both",
|
||||
multilib: {
|
||||
lib32: {
|
||||
suffix: "32",
|
||||
},
|
||||
lib64: {
|
||||
suffix: "64",
|
||||
},
|
||||
},
|
||||
|
||||
srcs: ["src/NativeQtaguidTest.cpp"],
|
||||
|
||||
shared_libs: [
|
||||
"libutils",
|
||||
"liblog",
|
||||
],
|
||||
|
||||
static_libs: [
|
||||
"libgtest",
|
||||
"libqtaguid",
|
||||
],
|
||||
|
||||
// Tag this module as a cts test artifact
|
||||
test_suites: [
|
||||
"cts",
|
||||
"general-tests",
|
||||
],
|
||||
|
||||
cflags: [
|
||||
"-Werror",
|
||||
"-Wall",
|
||||
],
|
||||
|
||||
}
|
||||
32
tests/cts/net/native/qtaguid/AndroidTest.xml
Normal file
32
tests/cts/net/native/qtaguid/AndroidTest.xml
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright 2017 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.
|
||||
-->
|
||||
<configuration description="Config for CTS Native Network xt_qtaguid test cases">
|
||||
<option name="test-suite-tag" value="cts" />
|
||||
<option name="config-descriptor:metadata" key="component" value="networking" />
|
||||
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
|
||||
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
|
||||
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
|
||||
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
|
||||
<option name="cleanup" value="true" />
|
||||
<option name="push" value="CtsNativeNetTestCases->/data/local/tmp/CtsNativeNetTestCases" />
|
||||
<option name="append-bitness" value="true" />
|
||||
</target_preparer>
|
||||
<test class="com.android.tradefed.testtype.GTest" >
|
||||
<option name="native-test-device-path" value="/data/local/tmp" />
|
||||
<option name="module-name" value="CtsNativeNetTestCases" />
|
||||
<option name="runtime-hint" value="1m" />
|
||||
</test>
|
||||
</configuration>
|
||||
130
tests/cts/net/native/qtaguid/src/NativeQtaguidTest.cpp
Normal file
130
tests/cts/net/native/qtaguid/src/NativeQtaguidTest.cpp
Normal file
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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.
|
||||
*/
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <error.h>
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <qtaguid/qtaguid.h>
|
||||
|
||||
int canAccessQtaguidFile() {
|
||||
int fd = open("/proc/net/xt_qtaguid/ctrl", O_RDONLY | O_CLOEXEC);
|
||||
close(fd);
|
||||
return fd != -1;
|
||||
}
|
||||
|
||||
#define SKIP_IF_QTAGUID_NOT_SUPPORTED() \
|
||||
do { \
|
||||
int res = canAccessQtaguidFile(); \
|
||||
ASSERT_LE(0, res); \
|
||||
if (!res) { \
|
||||
GTEST_LOG_(INFO) << "This test is skipped since kernel may not have the module\n"; \
|
||||
return; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
int getCtrlSkInfo(int tag, uid_t uid, uint64_t* sk_addr, int* ref_cnt) {
|
||||
FILE *fp;
|
||||
fp = fopen("/proc/net/xt_qtaguid/ctrl", "r");
|
||||
if (!fp)
|
||||
return -ENOENT;
|
||||
uint64_t full_tag = (uint64_t)tag << 32 | uid;
|
||||
char pattern[40];
|
||||
snprintf(pattern, sizeof(pattern), " tag=0x%" PRIx64 " (uid=%" PRIu32 ")", full_tag, uid);
|
||||
|
||||
size_t len;
|
||||
char *line_buffer = NULL;
|
||||
while(getline(&line_buffer, &len, fp) != -1) {
|
||||
if (strstr(line_buffer, pattern) == NULL)
|
||||
continue;
|
||||
int res;
|
||||
pid_t dummy_pid;
|
||||
uint64_t k_tag;
|
||||
uint32_t k_uid;
|
||||
const int TOTAL_PARAM = 5;
|
||||
res = sscanf(line_buffer, "sock=%" PRIx64 " tag=0x%" PRIx64 " (uid=%" PRIu32 ") "
|
||||
"pid=%u f_count=%u", sk_addr, &k_tag, &k_uid,
|
||||
&dummy_pid, ref_cnt);
|
||||
if (!(res == TOTAL_PARAM && k_tag == full_tag && k_uid == uid))
|
||||
return -EINVAL;
|
||||
free(line_buffer);
|
||||
return 0;
|
||||
}
|
||||
free(line_buffer);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
void checkNoSocketPointerLeaks(int family) {
|
||||
int sockfd = socket(family, SOCK_STREAM, 0);
|
||||
uid_t uid = getuid();
|
||||
int tag = arc4random();
|
||||
int ref_cnt;
|
||||
uint64_t sk_addr;
|
||||
uint64_t expect_addr = 0;
|
||||
|
||||
EXPECT_EQ(0, legacy_tagSocket(sockfd, tag, uid));
|
||||
EXPECT_EQ(0, getCtrlSkInfo(tag, uid, &sk_addr, &ref_cnt));
|
||||
EXPECT_EQ(expect_addr, sk_addr);
|
||||
close(sockfd);
|
||||
EXPECT_EQ(-ENOENT, getCtrlSkInfo(tag, uid, &sk_addr, &ref_cnt));
|
||||
}
|
||||
|
||||
TEST (NativeQtaguidTest, close_socket_without_untag) {
|
||||
SKIP_IF_QTAGUID_NOT_SUPPORTED();
|
||||
|
||||
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
uid_t uid = getuid();
|
||||
int tag = arc4random();
|
||||
int ref_cnt;
|
||||
uint64_t dummy_sk;
|
||||
EXPECT_EQ(0, legacy_tagSocket(sockfd, tag, uid));
|
||||
EXPECT_EQ(0, getCtrlSkInfo(tag, uid, &dummy_sk, &ref_cnt));
|
||||
EXPECT_EQ(2, ref_cnt);
|
||||
close(sockfd);
|
||||
EXPECT_EQ(-ENOENT, getCtrlSkInfo(tag, uid, &dummy_sk, &ref_cnt));
|
||||
}
|
||||
|
||||
TEST (NativeQtaguidTest, close_socket_without_untag_ipv6) {
|
||||
SKIP_IF_QTAGUID_NOT_SUPPORTED();
|
||||
|
||||
int sockfd = socket(AF_INET6, SOCK_STREAM, 0);
|
||||
uid_t uid = getuid();
|
||||
int tag = arc4random();
|
||||
int ref_cnt;
|
||||
uint64_t dummy_sk;
|
||||
EXPECT_EQ(0, legacy_tagSocket(sockfd, tag, uid));
|
||||
EXPECT_EQ(0, getCtrlSkInfo(tag, uid, &dummy_sk, &ref_cnt));
|
||||
EXPECT_EQ(2, ref_cnt);
|
||||
close(sockfd);
|
||||
EXPECT_EQ(-ENOENT, getCtrlSkInfo(tag, uid, &dummy_sk, &ref_cnt));
|
||||
}
|
||||
|
||||
TEST (NativeQtaguidTest, no_socket_addr_leak) {
|
||||
SKIP_IF_QTAGUID_NOT_SUPPORTED();
|
||||
|
||||
checkNoSocketPointerLeaks(AF_INET);
|
||||
checkNoSocketPointerLeaks(AF_INET6);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
86
tests/cts/net/src/android/net/cts/AirplaneModeTest.java
Normal file
86
tests/cts/net/src/android/net/cts/AirplaneModeTest.java
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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 android.net.cts;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.platform.test.annotations.AppModeFull;
|
||||
import android.provider.Settings;
|
||||
import android.test.AndroidTestCase;
|
||||
import android.util.Log;
|
||||
|
||||
import java.lang.Thread;
|
||||
|
||||
@AppModeFull(reason = "WRITE_SECURE_SETTINGS permission can't be granted to instant apps")
|
||||
public class AirplaneModeTest extends AndroidTestCase {
|
||||
private static final String TAG = "AirplaneModeTest";
|
||||
private static final String FEATURE_BLUETOOTH = "android.hardware.bluetooth";
|
||||
private static final String FEATURE_WIFI = "android.hardware.wifi";
|
||||
private static final int TIMEOUT_MS = 10 * 1000;
|
||||
private boolean mHasFeature;
|
||||
private Context mContext;
|
||||
private ContentResolver resolver;
|
||||
|
||||
public void setup() {
|
||||
mContext= getContext();
|
||||
resolver = mContext.getContentResolver();
|
||||
mHasFeature = (mContext.getPackageManager().hasSystemFeature(FEATURE_BLUETOOTH)
|
||||
|| mContext.getPackageManager().hasSystemFeature(FEATURE_WIFI));
|
||||
}
|
||||
|
||||
public void testAirplaneMode() {
|
||||
setup();
|
||||
if (!mHasFeature) {
|
||||
Log.i(TAG, "The device doesn't support network bluetooth or wifi feature");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int testCount = 0; testCount < 2; testCount++) {
|
||||
if (!doOneTest()) {
|
||||
fail("Airplane mode failed to change in " + TIMEOUT_MS + "msec");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean doOneTest() {
|
||||
boolean airplaneModeOn = isAirplaneModeOn();
|
||||
setAirplaneModeOn(!airplaneModeOn);
|
||||
|
||||
try {
|
||||
Thread.sleep(TIMEOUT_MS);
|
||||
} catch (InterruptedException e) {
|
||||
Log.e(TAG, "Sleep time interrupted.", e);
|
||||
}
|
||||
|
||||
if (airplaneModeOn == isAirplaneModeOn()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void setAirplaneModeOn(boolean enabling) {
|
||||
// Change the system setting for airplane mode
|
||||
Settings.Global.putInt(resolver, Settings.Global.AIRPLANE_MODE_ON, enabling ? 1 : 0);
|
||||
}
|
||||
|
||||
private boolean isAirplaneModeOn() {
|
||||
// Read the system setting for airplane mode
|
||||
return Settings.Global.getInt(mContext.getContentResolver(),
|
||||
Settings.Global.AIRPLANE_MODE_ON, 0) != 0;
|
||||
}
|
||||
}
|
||||
194
tests/cts/net/src/android/net/cts/CaptivePortalTest.kt
Normal file
194
tests/cts/net/src/android/net/cts/CaptivePortalTest.kt
Normal file
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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 android.net.cts
|
||||
|
||||
import android.Manifest.permission.CONNECTIVITY_INTERNAL
|
||||
import android.Manifest.permission.NETWORK_SETTINGS
|
||||
import android.Manifest.permission.READ_DEVICE_CONFIG
|
||||
import android.content.pm.PackageManager.FEATURE_TELEPHONY
|
||||
import android.content.pm.PackageManager.FEATURE_WIFI
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.ConnectivityManager.NetworkCallback
|
||||
import android.net.Network
|
||||
import android.net.NetworkCapabilities
|
||||
import android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL
|
||||
import android.net.NetworkCapabilities.TRANSPORT_WIFI
|
||||
import android.net.NetworkRequest
|
||||
import android.net.Uri
|
||||
import android.net.cts.NetworkValidationTestUtil.clearValidationTestUrlsDeviceConfig
|
||||
import android.net.cts.NetworkValidationTestUtil.setHttpUrlDeviceConfig
|
||||
import android.net.cts.NetworkValidationTestUtil.setHttpsUrlDeviceConfig
|
||||
import android.net.cts.NetworkValidationTestUtil.setUrlExpirationDeviceConfig
|
||||
import android.net.cts.util.CtsNetUtils
|
||||
import android.net.util.NetworkStackUtils.TEST_CAPTIVE_PORTAL_HTTPS_URL
|
||||
import android.net.util.NetworkStackUtils.TEST_CAPTIVE_PORTAL_HTTP_URL
|
||||
import android.net.wifi.WifiManager
|
||||
import android.os.Build
|
||||
import android.platform.test.annotations.AppModeFull
|
||||
import android.provider.DeviceConfig
|
||||
import android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY
|
||||
import android.text.TextUtils
|
||||
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
|
||||
import androidx.test.runner.AndroidJUnit4
|
||||
import com.android.testutils.TestHttpServer
|
||||
import com.android.testutils.TestHttpServer.Request
|
||||
import com.android.testutils.isDevSdkInRange
|
||||
import com.android.testutils.runAsShell
|
||||
import fi.iki.elonen.NanoHTTPD.Response.Status
|
||||
import junit.framework.AssertionFailedError
|
||||
import org.junit.After
|
||||
import org.junit.Assume.assumeTrue
|
||||
import org.junit.Before
|
||||
import org.junit.runner.RunWith
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.TimeoutException
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertNotEquals
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
private const val TEST_HTTPS_URL_PATH = "/https_path"
|
||||
private const val TEST_HTTP_URL_PATH = "/http_path"
|
||||
private const val TEST_PORTAL_URL_PATH = "/portal_path"
|
||||
|
||||
private const val LOCALHOST_HOSTNAME = "localhost"
|
||||
|
||||
// Re-connecting to the AP, obtaining an IP address, revalidating can take a long time
|
||||
private const val WIFI_CONNECT_TIMEOUT_MS = 120_000L
|
||||
private const val TEST_TIMEOUT_MS = 10_000L
|
||||
|
||||
private fun <T> CompletableFuture<T>.assertGet(timeoutMs: Long, message: String): T {
|
||||
try {
|
||||
return get(timeoutMs, TimeUnit.MILLISECONDS)
|
||||
} catch (e: TimeoutException) {
|
||||
throw AssertionFailedError(message)
|
||||
}
|
||||
}
|
||||
|
||||
@AppModeFull(reason = "WRITE_DEVICE_CONFIG permission can't be granted to instant apps")
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class CaptivePortalTest {
|
||||
private val context: android.content.Context by lazy { getInstrumentation().context }
|
||||
private val wm by lazy { context.getSystemService(WifiManager::class.java) }
|
||||
private val cm by lazy { context.getSystemService(ConnectivityManager::class.java) }
|
||||
private val pm by lazy { context.packageManager }
|
||||
private val utils by lazy { CtsNetUtils(context) }
|
||||
|
||||
private val server = TestHttpServer("localhost")
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
runAsShell(READ_DEVICE_CONFIG) {
|
||||
// Verify that the test URLs are not normally set on the device, but do not fail if the
|
||||
// test URLs are set to what this test uses (URLs on localhost), in case the test was
|
||||
// interrupted manually and rerun.
|
||||
assertEmptyOrLocalhostUrl(TEST_CAPTIVE_PORTAL_HTTPS_URL)
|
||||
assertEmptyOrLocalhostUrl(TEST_CAPTIVE_PORTAL_HTTP_URL)
|
||||
}
|
||||
clearValidationTestUrlsDeviceConfig()
|
||||
server.start()
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
clearValidationTestUrlsDeviceConfig()
|
||||
if (pm.hasSystemFeature(FEATURE_WIFI)) {
|
||||
reconnectWifi()
|
||||
}
|
||||
server.stop()
|
||||
}
|
||||
|
||||
private fun assertEmptyOrLocalhostUrl(urlKey: String) {
|
||||
val url = DeviceConfig.getProperty(NAMESPACE_CONNECTIVITY, urlKey)
|
||||
assertTrue(TextUtils.isEmpty(url) || LOCALHOST_HOSTNAME == Uri.parse(url).host,
|
||||
"$urlKey must not be set in production scenarios (current value: $url)")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCaptivePortalIsNotDefaultNetwork() {
|
||||
assumeTrue(pm.hasSystemFeature(FEATURE_TELEPHONY))
|
||||
assumeTrue(pm.hasSystemFeature(FEATURE_WIFI))
|
||||
utils.ensureWifiConnected()
|
||||
utils.connectToCell()
|
||||
|
||||
// Have network validation use a local server that serves a HTTPS error / HTTP redirect
|
||||
server.addResponse(Request(TEST_PORTAL_URL_PATH), Status.OK,
|
||||
content = "Test captive portal content")
|
||||
server.addResponse(Request(TEST_HTTPS_URL_PATH), Status.INTERNAL_ERROR)
|
||||
server.addResponse(Request(TEST_HTTP_URL_PATH), Status.REDIRECT,
|
||||
locationHeader = makeUrl(TEST_PORTAL_URL_PATH))
|
||||
setHttpsUrlDeviceConfig(makeUrl(TEST_HTTPS_URL_PATH))
|
||||
setHttpUrlDeviceConfig(makeUrl(TEST_HTTP_URL_PATH))
|
||||
// URL expiration needs to be in the next 10 minutes
|
||||
setUrlExpirationDeviceConfig(System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(9))
|
||||
|
||||
// Wait for a captive portal to be detected on the network
|
||||
val wifiNetworkFuture = CompletableFuture<Network>()
|
||||
val wifiCb = object : NetworkCallback() {
|
||||
override fun onCapabilitiesChanged(
|
||||
network: Network,
|
||||
nc: NetworkCapabilities
|
||||
) {
|
||||
if (nc.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL)) {
|
||||
wifiNetworkFuture.complete(network)
|
||||
}
|
||||
}
|
||||
}
|
||||
cm.requestNetwork(NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI).build(), wifiCb)
|
||||
|
||||
try {
|
||||
reconnectWifi()
|
||||
val network = wifiNetworkFuture.assertGet(WIFI_CONNECT_TIMEOUT_MS,
|
||||
"Captive portal not detected after ${WIFI_CONNECT_TIMEOUT_MS}ms")
|
||||
|
||||
val wifiDefaultMessage = "Wifi should not be the default network when a captive " +
|
||||
"portal was detected and another network (mobile data) can provide internet " +
|
||||
"access."
|
||||
assertNotEquals(network, cm.activeNetwork, wifiDefaultMessage)
|
||||
|
||||
val startPortalAppPermission =
|
||||
if (isDevSdkInRange(0, Build.VERSION_CODES.Q)) CONNECTIVITY_INTERNAL
|
||||
else NETWORK_SETTINGS
|
||||
runAsShell(startPortalAppPermission) { cm.startCaptivePortalApp(network) }
|
||||
|
||||
// Expect the portal content to be fetched at some point after detecting the portal.
|
||||
// Some implementations may fetch the URL before startCaptivePortalApp is called.
|
||||
assertNotNull(server.requestsRecord.poll(TEST_TIMEOUT_MS, pos = 0) {
|
||||
it.path == TEST_PORTAL_URL_PATH
|
||||
}, "The captive portal login page was still not fetched ${TEST_TIMEOUT_MS}ms " +
|
||||
"after startCaptivePortalApp.")
|
||||
|
||||
assertNotEquals(network, cm.activeNetwork, wifiDefaultMessage)
|
||||
} finally {
|
||||
cm.unregisterNetworkCallback(wifiCb)
|
||||
server.stop()
|
||||
// disconnectFromCell should be called after connectToCell
|
||||
utils.disconnectFromCell()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a URL string that, when fetched, will hit the test server with the given URL [path].
|
||||
*/
|
||||
private fun makeUrl(path: String) = "http://localhost:${server.listeningPort}" + path
|
||||
|
||||
private fun reconnectWifi() {
|
||||
utils.ensureWifiDisconnected(null /* wifiNetworkToCheck */)
|
||||
utils.ensureWifiConnected()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,576 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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 android.net.cts;
|
||||
|
||||
import static android.content.pm.PackageManager.FEATURE_TELEPHONY;
|
||||
import static android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback;
|
||||
import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport;
|
||||
import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_PROBES_ATTEMPTED_BITMASK;
|
||||
import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_PROBES_SUCCEEDED_BITMASK;
|
||||
import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_VALIDATION_RESULT;
|
||||
import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.NETWORK_VALIDATION_RESULT_VALID;
|
||||
import static android.net.ConnectivityDiagnosticsManager.DataStallReport;
|
||||
import static android.net.ConnectivityDiagnosticsManager.DataStallReport.DETECTION_METHOD_DNS_EVENTS;
|
||||
import static android.net.ConnectivityDiagnosticsManager.DataStallReport.DETECTION_METHOD_TCP_METRICS;
|
||||
import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_DNS_CONSECUTIVE_TIMEOUTS;
|
||||
import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_TCP_METRICS_COLLECTION_PERIOD_MILLIS;
|
||||
import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_TCP_PACKET_FAIL_RATE;
|
||||
import static android.net.ConnectivityDiagnosticsManager.persistableBundleEquals;
|
||||
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
|
||||
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
|
||||
import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
|
||||
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
|
||||
import static android.net.NetworkCapabilities.TRANSPORT_TEST;
|
||||
import static android.net.cts.util.CtsNetUtils.TestNetworkCallback;
|
||||
|
||||
import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity;
|
||||
import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.ConnectivityDiagnosticsManager;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.LinkAddress;
|
||||
import android.net.Network;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.NetworkRequest;
|
||||
import android.net.TestNetworkInterface;
|
||||
import android.net.TestNetworkManager;
|
||||
import android.os.Binder;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.PersistableBundle;
|
||||
import android.os.Process;
|
||||
import android.platform.test.annotations.AppModeFull;
|
||||
import android.telephony.CarrierConfigManager;
|
||||
import android.telephony.SubscriptionManager;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
|
||||
import com.android.internal.telephony.uicc.IccUtils;
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
import com.android.net.module.util.ArrayTrackRecord;
|
||||
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
|
||||
import com.android.testutils.DevSdkIgnoreRunner;
|
||||
import com.android.testutils.SkipPresubmit;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@RunWith(DevSdkIgnoreRunner.class)
|
||||
@IgnoreUpTo(Build.VERSION_CODES.Q) // ConnectivityDiagnosticsManager did not exist in Q
|
||||
@AppModeFull(reason = "CHANGE_NETWORK_STATE, MANAGE_TEST_NETWORKS not grantable to instant apps")
|
||||
public class ConnectivityDiagnosticsManagerTest {
|
||||
private static final int CALLBACK_TIMEOUT_MILLIS = 5000;
|
||||
private static final int NO_CALLBACK_INVOKED_TIMEOUT = 500;
|
||||
private static final long TIMESTAMP = 123456789L;
|
||||
private static final int DNS_CONSECUTIVE_TIMEOUTS = 5;
|
||||
private static final int COLLECTION_PERIOD_MILLIS = 5000;
|
||||
private static final int FAIL_RATE_PERCENTAGE = 100;
|
||||
private static final int UNKNOWN_DETECTION_METHOD = 4;
|
||||
private static final int FILTERED_UNKNOWN_DETECTION_METHOD = 0;
|
||||
private static final int CARRIER_CONFIG_CHANGED_BROADCAST_TIMEOUT = 5000;
|
||||
private static final int DELAY_FOR_ADMIN_UIDS_MILLIS = 2000;
|
||||
|
||||
private static final Executor INLINE_EXECUTOR = x -> x.run();
|
||||
|
||||
private static final NetworkRequest TEST_NETWORK_REQUEST =
|
||||
new NetworkRequest.Builder()
|
||||
.addTransportType(TRANSPORT_TEST)
|
||||
.removeCapability(NET_CAPABILITY_TRUSTED)
|
||||
.removeCapability(NET_CAPABILITY_NOT_VPN)
|
||||
.build();
|
||||
|
||||
private static final String SHA_256 = "SHA-256";
|
||||
|
||||
private static final NetworkRequest CELLULAR_NETWORK_REQUEST =
|
||||
new NetworkRequest.Builder()
|
||||
.addTransportType(TRANSPORT_CELLULAR)
|
||||
.addCapability(NET_CAPABILITY_INTERNET)
|
||||
.build();
|
||||
|
||||
private static final IBinder BINDER = new Binder();
|
||||
|
||||
private Context mContext;
|
||||
private ConnectivityManager mConnectivityManager;
|
||||
private ConnectivityDiagnosticsManager mCdm;
|
||||
private CarrierConfigManager mCarrierConfigManager;
|
||||
private PackageManager mPackageManager;
|
||||
private TelephonyManager mTelephonyManager;
|
||||
|
||||
// Callback used to keep TestNetworks up when there are no other outstanding NetworkRequests
|
||||
// for it.
|
||||
private TestNetworkCallback mTestNetworkCallback;
|
||||
private Network mTestNetwork;
|
||||
private ParcelFileDescriptor mTestNetworkFD;
|
||||
|
||||
private List<TestConnectivityDiagnosticsCallback> mRegisteredCallbacks;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mContext = InstrumentationRegistry.getContext();
|
||||
mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
|
||||
mCdm = mContext.getSystemService(ConnectivityDiagnosticsManager.class);
|
||||
mCarrierConfigManager = mContext.getSystemService(CarrierConfigManager.class);
|
||||
mPackageManager = mContext.getPackageManager();
|
||||
mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
|
||||
|
||||
mTestNetworkCallback = new TestNetworkCallback();
|
||||
mConnectivityManager.requestNetwork(TEST_NETWORK_REQUEST, mTestNetworkCallback);
|
||||
|
||||
mRegisteredCallbacks = new ArrayList<>();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
mConnectivityManager.unregisterNetworkCallback(mTestNetworkCallback);
|
||||
if (mTestNetwork != null) {
|
||||
runWithShellPermissionIdentity(() -> {
|
||||
final TestNetworkManager tnm = mContext.getSystemService(TestNetworkManager.class);
|
||||
tnm.teardownTestNetwork(mTestNetwork);
|
||||
});
|
||||
mTestNetwork = null;
|
||||
}
|
||||
|
||||
if (mTestNetworkFD != null) {
|
||||
mTestNetworkFD.close();
|
||||
mTestNetworkFD = null;
|
||||
}
|
||||
|
||||
for (TestConnectivityDiagnosticsCallback cb : mRegisteredCallbacks) {
|
||||
mCdm.unregisterConnectivityDiagnosticsCallback(cb);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRegisterConnectivityDiagnosticsCallback() throws Exception {
|
||||
mTestNetworkFD = setUpTestNetwork().getFileDescriptor();
|
||||
mTestNetwork = mTestNetworkCallback.waitForAvailable();
|
||||
|
||||
final TestConnectivityDiagnosticsCallback cb =
|
||||
createAndRegisterConnectivityDiagnosticsCallback(TEST_NETWORK_REQUEST);
|
||||
|
||||
final String interfaceName =
|
||||
mConnectivityManager.getLinkProperties(mTestNetwork).getInterfaceName();
|
||||
|
||||
cb.expectOnConnectivityReportAvailable(mTestNetwork, interfaceName);
|
||||
cb.assertNoCallback();
|
||||
}
|
||||
|
||||
@SkipPresubmit(reason = "Flaky: b/159718782; add to presubmit after fixing")
|
||||
@Test
|
||||
public void testRegisterCallbackWithCarrierPrivileges() throws Exception {
|
||||
assumeTrue(mPackageManager.hasSystemFeature(FEATURE_TELEPHONY));
|
||||
|
||||
final int subId = SubscriptionManager.getDefaultSubscriptionId();
|
||||
if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
|
||||
fail("Need an active subscription. Please ensure that the device has working mobile"
|
||||
+ " data.");
|
||||
}
|
||||
|
||||
final CarrierConfigReceiver carrierConfigReceiver = new CarrierConfigReceiver(subId);
|
||||
mContext.registerReceiver(
|
||||
carrierConfigReceiver,
|
||||
new IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
|
||||
|
||||
final TestNetworkCallback testNetworkCallback = new TestNetworkCallback();
|
||||
|
||||
try {
|
||||
doBroadcastCarrierConfigsAndVerifyOnConnectivityReportAvailable(
|
||||
subId, carrierConfigReceiver, testNetworkCallback);
|
||||
} finally {
|
||||
runWithShellPermissionIdentity(
|
||||
() -> mCarrierConfigManager.overrideConfig(subId, null),
|
||||
android.Manifest.permission.MODIFY_PHONE_STATE);
|
||||
mConnectivityManager.unregisterNetworkCallback(testNetworkCallback);
|
||||
mContext.unregisterReceiver(carrierConfigReceiver);
|
||||
}
|
||||
}
|
||||
|
||||
private String getCertHashForThisPackage() throws Exception {
|
||||
final PackageInfo pkgInfo =
|
||||
mPackageManager.getPackageInfo(
|
||||
mContext.getOpPackageName(), PackageManager.GET_SIGNATURES);
|
||||
final MessageDigest md = MessageDigest.getInstance(SHA_256);
|
||||
final byte[] certHash = md.digest(pkgInfo.signatures[0].toByteArray());
|
||||
return IccUtils.bytesToHexString(certHash);
|
||||
}
|
||||
|
||||
private void doBroadcastCarrierConfigsAndVerifyOnConnectivityReportAvailable(
|
||||
int subId,
|
||||
@NonNull CarrierConfigReceiver carrierConfigReceiver,
|
||||
@NonNull TestNetworkCallback testNetworkCallback)
|
||||
throws Exception {
|
||||
final PersistableBundle carrierConfigs = new PersistableBundle();
|
||||
carrierConfigs.putStringArray(
|
||||
CarrierConfigManager.KEY_CARRIER_CERTIFICATE_STRING_ARRAY,
|
||||
new String[] {getCertHashForThisPackage()});
|
||||
|
||||
runWithShellPermissionIdentity(
|
||||
() -> {
|
||||
mCarrierConfigManager.overrideConfig(subId, carrierConfigs);
|
||||
mCarrierConfigManager.notifyConfigChangedForSubId(subId);
|
||||
},
|
||||
android.Manifest.permission.MODIFY_PHONE_STATE);
|
||||
|
||||
// TODO(b/157779832): This should use android.permission.CHANGE_NETWORK_STATE. However, the
|
||||
// shell does not have CHANGE_NETWORK_STATE, so use CONNECTIVITY_INTERNAL until the shell
|
||||
// permissions are updated.
|
||||
runWithShellPermissionIdentity(
|
||||
() -> mConnectivityManager.requestNetwork(
|
||||
CELLULAR_NETWORK_REQUEST, testNetworkCallback),
|
||||
android.Manifest.permission.CONNECTIVITY_INTERNAL);
|
||||
|
||||
final Network network = testNetworkCallback.waitForAvailable();
|
||||
assertNotNull(network);
|
||||
|
||||
assertTrue("Didn't receive broadcast for ACTION_CARRIER_CONFIG_CHANGED for subId=" + subId,
|
||||
carrierConfigReceiver.waitForCarrierConfigChanged());
|
||||
assertTrue("Don't have Carrier Privileges after adding cert for this package",
|
||||
mTelephonyManager.createForSubscriptionId(subId).hasCarrierPrivileges());
|
||||
|
||||
// Wait for CarrierPrivilegesTracker to receive the ACTION_CARRIER_CONFIG_CHANGED
|
||||
// broadcast. CPT then needs to update the corresponding DataConnection, which then
|
||||
// updates ConnectivityService. Unfortunately, this update to the NetworkCapabilities in
|
||||
// CS does not trigger NetworkCallback#onCapabilitiesChanged as changing the
|
||||
// administratorUids is not a publicly visible change. In lieu of a better signal to
|
||||
// detministically wait for, use Thread#sleep here.
|
||||
// TODO(b/157949581): replace this Thread#sleep with a deterministic signal
|
||||
Thread.sleep(DELAY_FOR_ADMIN_UIDS_MILLIS);
|
||||
|
||||
final TestConnectivityDiagnosticsCallback connDiagsCallback =
|
||||
createAndRegisterConnectivityDiagnosticsCallback(CELLULAR_NETWORK_REQUEST);
|
||||
|
||||
final String interfaceName =
|
||||
mConnectivityManager.getLinkProperties(network).getInterfaceName();
|
||||
connDiagsCallback.expectOnConnectivityReportAvailable(
|
||||
network, interfaceName, TRANSPORT_CELLULAR);
|
||||
connDiagsCallback.assertNoCallback();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRegisterDuplicateConnectivityDiagnosticsCallback() {
|
||||
final TestConnectivityDiagnosticsCallback cb =
|
||||
createAndRegisterConnectivityDiagnosticsCallback(TEST_NETWORK_REQUEST);
|
||||
|
||||
try {
|
||||
mCdm.registerConnectivityDiagnosticsCallback(TEST_NETWORK_REQUEST, INLINE_EXECUTOR, cb);
|
||||
fail("Registering the same callback twice should throw an IllegalArgumentException");
|
||||
} catch (IllegalArgumentException expected) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnregisterConnectivityDiagnosticsCallback() {
|
||||
final TestConnectivityDiagnosticsCallback cb = new TestConnectivityDiagnosticsCallback();
|
||||
mCdm.registerConnectivityDiagnosticsCallback(TEST_NETWORK_REQUEST, INLINE_EXECUTOR, cb);
|
||||
mCdm.unregisterConnectivityDiagnosticsCallback(cb);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnregisterUnknownConnectivityDiagnosticsCallback() {
|
||||
// Expected to silently ignore the unregister() call
|
||||
mCdm.unregisterConnectivityDiagnosticsCallback(new TestConnectivityDiagnosticsCallback());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnConnectivityReportAvailable() throws Exception {
|
||||
final TestConnectivityDiagnosticsCallback cb =
|
||||
createAndRegisterConnectivityDiagnosticsCallback(TEST_NETWORK_REQUEST);
|
||||
|
||||
mTestNetworkFD = setUpTestNetwork().getFileDescriptor();
|
||||
mTestNetwork = mTestNetworkCallback.waitForAvailable();
|
||||
|
||||
final String interfaceName =
|
||||
mConnectivityManager.getLinkProperties(mTestNetwork).getInterfaceName();
|
||||
|
||||
cb.expectOnConnectivityReportAvailable(mTestNetwork, interfaceName);
|
||||
cb.assertNoCallback();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnDataStallSuspected_DnsEvents() throws Exception {
|
||||
final PersistableBundle extras = new PersistableBundle();
|
||||
extras.putInt(KEY_DNS_CONSECUTIVE_TIMEOUTS, DNS_CONSECUTIVE_TIMEOUTS);
|
||||
|
||||
verifyOnDataStallSuspected(DETECTION_METHOD_DNS_EVENTS, TIMESTAMP, extras);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnDataStallSuspected_TcpMetrics() throws Exception {
|
||||
final PersistableBundle extras = new PersistableBundle();
|
||||
extras.putInt(KEY_TCP_METRICS_COLLECTION_PERIOD_MILLIS, COLLECTION_PERIOD_MILLIS);
|
||||
extras.putInt(KEY_TCP_PACKET_FAIL_RATE, FAIL_RATE_PERCENTAGE);
|
||||
|
||||
verifyOnDataStallSuspected(DETECTION_METHOD_TCP_METRICS, TIMESTAMP, extras);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnDataStallSuspected_UnknownDetectionMethod() throws Exception {
|
||||
verifyOnDataStallSuspected(
|
||||
UNKNOWN_DETECTION_METHOD,
|
||||
FILTERED_UNKNOWN_DETECTION_METHOD,
|
||||
TIMESTAMP,
|
||||
PersistableBundle.EMPTY);
|
||||
}
|
||||
|
||||
private void verifyOnDataStallSuspected(
|
||||
int detectionMethod, long timestampMillis, @NonNull PersistableBundle extras)
|
||||
throws Exception {
|
||||
// Input detection method is expected to match received detection method
|
||||
verifyOnDataStallSuspected(detectionMethod, detectionMethod, timestampMillis, extras);
|
||||
}
|
||||
|
||||
private void verifyOnDataStallSuspected(
|
||||
int inputDetectionMethod,
|
||||
int expectedDetectionMethod,
|
||||
long timestampMillis,
|
||||
@NonNull PersistableBundle extras)
|
||||
throws Exception {
|
||||
mTestNetworkFD = setUpTestNetwork().getFileDescriptor();
|
||||
mTestNetwork = mTestNetworkCallback.waitForAvailable();
|
||||
|
||||
final TestConnectivityDiagnosticsCallback cb =
|
||||
createAndRegisterConnectivityDiagnosticsCallback(TEST_NETWORK_REQUEST);
|
||||
|
||||
final String interfaceName =
|
||||
mConnectivityManager.getLinkProperties(mTestNetwork).getInterfaceName();
|
||||
|
||||
cb.expectOnConnectivityReportAvailable(mTestNetwork, interfaceName);
|
||||
|
||||
runWithShellPermissionIdentity(
|
||||
() -> mConnectivityManager.simulateDataStall(
|
||||
inputDetectionMethod, timestampMillis, mTestNetwork, extras),
|
||||
android.Manifest.permission.MANAGE_TEST_NETWORKS);
|
||||
|
||||
cb.expectOnDataStallSuspected(
|
||||
mTestNetwork, interfaceName, expectedDetectionMethod, timestampMillis, extras);
|
||||
cb.assertNoCallback();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnNetworkConnectivityReportedTrue() throws Exception {
|
||||
verifyOnNetworkConnectivityReported(true /* hasConnectivity */);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnNetworkConnectivityReportedFalse() throws Exception {
|
||||
verifyOnNetworkConnectivityReported(false /* hasConnectivity */);
|
||||
}
|
||||
|
||||
private void verifyOnNetworkConnectivityReported(boolean hasConnectivity) throws Exception {
|
||||
mTestNetworkFD = setUpTestNetwork().getFileDescriptor();
|
||||
mTestNetwork = mTestNetworkCallback.waitForAvailable();
|
||||
|
||||
final TestConnectivityDiagnosticsCallback cb =
|
||||
createAndRegisterConnectivityDiagnosticsCallback(TEST_NETWORK_REQUEST);
|
||||
|
||||
// onConnectivityReportAvailable always invoked when the test network is established
|
||||
final String interfaceName =
|
||||
mConnectivityManager.getLinkProperties(mTestNetwork).getInterfaceName();
|
||||
cb.expectOnConnectivityReportAvailable(mTestNetwork, interfaceName);
|
||||
cb.assertNoCallback();
|
||||
|
||||
mConnectivityManager.reportNetworkConnectivity(mTestNetwork, hasConnectivity);
|
||||
|
||||
cb.expectOnNetworkConnectivityReported(mTestNetwork, hasConnectivity);
|
||||
|
||||
// if hasConnectivity does not match the network's known connectivity, it will be
|
||||
// revalidated which will trigger another onConnectivityReportAvailable callback.
|
||||
if (!hasConnectivity) {
|
||||
cb.expectOnConnectivityReportAvailable(mTestNetwork, interfaceName);
|
||||
}
|
||||
|
||||
cb.assertNoCallback();
|
||||
}
|
||||
|
||||
private TestConnectivityDiagnosticsCallback createAndRegisterConnectivityDiagnosticsCallback(
|
||||
NetworkRequest request) {
|
||||
final TestConnectivityDiagnosticsCallback cb = new TestConnectivityDiagnosticsCallback();
|
||||
mCdm.registerConnectivityDiagnosticsCallback(request, INLINE_EXECUTOR, cb);
|
||||
mRegisteredCallbacks.add(cb);
|
||||
return cb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a test NetworkAgent with ConnectivityService with limited capabilities, which leads
|
||||
* to the Network being validated.
|
||||
*/
|
||||
@NonNull
|
||||
private TestNetworkInterface setUpTestNetwork() throws Exception {
|
||||
final int[] administratorUids = new int[] {Process.myUid()};
|
||||
return callWithShellPermissionIdentity(
|
||||
() -> {
|
||||
final TestNetworkManager tnm =
|
||||
mContext.getSystemService(TestNetworkManager.class);
|
||||
final TestNetworkInterface tni = tnm.createTunInterface(new LinkAddress[0]);
|
||||
tnm.setupTestNetwork(tni.getInterfaceName(), administratorUids, BINDER);
|
||||
return tni;
|
||||
});
|
||||
}
|
||||
|
||||
private static class TestConnectivityDiagnosticsCallback
|
||||
extends ConnectivityDiagnosticsCallback {
|
||||
private final ArrayTrackRecord<Object>.ReadHead mHistory =
|
||||
new ArrayTrackRecord<Object>().newReadHead();
|
||||
|
||||
@Override
|
||||
public void onConnectivityReportAvailable(ConnectivityReport report) {
|
||||
mHistory.add(report);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDataStallSuspected(DataStallReport report) {
|
||||
mHistory.add(report);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNetworkConnectivityReported(Network network, boolean hasConnectivity) {
|
||||
mHistory.add(new Pair<Network, Boolean>(network, hasConnectivity));
|
||||
}
|
||||
|
||||
public void expectOnConnectivityReportAvailable(
|
||||
@NonNull Network network, @NonNull String interfaceName) {
|
||||
expectOnConnectivityReportAvailable(network, interfaceName, TRANSPORT_TEST);
|
||||
}
|
||||
|
||||
public void expectOnConnectivityReportAvailable(
|
||||
@NonNull Network network, @NonNull String interfaceName, int transportType) {
|
||||
final ConnectivityReport result =
|
||||
(ConnectivityReport) mHistory.poll(CALLBACK_TIMEOUT_MILLIS, x -> true);
|
||||
assertEquals(network, result.getNetwork());
|
||||
|
||||
final NetworkCapabilities nc = result.getNetworkCapabilities();
|
||||
assertNotNull(nc);
|
||||
assertTrue(nc.hasTransport(transportType));
|
||||
assertNotNull(result.getLinkProperties());
|
||||
assertEquals(interfaceName, result.getLinkProperties().getInterfaceName());
|
||||
|
||||
final PersistableBundle extras = result.getAdditionalInfo();
|
||||
assertTrue(extras.containsKey(KEY_NETWORK_VALIDATION_RESULT));
|
||||
final int validationResult = extras.getInt(KEY_NETWORK_VALIDATION_RESULT);
|
||||
assertEquals("Network validation result is not 'valid'",
|
||||
NETWORK_VALIDATION_RESULT_VALID, validationResult);
|
||||
|
||||
assertTrue(extras.containsKey(KEY_NETWORK_PROBES_SUCCEEDED_BITMASK));
|
||||
final int probesSucceeded = extras.getInt(KEY_NETWORK_VALIDATION_RESULT);
|
||||
assertTrue("PROBES_SUCCEEDED mask not in expected range", probesSucceeded >= 0);
|
||||
|
||||
assertTrue(extras.containsKey(KEY_NETWORK_PROBES_ATTEMPTED_BITMASK));
|
||||
final int probesAttempted = extras.getInt(KEY_NETWORK_PROBES_ATTEMPTED_BITMASK);
|
||||
assertTrue("PROBES_ATTEMPTED mask not in expected range", probesAttempted >= 0);
|
||||
}
|
||||
|
||||
public void expectOnDataStallSuspected(
|
||||
@NonNull Network network,
|
||||
@NonNull String interfaceName,
|
||||
int detectionMethod,
|
||||
long timestampMillis,
|
||||
@NonNull PersistableBundle extras) {
|
||||
final DataStallReport result =
|
||||
(DataStallReport) mHistory.poll(CALLBACK_TIMEOUT_MILLIS, x -> true);
|
||||
assertEquals(network, result.getNetwork());
|
||||
assertEquals(detectionMethod, result.getDetectionMethod());
|
||||
assertEquals(timestampMillis, result.getReportTimestamp());
|
||||
|
||||
final NetworkCapabilities nc = result.getNetworkCapabilities();
|
||||
assertNotNull(nc);
|
||||
assertTrue(nc.hasTransport(TRANSPORT_TEST));
|
||||
assertNotNull(result.getLinkProperties());
|
||||
assertEquals(interfaceName, result.getLinkProperties().getInterfaceName());
|
||||
|
||||
assertTrue(persistableBundleEquals(extras, result.getStallDetails()));
|
||||
}
|
||||
|
||||
public void expectOnNetworkConnectivityReported(
|
||||
@NonNull Network network, boolean hasConnectivity) {
|
||||
final Pair<Network, Boolean> result =
|
||||
(Pair<Network, Boolean>) mHistory.poll(CALLBACK_TIMEOUT_MILLIS, x -> true);
|
||||
assertEquals(network, result.first /* network */);
|
||||
assertEquals(hasConnectivity, result.second /* hasConnectivity */);
|
||||
}
|
||||
|
||||
public void assertNoCallback() {
|
||||
// If no more callbacks exist, there should be nothing left in the ReadHead
|
||||
assertNull("Unexpected event in history",
|
||||
mHistory.poll(NO_CALLBACK_INVOKED_TIMEOUT, x -> true));
|
||||
}
|
||||
}
|
||||
|
||||
private class CarrierConfigReceiver extends BroadcastReceiver {
|
||||
private final CountDownLatch mLatch = new CountDownLatch(1);
|
||||
private final int mSubId;
|
||||
|
||||
CarrierConfigReceiver(int subId) {
|
||||
mSubId = subId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (!CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(intent.getAction())) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int subId =
|
||||
intent.getIntExtra(
|
||||
CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX,
|
||||
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
|
||||
if (mSubId != subId) return;
|
||||
|
||||
final PersistableBundle carrierConfigs = mCarrierConfigManager.getConfigForSubId(subId);
|
||||
if (!CarrierConfigManager.isConfigForIdentifiedCarrier(carrierConfigs)) return;
|
||||
|
||||
final String[] certs =
|
||||
carrierConfigs.getStringArray(
|
||||
CarrierConfigManager.KEY_CARRIER_CERTIFICATE_STRING_ARRAY);
|
||||
try {
|
||||
if (ArrayUtils.contains(certs, getCertHashForThisPackage())) {
|
||||
mLatch.countDown();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
|
||||
boolean waitForCarrierConfigChanged() throws Exception {
|
||||
return mLatch.await(CARRIER_CONFIG_CHANGED_BROADCAST_TIMEOUT, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
}
|
||||
1530
tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
Normal file
1530
tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
Normal file
File diff suppressed because it is too large
Load Diff
44
tests/cts/net/src/android/net/cts/CredentialsTest.java
Normal file
44
tests/cts/net/src/android/net/cts/CredentialsTest.java
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (C) 2008 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 android.net.cts;
|
||||
|
||||
import android.net.Credentials;
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
public class CredentialsTest extends AndroidTestCase {
|
||||
|
||||
public void testCredentials() {
|
||||
// new the Credentials instance
|
||||
// Test with zero inputs
|
||||
Credentials cred = new Credentials(0, 0, 0);
|
||||
assertEquals(0, cred.getGid());
|
||||
assertEquals(0, cred.getPid());
|
||||
assertEquals(0, cred.getUid());
|
||||
|
||||
// Test with big integer
|
||||
cred = new Credentials(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE);
|
||||
assertEquals(Integer.MAX_VALUE, cred.getGid());
|
||||
assertEquals(Integer.MAX_VALUE, cred.getPid());
|
||||
assertEquals(Integer.MAX_VALUE, cred.getUid());
|
||||
|
||||
// Test with big negative integer
|
||||
cred = new Credentials(Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE);
|
||||
assertEquals(Integer.MIN_VALUE, cred.getGid());
|
||||
assertEquals(Integer.MIN_VALUE, cred.getPid());
|
||||
assertEquals(Integer.MIN_VALUE, cred.getUid());
|
||||
}
|
||||
}
|
||||
756
tests/cts/net/src/android/net/cts/DnsResolverTest.java
Normal file
756
tests/cts/net/src/android/net/cts/DnsResolverTest.java
Normal file
@@ -0,0 +1,756 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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 android.net.cts;
|
||||
|
||||
import static android.net.DnsResolver.CLASS_IN;
|
||||
import static android.net.DnsResolver.FLAG_EMPTY;
|
||||
import static android.net.DnsResolver.FLAG_NO_CACHE_LOOKUP;
|
||||
import static android.net.DnsResolver.TYPE_A;
|
||||
import static android.net.DnsResolver.TYPE_AAAA;
|
||||
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
|
||||
import static android.system.OsConstants.ETIMEDOUT;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.content.ContentResolver;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.ConnectivityManager.NetworkCallback;
|
||||
import android.net.DnsResolver;
|
||||
import android.net.LinkProperties;
|
||||
import android.net.Network;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.NetworkRequest;
|
||||
import android.net.ParseException;
|
||||
import android.net.cts.util.CtsNetUtils;
|
||||
import android.os.CancellationSignal;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.platform.test.annotations.AppModeFull;
|
||||
import android.provider.Settings;
|
||||
import android.system.ErrnoException;
|
||||
import android.test.AndroidTestCase;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.net.module.util.DnsPacket;
|
||||
import com.android.testutils.SkipPresubmit;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@AppModeFull(reason = "WRITE_SECURE_SETTINGS permission can't be granted to instant apps")
|
||||
public class DnsResolverTest extends AndroidTestCase {
|
||||
private static final String TAG = "DnsResolverTest";
|
||||
private static final char[] HEX_CHARS = {
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
|
||||
};
|
||||
|
||||
static final String TEST_DOMAIN = "www.google.com";
|
||||
static final String TEST_NX_DOMAIN = "test1-nx.metric.gstatic.com";
|
||||
static final String INVALID_PRIVATE_DNS_SERVER = "invalid.google";
|
||||
static final String GOOGLE_PRIVATE_DNS_SERVER = "dns.google";
|
||||
static final byte[] TEST_BLOB = new byte[]{
|
||||
/* Header */
|
||||
0x55, 0x66, /* Transaction ID */
|
||||
0x01, 0x00, /* Flags */
|
||||
0x00, 0x01, /* Questions */
|
||||
0x00, 0x00, /* Answer RRs */
|
||||
0x00, 0x00, /* Authority RRs */
|
||||
0x00, 0x00, /* Additional RRs */
|
||||
/* Queries */
|
||||
0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65,
|
||||
0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */
|
||||
0x00, 0x01, /* Type */
|
||||
0x00, 0x01 /* Class */
|
||||
};
|
||||
static final int TIMEOUT_MS = 12_000;
|
||||
static final int CANCEL_TIMEOUT_MS = 3_000;
|
||||
static final int CANCEL_RETRY_TIMES = 5;
|
||||
static final int QUERY_TIMES = 10;
|
||||
static final int NXDOMAIN = 3;
|
||||
|
||||
private ContentResolver mCR;
|
||||
private ConnectivityManager mCM;
|
||||
private CtsNetUtils mCtsNetUtils;
|
||||
private Executor mExecutor;
|
||||
private Executor mExecutorInline;
|
||||
private DnsResolver mDns;
|
||||
|
||||
private String mOldMode;
|
||||
private String mOldDnsSpecifier;
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
mCM = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
mDns = DnsResolver.getInstance();
|
||||
mExecutor = new Handler(Looper.getMainLooper())::post;
|
||||
mExecutorInline = (Runnable r) -> r.run();
|
||||
mCR = getContext().getContentResolver();
|
||||
mCtsNetUtils = new CtsNetUtils(getContext());
|
||||
mCtsNetUtils.storePrivateDnsSetting();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tearDown() throws Exception {
|
||||
mCtsNetUtils.restorePrivateDnsSetting();
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
private static String byteArrayToHexString(byte[] bytes) {
|
||||
char[] hexChars = new char[bytes.length * 2];
|
||||
for (int i = 0; i < bytes.length; ++i) {
|
||||
int b = bytes[i] & 0xFF;
|
||||
hexChars[i * 2] = HEX_CHARS[b >>> 4];
|
||||
hexChars[i * 2 + 1] = HEX_CHARS[b & 0x0F];
|
||||
}
|
||||
return new String(hexChars);
|
||||
}
|
||||
|
||||
private Network[] getTestableNetworks() {
|
||||
final ArrayList<Network> testableNetworks = new ArrayList<Network>();
|
||||
for (Network network : mCM.getAllNetworks()) {
|
||||
final NetworkCapabilities nc = mCM.getNetworkCapabilities(network);
|
||||
if (nc != null
|
||||
&& nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
|
||||
&& nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
|
||||
testableNetworks.add(network);
|
||||
}
|
||||
}
|
||||
|
||||
assertTrue(
|
||||
"This test requires that at least one network be connected. " +
|
||||
"Please ensure that the device is connected to a network.",
|
||||
testableNetworks.size() >= 1);
|
||||
// In order to test query with null network, add null as an element.
|
||||
// Test cases which query with null network will go on default network.
|
||||
testableNetworks.add(null);
|
||||
return testableNetworks.toArray(new Network[0]);
|
||||
}
|
||||
|
||||
static private void assertGreaterThan(String msg, int first, int second) {
|
||||
assertTrue(msg + " Excepted " + first + " to be greater than " + second, first > second);
|
||||
}
|
||||
|
||||
private static class DnsParseException extends Exception {
|
||||
public DnsParseException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private static class DnsAnswer extends DnsPacket {
|
||||
DnsAnswer(@NonNull byte[] data) throws DnsParseException {
|
||||
super(data);
|
||||
|
||||
// Check QR field.(query (0), or a response (1)).
|
||||
if ((mHeader.flags & (1 << 15)) == 0) {
|
||||
throw new DnsParseException("Not an answer packet");
|
||||
}
|
||||
}
|
||||
|
||||
int getRcode() {
|
||||
return mHeader.rcode;
|
||||
}
|
||||
|
||||
int getANCount() {
|
||||
return mHeader.getRecordCount(ANSECTION);
|
||||
}
|
||||
|
||||
int getQDCount() {
|
||||
return mHeader.getRecordCount(QDSECTION);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A query callback that ensures that the query is cancelled and that onAnswer is never
|
||||
* called. If the query succeeds before it is cancelled, needRetry will return true so the
|
||||
* test can retry.
|
||||
*/
|
||||
class VerifyCancelCallback implements DnsResolver.Callback<byte[]> {
|
||||
private final CountDownLatch mLatch = new CountDownLatch(1);
|
||||
private final String mMsg;
|
||||
private final CancellationSignal mCancelSignal;
|
||||
private int mRcode;
|
||||
private DnsAnswer mDnsAnswer;
|
||||
private String mErrorMsg = null;
|
||||
|
||||
VerifyCancelCallback(@NonNull String msg, @Nullable CancellationSignal cancel) {
|
||||
mMsg = msg;
|
||||
mCancelSignal = cancel;
|
||||
}
|
||||
|
||||
VerifyCancelCallback(@NonNull String msg) {
|
||||
this(msg, null);
|
||||
}
|
||||
|
||||
public boolean waitForAnswer(int timeout) throws InterruptedException {
|
||||
return mLatch.await(timeout, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public boolean waitForAnswer() throws InterruptedException {
|
||||
return waitForAnswer(TIMEOUT_MS);
|
||||
}
|
||||
|
||||
public boolean needRetry() throws InterruptedException {
|
||||
return mLatch.await(CANCEL_TIMEOUT_MS, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnswer(@NonNull byte[] answer, int rcode) {
|
||||
if (mCancelSignal != null && mCancelSignal.isCanceled()) {
|
||||
mErrorMsg = mMsg + " should not have returned any answers";
|
||||
mLatch.countDown();
|
||||
return;
|
||||
}
|
||||
|
||||
mRcode = rcode;
|
||||
try {
|
||||
mDnsAnswer = new DnsAnswer(answer);
|
||||
} catch (ParseException | DnsParseException e) {
|
||||
mErrorMsg = mMsg + e.getMessage();
|
||||
mLatch.countDown();
|
||||
return;
|
||||
}
|
||||
Log.d(TAG, "Reported blob: " + byteArrayToHexString(answer));
|
||||
mLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@NonNull DnsResolver.DnsException error) {
|
||||
mErrorMsg = mMsg + error.getMessage();
|
||||
mLatch.countDown();
|
||||
}
|
||||
|
||||
private void assertValidAnswer() {
|
||||
assertNull(mErrorMsg);
|
||||
assertNotNull(mMsg + " No valid answer", mDnsAnswer);
|
||||
assertEquals(mMsg + " Unexpected error: reported rcode" + mRcode +
|
||||
" blob's rcode " + mDnsAnswer.getRcode(), mRcode, mDnsAnswer.getRcode());
|
||||
}
|
||||
|
||||
public void assertHasAnswer() {
|
||||
assertValidAnswer();
|
||||
// Check rcode field.(0, No error condition).
|
||||
assertEquals(mMsg + " Response error, rcode: " + mRcode, mRcode, 0);
|
||||
// Check answer counts.
|
||||
assertGreaterThan(mMsg + " No answer found", mDnsAnswer.getANCount(), 0);
|
||||
// Check question counts.
|
||||
assertGreaterThan(mMsg + " No question found", mDnsAnswer.getQDCount(), 0);
|
||||
}
|
||||
|
||||
public void assertNXDomain() {
|
||||
assertValidAnswer();
|
||||
// Check rcode field.(3, NXDomain).
|
||||
assertEquals(mMsg + " Unexpected rcode: " + mRcode, mRcode, NXDOMAIN);
|
||||
// Check answer counts. Expect 0 answer.
|
||||
assertEquals(mMsg + " Not an empty answer", mDnsAnswer.getANCount(), 0);
|
||||
// Check question counts.
|
||||
assertGreaterThan(mMsg + " No question found", mDnsAnswer.getQDCount(), 0);
|
||||
}
|
||||
|
||||
public void assertEmptyAnswer() {
|
||||
assertValidAnswer();
|
||||
// Check rcode field.(0, No error condition).
|
||||
assertEquals(mMsg + " Response error, rcode: " + mRcode, mRcode, 0);
|
||||
// Check answer counts. Expect 0 answer.
|
||||
assertEquals(mMsg + " Not an empty answer", mDnsAnswer.getANCount(), 0);
|
||||
// Check question counts.
|
||||
assertGreaterThan(mMsg + " No question found", mDnsAnswer.getQDCount(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
public void testRawQuery() throws Exception {
|
||||
doTestRawQuery(mExecutor);
|
||||
}
|
||||
|
||||
public void testRawQueryInline() throws Exception {
|
||||
doTestRawQuery(mExecutorInline);
|
||||
}
|
||||
|
||||
public void testRawQueryBlob() throws Exception {
|
||||
doTestRawQueryBlob(mExecutor);
|
||||
}
|
||||
|
||||
public void testRawQueryBlobInline() throws Exception {
|
||||
doTestRawQueryBlob(mExecutorInline);
|
||||
}
|
||||
|
||||
public void testRawQueryRoot() throws Exception {
|
||||
doTestRawQueryRoot(mExecutor);
|
||||
}
|
||||
|
||||
public void testRawQueryRootInline() throws Exception {
|
||||
doTestRawQueryRoot(mExecutorInline);
|
||||
}
|
||||
|
||||
public void testRawQueryNXDomain() throws Exception {
|
||||
doTestRawQueryNXDomain(mExecutor);
|
||||
}
|
||||
|
||||
public void testRawQueryNXDomainInline() throws Exception {
|
||||
doTestRawQueryNXDomain(mExecutorInline);
|
||||
}
|
||||
|
||||
public void testRawQueryNXDomainWithPrivateDns() throws Exception {
|
||||
doTestRawQueryNXDomainWithPrivateDns(mExecutor);
|
||||
}
|
||||
|
||||
public void testRawQueryNXDomainInlineWithPrivateDns() throws Exception {
|
||||
doTestRawQueryNXDomainWithPrivateDns(mExecutorInline);
|
||||
}
|
||||
|
||||
public void doTestRawQuery(Executor executor) throws InterruptedException {
|
||||
final String msg = "RawQuery " + TEST_DOMAIN;
|
||||
for (Network network : getTestableNetworks()) {
|
||||
final VerifyCancelCallback callback = new VerifyCancelCallback(msg);
|
||||
mDns.rawQuery(network, TEST_DOMAIN, CLASS_IN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
|
||||
executor, null, callback);
|
||||
|
||||
assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
|
||||
callback.waitForAnswer());
|
||||
callback.assertHasAnswer();
|
||||
}
|
||||
}
|
||||
|
||||
public void doTestRawQueryBlob(Executor executor) throws InterruptedException {
|
||||
final byte[] blob = new byte[]{
|
||||
/* Header */
|
||||
0x55, 0x66, /* Transaction ID */
|
||||
0x01, 0x00, /* Flags */
|
||||
0x00, 0x01, /* Questions */
|
||||
0x00, 0x00, /* Answer RRs */
|
||||
0x00, 0x00, /* Authority RRs */
|
||||
0x00, 0x00, /* Additional RRs */
|
||||
/* Queries */
|
||||
0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65,
|
||||
0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */
|
||||
0x00, 0x01, /* Type */
|
||||
0x00, 0x01 /* Class */
|
||||
};
|
||||
final String msg = "RawQuery blob " + byteArrayToHexString(blob);
|
||||
for (Network network : getTestableNetworks()) {
|
||||
final VerifyCancelCallback callback = new VerifyCancelCallback(msg);
|
||||
mDns.rawQuery(network, blob, FLAG_NO_CACHE_LOOKUP, executor, null, callback);
|
||||
|
||||
assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
|
||||
callback.waitForAnswer());
|
||||
callback.assertHasAnswer();
|
||||
}
|
||||
}
|
||||
|
||||
public void doTestRawQueryRoot(Executor executor) throws InterruptedException {
|
||||
final String dname = "";
|
||||
final String msg = "RawQuery empty dname(ROOT) ";
|
||||
for (Network network : getTestableNetworks()) {
|
||||
final VerifyCancelCallback callback = new VerifyCancelCallback(msg);
|
||||
mDns.rawQuery(network, dname, CLASS_IN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
|
||||
executor, null, callback);
|
||||
|
||||
assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
|
||||
callback.waitForAnswer());
|
||||
// Except no answer record because the root does not have AAAA records.
|
||||
callback.assertEmptyAnswer();
|
||||
}
|
||||
}
|
||||
|
||||
public void doTestRawQueryNXDomain(Executor executor) throws InterruptedException {
|
||||
final String msg = "RawQuery " + TEST_NX_DOMAIN;
|
||||
|
||||
for (Network network : getTestableNetworks()) {
|
||||
final NetworkCapabilities nc = (network != null)
|
||||
? mCM.getNetworkCapabilities(network)
|
||||
: mCM.getNetworkCapabilities(mCM.getActiveNetwork());
|
||||
assertNotNull("Couldn't determine NetworkCapabilities for " + network, nc);
|
||||
// Some cellular networks configure their DNS servers never to return NXDOMAIN, so don't
|
||||
// test NXDOMAIN on these DNS servers.
|
||||
// b/144521720
|
||||
if (nc.hasTransport(TRANSPORT_CELLULAR)) continue;
|
||||
final VerifyCancelCallback callback = new VerifyCancelCallback(msg);
|
||||
mDns.rawQuery(network, TEST_NX_DOMAIN, CLASS_IN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
|
||||
executor, null, callback);
|
||||
|
||||
assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
|
||||
callback.waitForAnswer());
|
||||
callback.assertNXDomain();
|
||||
}
|
||||
}
|
||||
|
||||
public void doTestRawQueryNXDomainWithPrivateDns(Executor executor)
|
||||
throws InterruptedException {
|
||||
final String msg = "RawQuery " + TEST_NX_DOMAIN + " with private DNS";
|
||||
// Enable private DNS strict mode and set server to dns.google before doing NxDomain test.
|
||||
// b/144521720
|
||||
mCtsNetUtils.setPrivateDnsStrictMode(GOOGLE_PRIVATE_DNS_SERVER);
|
||||
for (Network network : getTestableNetworks()) {
|
||||
final Network networkForPrivateDns =
|
||||
(network != null) ? network : mCM.getActiveNetwork();
|
||||
assertNotNull("Can't find network to await private DNS on", networkForPrivateDns);
|
||||
mCtsNetUtils.awaitPrivateDnsSetting(msg + " wait private DNS setting timeout",
|
||||
networkForPrivateDns, GOOGLE_PRIVATE_DNS_SERVER, true);
|
||||
final VerifyCancelCallback callback = new VerifyCancelCallback(msg);
|
||||
mDns.rawQuery(network, TEST_NX_DOMAIN, CLASS_IN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
|
||||
executor, null, callback);
|
||||
|
||||
assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
|
||||
callback.waitForAnswer());
|
||||
callback.assertNXDomain();
|
||||
}
|
||||
}
|
||||
|
||||
public void testRawQueryCancel() throws InterruptedException {
|
||||
final String msg = "Test cancel RawQuery " + TEST_DOMAIN;
|
||||
// Start a DNS query and the cancel it immediately. Use VerifyCancelCallback to expect
|
||||
// that the query is cancelled before it succeeds. If it is not cancelled before it
|
||||
// succeeds, retry the test until it is.
|
||||
for (Network network : getTestableNetworks()) {
|
||||
boolean retry = false;
|
||||
int round = 0;
|
||||
do {
|
||||
if (++round > CANCEL_RETRY_TIMES) {
|
||||
fail(msg + " cancel failed " + CANCEL_RETRY_TIMES + " times");
|
||||
}
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
final CancellationSignal cancelSignal = new CancellationSignal();
|
||||
final VerifyCancelCallback callback = new VerifyCancelCallback(msg, cancelSignal);
|
||||
mDns.rawQuery(network, TEST_DOMAIN, CLASS_IN, TYPE_AAAA, FLAG_EMPTY,
|
||||
mExecutor, cancelSignal, callback);
|
||||
mExecutor.execute(() -> {
|
||||
cancelSignal.cancel();
|
||||
latch.countDown();
|
||||
});
|
||||
|
||||
retry = callback.needRetry();
|
||||
assertTrue(msg + " query was not cancelled",
|
||||
latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
|
||||
} while (retry);
|
||||
}
|
||||
}
|
||||
|
||||
public void testRawQueryBlobCancel() throws InterruptedException {
|
||||
final String msg = "Test cancel RawQuery blob " + byteArrayToHexString(TEST_BLOB);
|
||||
// Start a DNS query and the cancel it immediately. Use VerifyCancelCallback to expect
|
||||
// that the query is cancelled before it succeeds. If it is not cancelled before it
|
||||
// succeeds, retry the test until it is.
|
||||
for (Network network : getTestableNetworks()) {
|
||||
boolean retry = false;
|
||||
int round = 0;
|
||||
do {
|
||||
if (++round > CANCEL_RETRY_TIMES) {
|
||||
fail(msg + " cancel failed " + CANCEL_RETRY_TIMES + " times");
|
||||
}
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
final CancellationSignal cancelSignal = new CancellationSignal();
|
||||
final VerifyCancelCallback callback = new VerifyCancelCallback(msg, cancelSignal);
|
||||
mDns.rawQuery(network, TEST_BLOB, FLAG_EMPTY, mExecutor, cancelSignal, callback);
|
||||
mExecutor.execute(() -> {
|
||||
cancelSignal.cancel();
|
||||
latch.countDown();
|
||||
});
|
||||
|
||||
retry = callback.needRetry();
|
||||
assertTrue(msg + " cancel is not done",
|
||||
latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
|
||||
} while (retry);
|
||||
}
|
||||
}
|
||||
|
||||
public void testCancelBeforeQuery() throws InterruptedException {
|
||||
final String msg = "Test cancelled RawQuery " + TEST_DOMAIN;
|
||||
for (Network network : getTestableNetworks()) {
|
||||
final VerifyCancelCallback callback = new VerifyCancelCallback(msg);
|
||||
final CancellationSignal cancelSignal = new CancellationSignal();
|
||||
cancelSignal.cancel();
|
||||
mDns.rawQuery(network, TEST_DOMAIN, CLASS_IN, TYPE_AAAA, FLAG_EMPTY,
|
||||
mExecutor, cancelSignal, callback);
|
||||
|
||||
assertTrue(msg + " should not return any answers",
|
||||
!callback.waitForAnswer(CANCEL_TIMEOUT_MS));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A query callback for InetAddress that ensures that the query is
|
||||
* cancelled and that onAnswer is never called. If the query succeeds
|
||||
* before it is cancelled, needRetry will return true so the
|
||||
* test can retry.
|
||||
*/
|
||||
class VerifyCancelInetAddressCallback implements DnsResolver.Callback<List<InetAddress>> {
|
||||
private final CountDownLatch mLatch = new CountDownLatch(1);
|
||||
private final String mMsg;
|
||||
private final List<InetAddress> mAnswers;
|
||||
private final CancellationSignal mCancelSignal;
|
||||
private String mErrorMsg = null;
|
||||
|
||||
VerifyCancelInetAddressCallback(@NonNull String msg, @Nullable CancellationSignal cancel) {
|
||||
this.mMsg = msg;
|
||||
this.mCancelSignal = cancel;
|
||||
mAnswers = new ArrayList<>();
|
||||
}
|
||||
|
||||
public boolean waitForAnswer() throws InterruptedException {
|
||||
return mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public boolean needRetry() throws InterruptedException {
|
||||
return mLatch.await(CANCEL_TIMEOUT_MS, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public boolean isAnswerEmpty() {
|
||||
return mAnswers.isEmpty();
|
||||
}
|
||||
|
||||
public boolean hasIpv6Answer() {
|
||||
for (InetAddress answer : mAnswers) {
|
||||
if (answer instanceof Inet6Address) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean hasIpv4Answer() {
|
||||
for (InetAddress answer : mAnswers) {
|
||||
if (answer instanceof Inet4Address) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void assertNoError() {
|
||||
assertNull(mErrorMsg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnswer(@NonNull List<InetAddress> answerList, int rcode) {
|
||||
if (mCancelSignal != null && mCancelSignal.isCanceled()) {
|
||||
mErrorMsg = mMsg + " should not have returned any answers";
|
||||
mLatch.countDown();
|
||||
return;
|
||||
}
|
||||
for (InetAddress addr : answerList) {
|
||||
Log.d(TAG, "Reported addr: " + addr.toString());
|
||||
}
|
||||
mAnswers.clear();
|
||||
mAnswers.addAll(answerList);
|
||||
mLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@NonNull DnsResolver.DnsException error) {
|
||||
mErrorMsg = mMsg + error.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
public void testQueryForInetAddress() throws Exception {
|
||||
doTestQueryForInetAddress(mExecutor);
|
||||
}
|
||||
|
||||
public void testQueryForInetAddressInline() throws Exception {
|
||||
doTestQueryForInetAddress(mExecutorInline);
|
||||
}
|
||||
|
||||
public void testQueryForInetAddressIpv4() throws Exception {
|
||||
doTestQueryForInetAddressIpv4(mExecutor);
|
||||
}
|
||||
|
||||
public void testQueryForInetAddressIpv4Inline() throws Exception {
|
||||
doTestQueryForInetAddressIpv4(mExecutorInline);
|
||||
}
|
||||
|
||||
public void testQueryForInetAddressIpv6() throws Exception {
|
||||
doTestQueryForInetAddressIpv6(mExecutor);
|
||||
}
|
||||
|
||||
public void testQueryForInetAddressIpv6Inline() throws Exception {
|
||||
doTestQueryForInetAddressIpv6(mExecutorInline);
|
||||
}
|
||||
|
||||
public void testContinuousQueries() throws Exception {
|
||||
doTestContinuousQueries(mExecutor);
|
||||
}
|
||||
|
||||
@SkipPresubmit(reason = "Flaky: b/159762682; add to presubmit after fixing")
|
||||
public void testContinuousQueriesInline() throws Exception {
|
||||
doTestContinuousQueries(mExecutorInline);
|
||||
}
|
||||
|
||||
public void doTestQueryForInetAddress(Executor executor) throws InterruptedException {
|
||||
final String msg = "Test query for InetAddress " + TEST_DOMAIN;
|
||||
for (Network network : getTestableNetworks()) {
|
||||
final VerifyCancelInetAddressCallback callback =
|
||||
new VerifyCancelInetAddressCallback(msg, null);
|
||||
mDns.query(network, TEST_DOMAIN, FLAG_NO_CACHE_LOOKUP, executor, null, callback);
|
||||
|
||||
assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
|
||||
callback.waitForAnswer());
|
||||
callback.assertNoError();
|
||||
assertTrue(msg + " returned 0 results", !callback.isAnswerEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
public void testQueryCancelForInetAddress() throws InterruptedException {
|
||||
final String msg = "Test cancel query for InetAddress " + TEST_DOMAIN;
|
||||
// Start a DNS query and the cancel it immediately. Use VerifyCancelInetAddressCallback to
|
||||
// expect that the query is cancelled before it succeeds. If it is not cancelled before it
|
||||
// succeeds, retry the test until it is.
|
||||
for (Network network : getTestableNetworks()) {
|
||||
boolean retry = false;
|
||||
int round = 0;
|
||||
do {
|
||||
if (++round > CANCEL_RETRY_TIMES) {
|
||||
fail(msg + " cancel failed " + CANCEL_RETRY_TIMES + " times");
|
||||
}
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
final CancellationSignal cancelSignal = new CancellationSignal();
|
||||
final VerifyCancelInetAddressCallback callback =
|
||||
new VerifyCancelInetAddressCallback(msg, cancelSignal);
|
||||
mDns.query(network, TEST_DOMAIN, FLAG_EMPTY, mExecutor, cancelSignal, callback);
|
||||
mExecutor.execute(() -> {
|
||||
cancelSignal.cancel();
|
||||
latch.countDown();
|
||||
});
|
||||
|
||||
retry = callback.needRetry();
|
||||
assertTrue(msg + " query was not cancelled",
|
||||
latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
|
||||
} while (retry);
|
||||
}
|
||||
}
|
||||
|
||||
public void doTestQueryForInetAddressIpv4(Executor executor) throws InterruptedException {
|
||||
final String msg = "Test query for IPv4 InetAddress " + TEST_DOMAIN;
|
||||
for (Network network : getTestableNetworks()) {
|
||||
final VerifyCancelInetAddressCallback callback =
|
||||
new VerifyCancelInetAddressCallback(msg, null);
|
||||
mDns.query(network, TEST_DOMAIN, TYPE_A, FLAG_NO_CACHE_LOOKUP,
|
||||
executor, null, callback);
|
||||
|
||||
assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
|
||||
callback.waitForAnswer());
|
||||
callback.assertNoError();
|
||||
assertTrue(msg + " returned 0 results", !callback.isAnswerEmpty());
|
||||
assertTrue(msg + " returned Ipv6 results", !callback.hasIpv6Answer());
|
||||
}
|
||||
}
|
||||
|
||||
public void doTestQueryForInetAddressIpv6(Executor executor) throws InterruptedException {
|
||||
final String msg = "Test query for IPv6 InetAddress " + TEST_DOMAIN;
|
||||
for (Network network : getTestableNetworks()) {
|
||||
final VerifyCancelInetAddressCallback callback =
|
||||
new VerifyCancelInetAddressCallback(msg, null);
|
||||
mDns.query(network, TEST_DOMAIN, TYPE_AAAA, FLAG_NO_CACHE_LOOKUP,
|
||||
executor, null, callback);
|
||||
|
||||
assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
|
||||
callback.waitForAnswer());
|
||||
callback.assertNoError();
|
||||
assertTrue(msg + " returned 0 results", !callback.isAnswerEmpty());
|
||||
assertTrue(msg + " returned Ipv4 results", !callback.hasIpv4Answer());
|
||||
}
|
||||
}
|
||||
|
||||
public void testPrivateDnsBypass() throws InterruptedException {
|
||||
final Network[] testNetworks = getTestableNetworks();
|
||||
|
||||
// Set an invalid private DNS server
|
||||
mCtsNetUtils.setPrivateDnsStrictMode(INVALID_PRIVATE_DNS_SERVER);
|
||||
final String msg = "Test PrivateDnsBypass " + TEST_DOMAIN;
|
||||
for (Network network : testNetworks) {
|
||||
// This test cannot be ran with null network because we need to explicitly pass a
|
||||
// private DNS bypassable network or bind one.
|
||||
if (network == null) continue;
|
||||
|
||||
// wait for private DNS setting propagating
|
||||
mCtsNetUtils.awaitPrivateDnsSetting(msg + " wait private DNS setting timeout",
|
||||
network, INVALID_PRIVATE_DNS_SERVER, false);
|
||||
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
final DnsResolver.Callback<List<InetAddress>> errorCallback =
|
||||
new DnsResolver.Callback<List<InetAddress>>() {
|
||||
@Override
|
||||
public void onAnswer(@NonNull List<InetAddress> answerList, int rcode) {
|
||||
fail(msg + " should not get valid answer");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@NonNull DnsResolver.DnsException error) {
|
||||
assertEquals(DnsResolver.ERROR_SYSTEM, error.code);
|
||||
assertEquals(ETIMEDOUT, ((ErrnoException) error.getCause()).errno);
|
||||
latch.countDown();
|
||||
}
|
||||
};
|
||||
// Private DNS strict mode with invalid DNS server is set
|
||||
// Expect no valid answer returned but ErrnoException with ETIMEDOUT
|
||||
mDns.query(network, TEST_DOMAIN, FLAG_NO_CACHE_LOOKUP, mExecutor, null, errorCallback);
|
||||
|
||||
assertTrue(msg + " invalid server round. No response after " + TIMEOUT_MS + "ms.",
|
||||
latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
|
||||
|
||||
final VerifyCancelInetAddressCallback callback =
|
||||
new VerifyCancelInetAddressCallback(msg, null);
|
||||
// Bypass privateDns, expect query works fine
|
||||
mDns.query(network.getPrivateDnsBypassingCopy(),
|
||||
TEST_DOMAIN, FLAG_NO_CACHE_LOOKUP, mExecutor, null, callback);
|
||||
|
||||
assertTrue(msg + " bypass private DNS round. No answer after " + TIMEOUT_MS + "ms.",
|
||||
callback.waitForAnswer());
|
||||
callback.assertNoError();
|
||||
assertTrue(msg + " returned 0 results", !callback.isAnswerEmpty());
|
||||
|
||||
// To ensure private DNS bypass still work even if passing null network.
|
||||
// Bind process network with a private DNS bypassable network.
|
||||
mCM.bindProcessToNetwork(network.getPrivateDnsBypassingCopy());
|
||||
final VerifyCancelInetAddressCallback callbackWithNullNetwork =
|
||||
new VerifyCancelInetAddressCallback(msg + " with null network ", null);
|
||||
mDns.query(null,
|
||||
TEST_DOMAIN, FLAG_NO_CACHE_LOOKUP, mExecutor, null, callbackWithNullNetwork);
|
||||
|
||||
assertTrue(msg + " with null network bypass private DNS round. No answer after " +
|
||||
TIMEOUT_MS + "ms.", callbackWithNullNetwork.waitForAnswer());
|
||||
callbackWithNullNetwork.assertNoError();
|
||||
assertTrue(msg + " with null network returned 0 results",
|
||||
!callbackWithNullNetwork.isAnswerEmpty());
|
||||
|
||||
// Reset process network to default.
|
||||
mCM.bindProcessToNetwork(null);
|
||||
}
|
||||
}
|
||||
|
||||
public void doTestContinuousQueries(Executor executor) throws InterruptedException {
|
||||
final String msg = "Test continuous " + QUERY_TIMES + " queries " + TEST_DOMAIN;
|
||||
for (Network network : getTestableNetworks()) {
|
||||
for (int i = 0; i < QUERY_TIMES ; ++i) {
|
||||
final VerifyCancelInetAddressCallback callback =
|
||||
new VerifyCancelInetAddressCallback(msg, null);
|
||||
// query v6/v4 in turn
|
||||
boolean queryV6 = (i % 2 == 0);
|
||||
mDns.query(network, TEST_DOMAIN, queryV6 ? TYPE_AAAA : TYPE_A,
|
||||
FLAG_NO_CACHE_LOOKUP, executor, null, callback);
|
||||
|
||||
assertTrue(msg + " but no answer after " + TIMEOUT_MS + "ms.",
|
||||
callback.waitForAnswer());
|
||||
callback.assertNoError();
|
||||
assertTrue(msg + " returned 0 results", !callback.isAnswerEmpty());
|
||||
assertTrue(msg + " returned " + (queryV6 ? "Ipv4" : "Ipv6") + " results",
|
||||
queryV6 ? !callback.hasIpv4Answer() : !callback.hasIpv6Answer());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
309
tests/cts/net/src/android/net/cts/DnsTest.java
Normal file
309
tests/cts/net/src/android/net/cts/DnsTest.java
Normal file
@@ -0,0 +1,309 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 android.net.cts;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.ConnectivityManager.NetworkCallback;
|
||||
import android.net.LinkProperties;
|
||||
import android.net.Network;
|
||||
import android.net.NetworkInfo;
|
||||
import android.os.SystemClock;
|
||||
import android.test.AndroidTestCase;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.testutils.SkipPresubmit;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class DnsTest extends AndroidTestCase {
|
||||
|
||||
static {
|
||||
System.loadLibrary("nativedns_jni");
|
||||
}
|
||||
|
||||
private static final boolean DBG = false;
|
||||
private static final String TAG = "DnsTest";
|
||||
private static final String PROXY_NETWORK_TYPE = "PROXY";
|
||||
|
||||
private ConnectivityManager mCm;
|
||||
|
||||
public void setUp() {
|
||||
mCm = getContext().getSystemService(ConnectivityManager.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true on success
|
||||
*/
|
||||
private static native boolean testNativeDns();
|
||||
|
||||
/**
|
||||
* Verify:
|
||||
* DNS works - forwards and backwards, giving ipv4 and ipv6
|
||||
* Test that DNS work on v4 and v6 networks
|
||||
* Test Native dns calls (4)
|
||||
* Todo:
|
||||
* Cache is flushed when we change networks
|
||||
* have per-network caches
|
||||
* No cache when there's no network
|
||||
* Perf - measure size of first and second tier caches and their effect
|
||||
* Assert requires network permission
|
||||
*/
|
||||
@SkipPresubmit(reason = "IPv6 support may be missing on presubmit virtual hardware")
|
||||
public void testDnsWorks() throws Exception {
|
||||
ensureIpv6Connectivity();
|
||||
|
||||
InetAddress addrs[] = {};
|
||||
try {
|
||||
addrs = InetAddress.getAllByName("www.google.com");
|
||||
} catch (UnknownHostException e) {}
|
||||
assertTrue("[RERUN] DNS could not resolve www.google.com. Check internet connection",
|
||||
addrs.length != 0);
|
||||
boolean foundV4 = false, foundV6 = false;
|
||||
for (InetAddress addr : addrs) {
|
||||
if (addr instanceof Inet4Address) foundV4 = true;
|
||||
else if (addr instanceof Inet6Address) foundV6 = true;
|
||||
if (DBG) Log.e(TAG, "www.google.com gave " + addr.toString());
|
||||
}
|
||||
|
||||
// We should have at least one of the addresses to connect!
|
||||
assertTrue("www.google.com must have IPv4 and/or IPv6 address", foundV4 || foundV6);
|
||||
|
||||
// Skip the rest of the test if the active network for watch is PROXY.
|
||||
// TODO: Check NetworkInfo type in addition to type name once ag/601257 is merged.
|
||||
if (getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)
|
||||
&& activeNetworkInfoIsProxy()) {
|
||||
Log.i(TAG, "Skipping test because the active network type name is PROXY.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear test state so we don't get confused with the previous results.
|
||||
addrs = new InetAddress[0];
|
||||
foundV4 = foundV6 = false;
|
||||
try {
|
||||
addrs = InetAddress.getAllByName("ipv6.google.com");
|
||||
} catch (UnknownHostException e) {}
|
||||
String msg =
|
||||
"[RERUN] DNS could not resolve ipv6.google.com, check the network supports IPv6. lp=" +
|
||||
mCm.getActiveLinkProperties();
|
||||
assertTrue(msg, addrs.length != 0);
|
||||
for (InetAddress addr : addrs) {
|
||||
msg = "[RERUN] ipv6.google.com returned IPv4 address: " + addr.getHostAddress() +
|
||||
", check your network's DNS server. lp=" + mCm.getActiveLinkProperties();
|
||||
assertFalse (msg, addr instanceof Inet4Address);
|
||||
foundV6 |= (addr instanceof Inet6Address);
|
||||
if (DBG) Log.e(TAG, "ipv6.google.com gave " + addr.toString());
|
||||
}
|
||||
|
||||
assertTrue(foundV6);
|
||||
|
||||
assertTrue(testNativeDns());
|
||||
}
|
||||
|
||||
private static final String[] URLS = { "www.google.com", "ipv6.google.com", "www.yahoo.com",
|
||||
"facebook.com", "youtube.com", "blogspot.com", "baidu.com", "wikipedia.org",
|
||||
// live.com fails rev lookup.
|
||||
"twitter.com", "qq.com", "msn.com", "yahoo.co.jp", "linkedin.com",
|
||||
"taobao.com", "google.co.in", "sina.com.cn", "amazon.com", "wordpress.com",
|
||||
"google.co.uk", "ebay.com", "yandex.ru", "163.com", "google.co.jp", "google.fr",
|
||||
"microsoft.com", "paypal.com", "google.com.br", "flickr.com",
|
||||
"mail.ru", "craigslist.org", "fc2.com", "google.it",
|
||||
// "apple.com", fails rev lookup
|
||||
"google.es",
|
||||
"imdb.com", "google.ru", "soho.com", "bbc.co.uk", "vkontakte.ru", "ask.com",
|
||||
"tumblr.com", "weibo.com", "go.com", "xvideos.com", "livejasmin.com", "cnn.com",
|
||||
"youku.com", "blogspot.com", "soso.com", "google.ca", "aol.com", "tudou.com",
|
||||
"xhamster.com", "megaupload.com", "ifeng.com", "zedo.com", "mediafire.com", "ameblo.jp",
|
||||
"pornhub.com", "google.co.id", "godaddy.com", "adobe.com", "rakuten.co.jp", "about.com",
|
||||
"espn.go.com", "4shared.com", "alibaba.com","ebay.de", "yieldmanager.com",
|
||||
"wordpress.org", "livejournal.com", "google.com.tr", "google.com.mx", "renren.com",
|
||||
"livedoor.com", "google.com.au", "youporn.com", "uol.com.br", "cnet.com", "conduit.com",
|
||||
"google.pl", "myspace.com", "nytimes.com", "ebay.co.uk", "chinaz.com", "hao123.com",
|
||||
"thepiratebay.org", "doubleclick.com", "alipay.com", "netflix.com", "cnzz.com",
|
||||
"huffingtonpost.com", "twitpic.com", "weather.com", "babylon.com", "amazon.de",
|
||||
"dailymotion.com", "orkut.com", "orkut.com.br", "google.com.sa", "odnoklassniki.ru",
|
||||
"amazon.co.jp", "google.nl", "goo.ne.jp", "stumbleupon.com", "tube8.com", "tmall.com",
|
||||
"imgur.com", "globo.com", "secureserver.net", "fileserve.com", "tianya.cn", "badoo.com",
|
||||
"ehow.com", "photobucket.com", "imageshack.us", "xnxx.com", "deviantart.com",
|
||||
"filestube.com", "addthis.com", "douban.com", "vimeo.com", "sogou.com",
|
||||
"stackoverflow.com", "reddit.com", "dailymail.co.uk", "redtube.com", "megavideo.com",
|
||||
"taringa.net", "pengyou.com", "amazon.co.uk", "fbcdn.net", "aweber.com", "spiegel.de",
|
||||
"rapidshare.com", "mixi.jp", "360buy.com", "google.cn", "digg.com", "answers.com",
|
||||
"bit.ly", "indiatimes.com", "skype.com", "yfrog.com", "optmd.com", "google.com.eg",
|
||||
"google.com.pk", "58.com", "hotfile.com", "google.co.th",
|
||||
"bankofamerica.com", "sourceforge.net", "maktoob.com", "warriorforum.com", "rediff.com",
|
||||
"google.co.za", "56.com", "torrentz.eu", "clicksor.com", "avg.com",
|
||||
"download.com", "ku6.com", "statcounter.com", "foxnews.com", "google.com.ar",
|
||||
"nicovideo.jp", "reference.com", "liveinternet.ru", "ucoz.ru", "xinhuanet.com",
|
||||
"xtendmedia.com", "naver.com", "youjizz.com", "domaintools.com", "sparkstudios.com",
|
||||
"rambler.ru", "scribd.com", "kaixin001.com", "mashable.com", "adultfirendfinder.com",
|
||||
"files.wordpress.com", "guardian.co.uk", "bild.de", "yelp.com", "wikimedia.org",
|
||||
"chase.com", "onet.pl", "ameba.jp", "pconline.com.cn", "free.fr", "etsy.com",
|
||||
"typepad.com", "youdao.com", "megaclick.com", "digitalpoint.com", "blogfa.com",
|
||||
"salesforce.com", "adf.ly", "ganji.com", "wikia.com", "archive.org", "terra.com.br",
|
||||
"w3schools.com", "ezinearticles.com", "wjs.com", "google.com.my", "clickbank.com",
|
||||
"squidoo.com", "hulu.com", "repubblica.it", "google.be", "allegro.pl", "comcast.net",
|
||||
"narod.ru", "zol.com.cn", "orange.fr", "soufun.com", "hatena.ne.jp", "google.gr",
|
||||
"in.com", "techcrunch.com", "orkut.co.in", "xunlei.com",
|
||||
"reuters.com", "google.com.vn", "hostgator.com", "kaskus.us", "espncricinfo.com",
|
||||
"hootsuite.com", "qiyi.com", "gmx.net", "xing.com", "php.net", "soku.com", "web.de",
|
||||
"libero.it", "groupon.com", "51.la", "slideshare.net", "booking.com", "seesaa.net",
|
||||
"126.com", "telegraph.co.uk", "wretch.cc", "twimg.com", "rutracker.org", "angege.com",
|
||||
"nba.com", "dell.com", "leboncoin.fr", "people.com", "google.com.tw", "walmart.com",
|
||||
"daum.net", "2ch.net", "constantcontact.com", "nifty.com", "mywebsearch.com",
|
||||
"tripadvisor.com", "google.se", "paipai.com", "google.com.ua", "ning.com", "hp.com",
|
||||
"google.at", "joomla.org", "icio.us", "hudong.com", "csdn.net", "getfirebug.com",
|
||||
"ups.com", "cj.com", "google.ch", "camzap.com", "wordreference.com", "tagged.com",
|
||||
"wp.pl", "mozilla.com", "google.ru", "usps.com", "china.com", "themeforest.net",
|
||||
"search-results.com", "tribalfusion.com", "thefreedictionary.com", "isohunt.com",
|
||||
"linkwithin.com", "cam4.com", "plentyoffish.com", "wellsfargo.com", "metacafe.com",
|
||||
"depositfiles.com", "freelancer.com", "opendns.com", "homeway.com", "engadget.com",
|
||||
"10086.cn", "360.cn", "marca.com", "dropbox.com", "ign.com", "match.com", "google.pt",
|
||||
"facemoods.com", "hardsextube.com", "google.com.ph", "lockerz.com", "istockphoto.com",
|
||||
"partypoker.com", "netlog.com", "outbrain.com", "elpais.com", "fiverr.com",
|
||||
"biglobe.ne.jp", "corriere.it", "love21cn.com", "yesky.com", "spankwire.com",
|
||||
"ig.com.br", "imagevenue.com", "hubpages.com", "google.co.ve"};
|
||||
|
||||
// TODO - this works, but is slow and cts doesn't do anything with the result.
|
||||
// Maybe require a min performance, a min cache size (detectable) and/or move
|
||||
// to perf testing
|
||||
private static final int LOOKUP_COUNT_GOAL = URLS.length;
|
||||
public void skiptestDnsPerf() {
|
||||
ArrayList<String> results = new ArrayList<String>();
|
||||
int failures = 0;
|
||||
try {
|
||||
for (int numberOfUrls = URLS.length; numberOfUrls > 0; numberOfUrls--) {
|
||||
failures = 0;
|
||||
int iterationLimit = LOOKUP_COUNT_GOAL / numberOfUrls;
|
||||
long startTime = SystemClock.elapsedRealtimeNanos();
|
||||
for (int iteration = 0; iteration < iterationLimit; iteration++) {
|
||||
for (int urlIndex = 0; urlIndex < numberOfUrls; urlIndex++) {
|
||||
try {
|
||||
InetAddress addr = InetAddress.getByName(URLS[urlIndex]);
|
||||
} catch (UnknownHostException e) {
|
||||
Log.e(TAG, "failed first lookup of " + URLS[urlIndex]);
|
||||
failures++;
|
||||
try {
|
||||
InetAddress addr = InetAddress.getByName(URLS[urlIndex]);
|
||||
} catch (UnknownHostException ee) {
|
||||
failures++;
|
||||
Log.e(TAG, "failed SECOND lookup of " + URLS[urlIndex]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
long endTime = SystemClock.elapsedRealtimeNanos();
|
||||
float nsPer = ((float)(endTime-startTime) / iterationLimit) / numberOfUrls/ 1000;
|
||||
String thisResult = new String("getByName for " + numberOfUrls + " took " +
|
||||
(endTime - startTime)/1000 + "(" + nsPer + ") with " +
|
||||
failures + " failures\n");
|
||||
Log.d(TAG, thisResult);
|
||||
results.add(thisResult);
|
||||
}
|
||||
// build up a list of addresses
|
||||
ArrayList<byte[]> addressList = new ArrayList<byte[]>();
|
||||
for (String url : URLS) {
|
||||
try {
|
||||
InetAddress addr = InetAddress.getByName(url);
|
||||
addressList.add(addr.getAddress());
|
||||
} catch (UnknownHostException e) {
|
||||
Log.e(TAG, "Exception making reverseDNS list: " + e.toString());
|
||||
}
|
||||
}
|
||||
for (int numberOfAddrs = addressList.size(); numberOfAddrs > 0; numberOfAddrs--) {
|
||||
int iterationLimit = LOOKUP_COUNT_GOAL / numberOfAddrs;
|
||||
failures = 0;
|
||||
long startTime = SystemClock.elapsedRealtimeNanos();
|
||||
for (int iteration = 0; iteration < iterationLimit; iteration++) {
|
||||
for (int addrIndex = 0; addrIndex < numberOfAddrs; addrIndex++) {
|
||||
try {
|
||||
InetAddress addr = InetAddress.getByAddress(addressList.get(addrIndex));
|
||||
String hostname = addr.getHostName();
|
||||
} catch (UnknownHostException e) {
|
||||
failures++;
|
||||
Log.e(TAG, "Failure doing reverse DNS lookup: " + e.toString());
|
||||
try {
|
||||
InetAddress addr =
|
||||
InetAddress.getByAddress(addressList.get(addrIndex));
|
||||
String hostname = addr.getHostName();
|
||||
|
||||
} catch (UnknownHostException ee) {
|
||||
failures++;
|
||||
Log.e(TAG, "Failure doing SECOND reverse DNS lookup: " +
|
||||
ee.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
long endTime = SystemClock.elapsedRealtimeNanos();
|
||||
float nsPer = ((endTime-startTime) / iterationLimit) / numberOfAddrs / 1000;
|
||||
String thisResult = new String("getHostName for " + numberOfAddrs + " took " +
|
||||
(endTime - startTime)/1000 + "(" + nsPer + ") with " +
|
||||
failures + " failures\n");
|
||||
Log.d(TAG, thisResult);
|
||||
results.add(thisResult);
|
||||
}
|
||||
for (String result : results) Log.d(TAG, result);
|
||||
|
||||
InetAddress exit = InetAddress.getByName("exitrightnow.com");
|
||||
Log.e(TAG, " exit address= "+exit.toString());
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "bad URL in testDnsPerf: " + e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean activeNetworkInfoIsProxy() {
|
||||
NetworkInfo info = mCm.getActiveNetworkInfo();
|
||||
if (PROXY_NETWORK_TYPE.equals(info.getTypeName())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ensureIpv6Connectivity() throws InterruptedException {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
final int TIMEOUT_MS = 5_000;
|
||||
|
||||
final NetworkCallback callback = new NetworkCallback() {
|
||||
@Override
|
||||
public void onLinkPropertiesChanged(Network network, LinkProperties lp) {
|
||||
if (lp.hasGlobalIpv6Address()) {
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
};
|
||||
mCm.registerDefaultNetworkCallback(callback);
|
||||
|
||||
String msg = "Default network did not provide IPv6 connectivity after " + TIMEOUT_MS
|
||||
+ "ms. Please connect to an IPv6-capable network. lp="
|
||||
+ mCm.getActiveLinkProperties();
|
||||
try {
|
||||
assertTrue(msg, latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
|
||||
} finally {
|
||||
mCm.unregisterNetworkCallback(callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
188
tests/cts/net/src/android/net/cts/IkeTunUtils.java
Normal file
188
tests/cts/net/src/android/net/cts/IkeTunUtils.java
Normal file
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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 android.net.cts;
|
||||
|
||||
import static android.net.cts.PacketUtils.BytePayload;
|
||||
import static android.net.cts.PacketUtils.IP4_HDRLEN;
|
||||
import static android.net.cts.PacketUtils.IP6_HDRLEN;
|
||||
import static android.net.cts.PacketUtils.IpHeader;
|
||||
import static android.net.cts.PacketUtils.UDP_HDRLEN;
|
||||
import static android.net.cts.PacketUtils.UdpHeader;
|
||||
import static android.net.cts.PacketUtils.getIpHeader;
|
||||
import static android.system.OsConstants.IPPROTO_UDP;
|
||||
|
||||
import android.os.ParcelFileDescriptor;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
|
||||
// TODO: Merge this with the version in the IPsec module (IKEv2 library) CTS tests.
|
||||
/** An extension of the TunUtils class with IKE-specific packet handling. */
|
||||
public class IkeTunUtils extends TunUtils {
|
||||
private static final int PORT_LEN = 2;
|
||||
|
||||
private static final byte[] NON_ESP_MARKER = new byte[] {0, 0, 0, 0};
|
||||
|
||||
private static final int IKE_HEADER_LEN = 28;
|
||||
private static final int IKE_SPI_LEN = 8;
|
||||
private static final int IKE_IS_RESP_BYTE_OFFSET = 19;
|
||||
private static final int IKE_MSG_ID_OFFSET = 20;
|
||||
private static final int IKE_MSG_ID_LEN = 4;
|
||||
|
||||
public IkeTunUtils(ParcelFileDescriptor tunFd) {
|
||||
super(tunFd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Await an expected IKE request and inject an IKE response.
|
||||
*
|
||||
* @param respIkePkt IKE response packet without IP/UDP headers or NON ESP MARKER.
|
||||
*/
|
||||
public byte[] awaitReqAndInjectResp(long expectedInitIkeSpi, int expectedMsgId,
|
||||
boolean encapExpected, byte[] respIkePkt) throws Exception {
|
||||
final byte[] request = awaitIkePacket(expectedInitIkeSpi, expectedMsgId, encapExpected);
|
||||
|
||||
// Build response header by flipping address and port
|
||||
final InetAddress srcAddr = getDstAddress(request);
|
||||
final InetAddress dstAddr = getSrcAddress(request);
|
||||
final int srcPort = getDstPort(request);
|
||||
final int dstPort = getSrcPort(request);
|
||||
|
||||
final byte[] response =
|
||||
buildIkePacket(srcAddr, dstAddr, srcPort, dstPort, encapExpected, respIkePkt);
|
||||
injectPacket(response);
|
||||
return request;
|
||||
}
|
||||
|
||||
private byte[] awaitIkePacket(long expectedInitIkeSpi, int expectedMsgId, boolean expectEncap)
|
||||
throws Exception {
|
||||
return super.awaitPacket(pkt -> isIke(pkt, expectedInitIkeSpi, expectedMsgId, expectEncap));
|
||||
}
|
||||
|
||||
private static boolean isIke(
|
||||
byte[] pkt, long expectedInitIkeSpi, int expectedMsgId, boolean encapExpected) {
|
||||
final int ipProtocolOffset;
|
||||
final int ikeOffset;
|
||||
|
||||
if (isIpv6(pkt)) {
|
||||
ipProtocolOffset = IP6_PROTO_OFFSET;
|
||||
ikeOffset = IP6_HDRLEN + UDP_HDRLEN;
|
||||
} else {
|
||||
if (encapExpected && !hasNonEspMarkerv4(pkt)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Use default IPv4 header length (assuming no options)
|
||||
final int encapMarkerLen = encapExpected ? NON_ESP_MARKER.length : 0;
|
||||
ipProtocolOffset = IP4_PROTO_OFFSET;
|
||||
ikeOffset = IP4_HDRLEN + UDP_HDRLEN + encapMarkerLen;
|
||||
}
|
||||
|
||||
return pkt[ipProtocolOffset] == IPPROTO_UDP
|
||||
&& areSpiAndMsgIdEqual(pkt, ikeOffset, expectedInitIkeSpi, expectedMsgId);
|
||||
}
|
||||
|
||||
/** Checks if the provided IPv4 packet has a UDP-encapsulation NON-ESP marker */
|
||||
private static boolean hasNonEspMarkerv4(byte[] ipv4Pkt) {
|
||||
final int nonEspMarkerOffset = IP4_HDRLEN + UDP_HDRLEN;
|
||||
if (ipv4Pkt.length < nonEspMarkerOffset + NON_ESP_MARKER.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final byte[] nonEspMarker = Arrays.copyOfRange(
|
||||
ipv4Pkt, nonEspMarkerOffset, nonEspMarkerOffset + NON_ESP_MARKER.length);
|
||||
return Arrays.equals(NON_ESP_MARKER, nonEspMarker);
|
||||
}
|
||||
|
||||
private static boolean areSpiAndMsgIdEqual(
|
||||
byte[] pkt, int ikeOffset, long expectedIkeInitSpi, int expectedMsgId) {
|
||||
if (pkt.length <= ikeOffset + IKE_HEADER_LEN) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final ByteBuffer buffer = ByteBuffer.wrap(pkt);
|
||||
final long spi = buffer.getLong(ikeOffset);
|
||||
final int msgId = buffer.getInt(ikeOffset + IKE_MSG_ID_OFFSET);
|
||||
|
||||
return expectedIkeInitSpi == spi && expectedMsgId == msgId;
|
||||
}
|
||||
|
||||
private static InetAddress getSrcAddress(byte[] pkt) throws Exception {
|
||||
return getAddress(pkt, true);
|
||||
}
|
||||
|
||||
private static InetAddress getDstAddress(byte[] pkt) throws Exception {
|
||||
return getAddress(pkt, false);
|
||||
}
|
||||
|
||||
private static InetAddress getAddress(byte[] pkt, boolean getSrcAddr) throws Exception {
|
||||
final int ipLen = isIpv6(pkt) ? IP6_ADDR_LEN : IP4_ADDR_LEN;
|
||||
final int srcIpOffset = isIpv6(pkt) ? IP6_ADDR_OFFSET : IP4_ADDR_OFFSET;
|
||||
final int ipOffset = getSrcAddr ? srcIpOffset : srcIpOffset + ipLen;
|
||||
|
||||
if (pkt.length < ipOffset + ipLen) {
|
||||
// Should be impossible; getAddress() is only called with a full IKE request including
|
||||
// the IP and UDP headers.
|
||||
throw new IllegalArgumentException("Packet was too short to contain IP address");
|
||||
}
|
||||
|
||||
return InetAddress.getByAddress(Arrays.copyOfRange(pkt, ipOffset, ipOffset + ipLen));
|
||||
}
|
||||
|
||||
private static int getSrcPort(byte[] pkt) throws Exception {
|
||||
return getPort(pkt, true);
|
||||
}
|
||||
|
||||
private static int getDstPort(byte[] pkt) throws Exception {
|
||||
return getPort(pkt, false);
|
||||
}
|
||||
|
||||
private static int getPort(byte[] pkt, boolean getSrcPort) {
|
||||
final int srcPortOffset = isIpv6(pkt) ? IP6_HDRLEN : IP4_HDRLEN;
|
||||
final int portOffset = getSrcPort ? srcPortOffset : srcPortOffset + PORT_LEN;
|
||||
|
||||
if (pkt.length < portOffset + PORT_LEN) {
|
||||
// Should be impossible; getPort() is only called with a full IKE request including the
|
||||
// IP and UDP headers.
|
||||
throw new IllegalArgumentException("Packet was too short to contain port");
|
||||
}
|
||||
|
||||
final ByteBuffer buffer = ByteBuffer.wrap(pkt);
|
||||
return Short.toUnsignedInt(buffer.getShort(portOffset));
|
||||
}
|
||||
|
||||
private static byte[] buildIkePacket(
|
||||
InetAddress srcAddr,
|
||||
InetAddress dstAddr,
|
||||
int srcPort,
|
||||
int dstPort,
|
||||
boolean useEncap,
|
||||
byte[] payload)
|
||||
throws Exception {
|
||||
// Append non-ESP marker if encap is enabled
|
||||
if (useEncap) {
|
||||
final ByteBuffer buffer = ByteBuffer.allocate(NON_ESP_MARKER.length + payload.length);
|
||||
buffer.put(NON_ESP_MARKER);
|
||||
buffer.put(payload);
|
||||
payload = buffer.array();
|
||||
}
|
||||
|
||||
final UdpHeader udpPkt = new UdpHeader(srcPort, dstPort, new BytePayload(payload));
|
||||
final IpHeader ipPkt = getIpHeader(udpPkt.getProtocolId(), srcAddr, dstAddr, udpPkt);
|
||||
return ipPkt.getPacketBytes();
|
||||
}
|
||||
}
|
||||
535
tests/cts/net/src/android/net/cts/Ikev2VpnTest.java
Normal file
535
tests/cts/net/src/android/net/cts/Ikev2VpnTest.java
Normal file
@@ -0,0 +1,535 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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 android.net.cts;
|
||||
|
||||
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
|
||||
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
|
||||
import static android.net.cts.util.CtsNetUtils.TestNetworkCallback;
|
||||
|
||||
import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.NonNull;
|
||||
import android.app.AppOpsManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.Ikev2VpnProfile;
|
||||
import android.net.IpSecAlgorithm;
|
||||
import android.net.LinkAddress;
|
||||
import android.net.Network;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.NetworkRequest;
|
||||
import android.net.ProxyInfo;
|
||||
import android.net.TestNetworkInterface;
|
||||
import android.net.TestNetworkManager;
|
||||
import android.net.VpnManager;
|
||||
import android.net.cts.util.CtsNetUtils;
|
||||
import android.os.Build;
|
||||
import android.os.Process;
|
||||
import android.platform.test.annotations.AppModeFull;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
|
||||
import com.android.internal.util.HexDump;
|
||||
import com.android.org.bouncycastle.x509.X509V1CertificateGenerator;
|
||||
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
|
||||
import com.android.testutils.DevSdkIgnoreRunner;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.net.InetAddress;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
|
||||
@RunWith(DevSdkIgnoreRunner.class)
|
||||
@IgnoreUpTo(Build.VERSION_CODES.Q)
|
||||
@AppModeFull(reason = "Appops state changes disallowed for instant apps (OP_ACTIVATE_PLATFORM_VPN)")
|
||||
public class Ikev2VpnTest {
|
||||
private static final String TAG = Ikev2VpnTest.class.getSimpleName();
|
||||
|
||||
// Test vectors for IKE negotiation in test mode.
|
||||
private static final String SUCCESSFUL_IKE_INIT_RESP_V4 =
|
||||
"46b8eca1e0d72a18b2b5d9006d47a0022120222000000000000002d0220000300000002c01010004030000"
|
||||
+ "0c0100000c800e0100030000080300000c030000080200000400000008040000102800020800"
|
||||
+ "100000b8070f159fe5141d8754ca86f72ecc28d66f514927e96cbe9eec0adb42bf2c276a0ab7"
|
||||
+ "a97fa93555f4be9218c14e7f286bb28c6b4fb13825a420f2ffc165854f200bab37d69c8963d4"
|
||||
+ "0acb831d983163aa50622fd35c182efe882cf54d6106222abcfaa597255d302f1b95ab71c142"
|
||||
+ "c279ea5839a180070bff73f9d03fab815f0d5ee2adec7e409d1e35979f8bd92ffd8aab13d1a0"
|
||||
+ "0657d816643ae767e9ae84d2ccfa2bcce1a50572be8d3748ae4863c41ae90da16271e014270f"
|
||||
+ "77edd5cd2e3299f3ab27d7203f93d770bacf816041cdcecd0f9af249033979da4369cb242dd9"
|
||||
+ "6d172e60513ff3db02de63e50eb7d7f596ada55d7946cad0af0669d1f3e2804846ab3f2a930d"
|
||||
+ "df56f7f025f25c25ada694e6231abbb87ee8cfd072c8481dc0b0f6b083fdc3bd89b080e49feb"
|
||||
+ "0288eef6fdf8a26ee2fc564a11e7385215cf2deaf2a9965638fc279c908ccdf04094988d91a2"
|
||||
+ "464b4a8c0326533aff5119ed79ecbd9d99a218b44f506a5eb09351e67da86698b4c58718db25"
|
||||
+ "d55f426fb4c76471b27a41fbce00777bc233c7f6e842e39146f466826de94f564cad8b92bfbe"
|
||||
+ "87c99c4c7973ec5f1eea8795e7da82819753aa7c4fcfdab77066c56b939330c4b0d354c23f83"
|
||||
+ "ea82fa7a64c4b108f1188379ea0eb4918ee009d804100e6bf118771b9058d42141c847d5ec37"
|
||||
+ "6e5ec591c71fc9dac01063c2bd31f9c783b28bf1182900002430f3d5de3449462b31dd28bc27"
|
||||
+ "297b6ad169bccce4f66c5399c6e0be9120166f2900001c0000400428b8df2e66f69c8584a186"
|
||||
+ "c5eac66783551d49b72900001c000040054e7a622e802d5cbfb96d5f30a6e433994370173529"
|
||||
+ "0000080000402e290000100000402f00020003000400050000000800004014";
|
||||
private static final String SUCCESSFUL_IKE_INIT_RESP_V6 =
|
||||
"46b8eca1e0d72a1800d9ea1babce26bf2120222000000000000002d0220000300000002c01010004030000"
|
||||
+ "0c0100000c800e0100030000080300000c030000080200000400000008040000102800020800"
|
||||
+ "100000ea0e6dd9ca5930a9a45c323a41f64bfd8cdef7730f5fbff37d7c377da427f489a42aa8"
|
||||
+ "c89233380e6e925990d49de35c2cdcf63a61302c731a4b3569df1ee1bf2457e55a6751838ede"
|
||||
+ "abb75cc63ba5c9e4355e8e784f383a5efe8a44727dc14aeaf8dacc2620fb1c8875416dc07739"
|
||||
+ "7fe4decc1bd514a9c7d270cf21fd734c63a25c34b30b68686e54e8a198f37f27cb491fe27235"
|
||||
+ "fab5476b036d875ccab9a68d65fbf3006197f9bebbf94de0d3802b4fafe1d48d931ce3a1a346"
|
||||
+ "2d65bd639e9bd7fa46299650a9dbaf9b324e40b466942d91a59f41ef8042f8474c4850ed0f63"
|
||||
+ "e9238949d41cd8bbaea9aefdb65443a6405792839563aa5dc5c36b5ce8326ccf8a94d9622b85"
|
||||
+ "038d390d5fc0299e14e1f022966d4ac66515f6108ca04faec44821fe5bbf2ed4f84ff5671219"
|
||||
+ "608cb4c36b44a31ba010c9088f8d5ff943bb9ff857f74be1755f57a5783874adc57f42bb174e"
|
||||
+ "4ad3215de628707014dbcb1707bd214658118fdd7a42b3e1638b991ce5b812a667f1145be811"
|
||||
+ "685e3cd3baf9b18d062657b64c206a4d19a531c252a6a51a04aeaf42c618620cdbab65baca23"
|
||||
+ "82c57ed888422aeaacf7f1bc3fe2247ff7e7eaca218b74d7b31d02f2b0afa123f802529e7e6c"
|
||||
+ "3259d418290740ddbf55686e26998d7edcbbf895664972fed666f2f20af40503aa2af436ec6d"
|
||||
+ "4ec981ab19b9088755d94ae7a7c2066ea331d4e56e290000243fefe5555fce552d57a84e682c"
|
||||
+ "d4a6dfb3f2f94a94464d5bec3d88b88e9559642900001c00004004eb4afff764e7b79bca78b1"
|
||||
+ "3a89100d36d678ae982900001c00004005d177216a3c26f782076e12570d40bfaaa148822929"
|
||||
+ "0000080000402e290000100000402f00020003000400050000000800004014";
|
||||
private static final String SUCCESSFUL_IKE_AUTH_RESP_V4 =
|
||||
"46b8eca1e0d72a18b2b5d9006d47a0022e20232000000001000000e0240000c420a2500a3da4c66fa6929e"
|
||||
+ "600f36349ba0e38de14f78a3ad0416cba8c058735712a3d3f9a0a6ed36de09b5e9e02697e7c4"
|
||||
+ "2d210ac86cfbd709503cfa51e2eab8cfdc6427d136313c072968f6506a546eb5927164200592"
|
||||
+ "6e36a16ee994e63f029432a67bc7d37ca619e1bd6e1678df14853067ecf816b48b81e8746069"
|
||||
+ "406363e5aa55f13cb2afda9dbebee94256c29d630b17dd7f1ee52351f92b6e1c3d8551c513f1"
|
||||
+ "d74ac52a80b2041397e109fe0aeb3c105b0d4be0ae343a943398764281";
|
||||
private static final String SUCCESSFUL_IKE_AUTH_RESP_V6 =
|
||||
"46b8eca1e0d72a1800d9ea1babce26bf2e20232000000001000000f0240000d4aaf6eaa6c06b50447e6f54"
|
||||
+ "827fd8a9d9d6ac8015c1ebb3e8cb03fc6e54b49a107441f50004027cc5021600828026367f03"
|
||||
+ "bc425821cd7772ee98637361300c9b76056e874fea2bd4a17212370b291894264d8c023a01d1"
|
||||
+ "c3b691fd4b7c0b534e8c95af4c4638e2d125cb21c6267e2507cd745d72e8da109c47b9259c6c"
|
||||
+ "57a26f6bc5b337b9b9496d54bdde0333d7a32e6e1335c9ee730c3ecd607a8689aa7b0577b74f"
|
||||
+ "3bf437696a9fd5fc0aee3ed346cd9e15d1dda293df89eb388a8719388a60ca7625754de12cdb"
|
||||
+ "efe4c886c5c401";
|
||||
private static final long IKE_INITIATOR_SPI = Long.parseLong("46B8ECA1E0D72A18", 16);
|
||||
|
||||
private static final InetAddress LOCAL_OUTER_4 = InetAddress.parseNumericAddress("192.0.2.1");
|
||||
private static final InetAddress LOCAL_OUTER_6 =
|
||||
InetAddress.parseNumericAddress("2001:db8::1");
|
||||
|
||||
private static final int IP4_PREFIX_LEN = 32;
|
||||
private static final int IP6_PREFIX_LEN = 128;
|
||||
|
||||
// TODO: Use IPv6 address when we can generate test vectors (GCE does not allow IPv6 yet).
|
||||
private static final String TEST_SERVER_ADDR_V4 = "192.0.2.2";
|
||||
private static final String TEST_SERVER_ADDR_V6 = "2001:db8::2";
|
||||
private static final String TEST_IDENTITY = "client.cts.android.com";
|
||||
private static final List<String> TEST_ALLOWED_ALGORITHMS =
|
||||
Arrays.asList(IpSecAlgorithm.AUTH_CRYPT_AES_GCM);
|
||||
|
||||
private static final ProxyInfo TEST_PROXY_INFO =
|
||||
ProxyInfo.buildDirectProxy("proxy.cts.android.com", 1234);
|
||||
private static final int TEST_MTU = 1300;
|
||||
|
||||
private static final byte[] TEST_PSK = "ikeAndroidPsk".getBytes();
|
||||
private static final String TEST_USER = "username";
|
||||
private static final String TEST_PASSWORD = "pa55w0rd";
|
||||
|
||||
// Static state to reduce setup/teardown
|
||||
private static final Context sContext = InstrumentationRegistry.getContext();
|
||||
private static final ConnectivityManager sCM =
|
||||
(ConnectivityManager) sContext.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
private static final VpnManager sVpnMgr =
|
||||
(VpnManager) sContext.getSystemService(Context.VPN_MANAGEMENT_SERVICE);
|
||||
private static final CtsNetUtils mCtsNetUtils = new CtsNetUtils(sContext);
|
||||
|
||||
private final X509Certificate mServerRootCa;
|
||||
private final CertificateAndKey mUserCertKey;
|
||||
|
||||
public Ikev2VpnTest() throws Exception {
|
||||
// Build certificates
|
||||
mServerRootCa = generateRandomCertAndKeyPair().cert;
|
||||
mUserCertKey = generateRandomCertAndKeyPair();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
setAppop(AppOpsManager.OP_ACTIVATE_VPN, false);
|
||||
setAppop(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the given appop using shell commands
|
||||
*
|
||||
* <p>This method must NEVER be called from within a shell permission, as it will attempt to
|
||||
* acquire, and then drop the shell permission identity. This results in the caller losing the
|
||||
* shell permission identity due to these calls not being reference counted.
|
||||
*/
|
||||
public void setAppop(int appop, boolean allow) {
|
||||
// Requires shell permission to update appops.
|
||||
runWithShellPermissionIdentity(() -> {
|
||||
mCtsNetUtils.setAppopPrivileged(appop, allow);
|
||||
}, Manifest.permission.MANAGE_TEST_NETWORKS);
|
||||
}
|
||||
|
||||
private Ikev2VpnProfile buildIkev2VpnProfileCommon(
|
||||
Ikev2VpnProfile.Builder builder, boolean isRestrictedToTestNetworks) throws Exception {
|
||||
if (isRestrictedToTestNetworks) {
|
||||
builder.restrictToTestNetworks();
|
||||
}
|
||||
|
||||
return builder.setBypassable(true)
|
||||
.setAllowedAlgorithms(TEST_ALLOWED_ALGORITHMS)
|
||||
.setProxy(TEST_PROXY_INFO)
|
||||
.setMaxMtu(TEST_MTU)
|
||||
.setMetered(false)
|
||||
.build();
|
||||
}
|
||||
|
||||
private Ikev2VpnProfile buildIkev2VpnProfilePsk(boolean isRestrictedToTestNetworks)
|
||||
throws Exception {
|
||||
return buildIkev2VpnProfilePsk(TEST_SERVER_ADDR_V6, isRestrictedToTestNetworks);
|
||||
}
|
||||
|
||||
private Ikev2VpnProfile buildIkev2VpnProfilePsk(
|
||||
String remote, boolean isRestrictedToTestNetworks) throws Exception {
|
||||
final Ikev2VpnProfile.Builder builder =
|
||||
new Ikev2VpnProfile.Builder(remote, TEST_IDENTITY).setAuthPsk(TEST_PSK);
|
||||
|
||||
return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks);
|
||||
}
|
||||
|
||||
private Ikev2VpnProfile buildIkev2VpnProfileUsernamePassword(boolean isRestrictedToTestNetworks)
|
||||
throws Exception {
|
||||
final Ikev2VpnProfile.Builder builder =
|
||||
new Ikev2VpnProfile.Builder(TEST_SERVER_ADDR_V6, TEST_IDENTITY)
|
||||
.setAuthUsernamePassword(TEST_USER, TEST_PASSWORD, mServerRootCa);
|
||||
|
||||
return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks);
|
||||
}
|
||||
|
||||
private Ikev2VpnProfile buildIkev2VpnProfileDigitalSignature(boolean isRestrictedToTestNetworks)
|
||||
throws Exception {
|
||||
final Ikev2VpnProfile.Builder builder =
|
||||
new Ikev2VpnProfile.Builder(TEST_SERVER_ADDR_V6, TEST_IDENTITY)
|
||||
.setAuthDigitalSignature(
|
||||
mUserCertKey.cert, mUserCertKey.key, mServerRootCa);
|
||||
|
||||
return buildIkev2VpnProfileCommon(builder, isRestrictedToTestNetworks);
|
||||
}
|
||||
|
||||
private void checkBasicIkev2VpnProfile(@NonNull Ikev2VpnProfile profile) throws Exception {
|
||||
assertEquals(TEST_SERVER_ADDR_V6, profile.getServerAddr());
|
||||
assertEquals(TEST_IDENTITY, profile.getUserIdentity());
|
||||
assertEquals(TEST_PROXY_INFO, profile.getProxyInfo());
|
||||
assertEquals(TEST_ALLOWED_ALGORITHMS, profile.getAllowedAlgorithms());
|
||||
assertTrue(profile.isBypassable());
|
||||
assertFalse(profile.isMetered());
|
||||
assertEquals(TEST_MTU, profile.getMaxMtu());
|
||||
assertFalse(profile.isRestrictedToTestNetworks());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildIkev2VpnProfilePsk() throws Exception {
|
||||
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
|
||||
|
||||
final Ikev2VpnProfile profile =
|
||||
buildIkev2VpnProfilePsk(false /* isRestrictedToTestNetworks */);
|
||||
|
||||
checkBasicIkev2VpnProfile(profile);
|
||||
assertArrayEquals(TEST_PSK, profile.getPresharedKey());
|
||||
|
||||
// Verify nothing else is set.
|
||||
assertNull(profile.getUsername());
|
||||
assertNull(profile.getPassword());
|
||||
assertNull(profile.getServerRootCaCert());
|
||||
assertNull(profile.getRsaPrivateKey());
|
||||
assertNull(profile.getUserCert());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildIkev2VpnProfileUsernamePassword() throws Exception {
|
||||
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
|
||||
|
||||
final Ikev2VpnProfile profile =
|
||||
buildIkev2VpnProfileUsernamePassword(false /* isRestrictedToTestNetworks */);
|
||||
|
||||
checkBasicIkev2VpnProfile(profile);
|
||||
assertEquals(TEST_USER, profile.getUsername());
|
||||
assertEquals(TEST_PASSWORD, profile.getPassword());
|
||||
assertEquals(mServerRootCa, profile.getServerRootCaCert());
|
||||
|
||||
// Verify nothing else is set.
|
||||
assertNull(profile.getPresharedKey());
|
||||
assertNull(profile.getRsaPrivateKey());
|
||||
assertNull(profile.getUserCert());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildIkev2VpnProfileDigitalSignature() throws Exception {
|
||||
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
|
||||
|
||||
final Ikev2VpnProfile profile =
|
||||
buildIkev2VpnProfileDigitalSignature(false /* isRestrictedToTestNetworks */);
|
||||
|
||||
checkBasicIkev2VpnProfile(profile);
|
||||
assertEquals(mUserCertKey.cert, profile.getUserCert());
|
||||
assertEquals(mUserCertKey.key, profile.getRsaPrivateKey());
|
||||
assertEquals(mServerRootCa, profile.getServerRootCaCert());
|
||||
|
||||
// Verify nothing else is set.
|
||||
assertNull(profile.getUsername());
|
||||
assertNull(profile.getPassword());
|
||||
assertNull(profile.getPresharedKey());
|
||||
}
|
||||
|
||||
private void verifyProvisionVpnProfile(
|
||||
boolean hasActivateVpn, boolean hasActivatePlatformVpn, boolean expectIntent)
|
||||
throws Exception {
|
||||
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
|
||||
|
||||
setAppop(AppOpsManager.OP_ACTIVATE_VPN, hasActivateVpn);
|
||||
setAppop(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, hasActivatePlatformVpn);
|
||||
|
||||
final Ikev2VpnProfile profile =
|
||||
buildIkev2VpnProfilePsk(false /* isRestrictedToTestNetworks */);
|
||||
final Intent intent = sVpnMgr.provisionVpnProfile(profile);
|
||||
assertEquals(expectIntent, intent != null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProvisionVpnProfileNoPreviousConsent() throws Exception {
|
||||
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
|
||||
|
||||
verifyProvisionVpnProfile(false /* hasActivateVpn */,
|
||||
false /* hasActivatePlatformVpn */, true /* expectIntent */);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProvisionVpnProfilePlatformVpnConsented() throws Exception {
|
||||
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
|
||||
|
||||
verifyProvisionVpnProfile(false /* hasActivateVpn */,
|
||||
true /* hasActivatePlatformVpn */, false /* expectIntent */);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProvisionVpnProfileVpnServiceConsented() throws Exception {
|
||||
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
|
||||
|
||||
verifyProvisionVpnProfile(true /* hasActivateVpn */,
|
||||
false /* hasActivatePlatformVpn */, false /* expectIntent */);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProvisionVpnProfileAllPreConsented() throws Exception {
|
||||
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
|
||||
|
||||
verifyProvisionVpnProfile(true /* hasActivateVpn */,
|
||||
true /* hasActivatePlatformVpn */, false /* expectIntent */);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteVpnProfile() throws Exception {
|
||||
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
|
||||
|
||||
setAppop(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, true);
|
||||
|
||||
final Ikev2VpnProfile profile =
|
||||
buildIkev2VpnProfilePsk(false /* isRestrictedToTestNetworks */);
|
||||
assertNull(sVpnMgr.provisionVpnProfile(profile));
|
||||
|
||||
// Verify that deleting the profile works (even without the appop)
|
||||
setAppop(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, false);
|
||||
sVpnMgr.deleteProvisionedVpnProfile();
|
||||
|
||||
// Test that the profile was deleted - starting it should throw an IAE.
|
||||
try {
|
||||
setAppop(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, true);
|
||||
sVpnMgr.startProvisionedVpnProfile();
|
||||
fail("Expected IllegalArgumentException due to missing profile");
|
||||
} catch (IllegalArgumentException expected) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartVpnProfileNoPreviousConsent() throws Exception {
|
||||
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
|
||||
|
||||
setAppop(AppOpsManager.OP_ACTIVATE_VPN, false);
|
||||
setAppop(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, false);
|
||||
|
||||
// Make sure the VpnProfile is not provisioned already.
|
||||
sVpnMgr.stopProvisionedVpnProfile();
|
||||
|
||||
try {
|
||||
sVpnMgr.startProvisionedVpnProfile();
|
||||
fail("Expected SecurityException for missing consent");
|
||||
} catch (SecurityException expected) {
|
||||
}
|
||||
}
|
||||
|
||||
private void checkStartStopVpnProfileBuildsNetworks(IkeTunUtils tunUtils, boolean testIpv6)
|
||||
throws Exception {
|
||||
String serverAddr = testIpv6 ? TEST_SERVER_ADDR_V6 : TEST_SERVER_ADDR_V4;
|
||||
String initResp = testIpv6 ? SUCCESSFUL_IKE_INIT_RESP_V6 : SUCCESSFUL_IKE_INIT_RESP_V4;
|
||||
String authResp = testIpv6 ? SUCCESSFUL_IKE_AUTH_RESP_V6 : SUCCESSFUL_IKE_AUTH_RESP_V4;
|
||||
boolean hasNat = !testIpv6;
|
||||
|
||||
// Requires MANAGE_TEST_NETWORKS to provision a test-mode profile.
|
||||
mCtsNetUtils.setAppopPrivileged(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, true);
|
||||
|
||||
final Ikev2VpnProfile profile =
|
||||
buildIkev2VpnProfilePsk(serverAddr, true /* isRestrictedToTestNetworks */);
|
||||
assertNull(sVpnMgr.provisionVpnProfile(profile));
|
||||
|
||||
sVpnMgr.startProvisionedVpnProfile();
|
||||
|
||||
// Inject IKE negotiation
|
||||
int expectedMsgId = 0;
|
||||
tunUtils.awaitReqAndInjectResp(IKE_INITIATOR_SPI, expectedMsgId++, false /* isEncap */,
|
||||
HexDump.hexStringToByteArray(initResp));
|
||||
tunUtils.awaitReqAndInjectResp(IKE_INITIATOR_SPI, expectedMsgId++, hasNat /* isEncap */,
|
||||
HexDump.hexStringToByteArray(authResp));
|
||||
|
||||
// Verify the VPN network came up
|
||||
final NetworkRequest nr = new NetworkRequest.Builder()
|
||||
.clearCapabilities().addTransportType(TRANSPORT_VPN).build();
|
||||
|
||||
final TestNetworkCallback cb = new TestNetworkCallback();
|
||||
sCM.requestNetwork(nr, cb);
|
||||
cb.waitForAvailable();
|
||||
final Network vpnNetwork = cb.currentNetwork;
|
||||
assertNotNull(vpnNetwork);
|
||||
|
||||
final NetworkCapabilities caps = sCM.getNetworkCapabilities(vpnNetwork);
|
||||
assertTrue(caps.hasTransport(TRANSPORT_VPN));
|
||||
assertTrue(caps.hasCapability(NET_CAPABILITY_INTERNET));
|
||||
assertEquals(Process.myUid(), caps.getOwnerUid());
|
||||
|
||||
sVpnMgr.stopProvisionedVpnProfile();
|
||||
cb.waitForLost();
|
||||
assertEquals(vpnNetwork, cb.lastLostNetwork);
|
||||
}
|
||||
|
||||
private void doTestStartStopVpnProfile(boolean testIpv6) throws Exception {
|
||||
// Non-final; these variables ensure we clean up properly after our test if we have
|
||||
// allocated test network resources
|
||||
final TestNetworkManager tnm = sContext.getSystemService(TestNetworkManager.class);
|
||||
TestNetworkInterface testIface = null;
|
||||
TestNetworkCallback tunNetworkCallback = null;
|
||||
|
||||
try {
|
||||
// Build underlying test network
|
||||
testIface = tnm.createTunInterface(
|
||||
new LinkAddress[] {
|
||||
new LinkAddress(LOCAL_OUTER_4, IP4_PREFIX_LEN),
|
||||
new LinkAddress(LOCAL_OUTER_6, IP6_PREFIX_LEN)});
|
||||
|
||||
// Hold on to this callback to ensure network does not get reaped.
|
||||
tunNetworkCallback = mCtsNetUtils.setupAndGetTestNetwork(
|
||||
testIface.getInterfaceName());
|
||||
final IkeTunUtils tunUtils = new IkeTunUtils(testIface.getFileDescriptor());
|
||||
|
||||
checkStartStopVpnProfileBuildsNetworks(tunUtils, testIpv6);
|
||||
} finally {
|
||||
// Make sure to stop the VPN profile. This is safe to call multiple times.
|
||||
sVpnMgr.stopProvisionedVpnProfile();
|
||||
|
||||
if (testIface != null) {
|
||||
testIface.getFileDescriptor().close();
|
||||
}
|
||||
|
||||
if (tunNetworkCallback != null) {
|
||||
sCM.unregisterNetworkCallback(tunNetworkCallback);
|
||||
}
|
||||
|
||||
final Network testNetwork = tunNetworkCallback.currentNetwork;
|
||||
if (testNetwork != null) {
|
||||
tnm.teardownTestNetwork(testNetwork);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartStopVpnProfileV4() throws Exception {
|
||||
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
|
||||
|
||||
// Requires shell permission to update appops.
|
||||
runWithShellPermissionIdentity(() -> {
|
||||
doTestStartStopVpnProfile(false);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartStopVpnProfileV6() throws Exception {
|
||||
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
|
||||
|
||||
// Requires shell permission to update appops.
|
||||
runWithShellPermissionIdentity(() -> {
|
||||
doTestStartStopVpnProfile(true);
|
||||
});
|
||||
}
|
||||
|
||||
private static class CertificateAndKey {
|
||||
public final X509Certificate cert;
|
||||
public final PrivateKey key;
|
||||
|
||||
CertificateAndKey(X509Certificate cert, PrivateKey key) {
|
||||
this.cert = cert;
|
||||
this.key = key;
|
||||
}
|
||||
}
|
||||
|
||||
private static CertificateAndKey generateRandomCertAndKeyPair() throws Exception {
|
||||
final Date validityBeginDate =
|
||||
new Date(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1L));
|
||||
final Date validityEndDate =
|
||||
new Date(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1L));
|
||||
|
||||
// Generate a keypair
|
||||
final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
|
||||
keyPairGenerator.initialize(512);
|
||||
final KeyPair keyPair = keyPairGenerator.generateKeyPair();
|
||||
|
||||
final X500Principal dnName = new X500Principal("CN=test.android.com");
|
||||
final X509V1CertificateGenerator certGen = new X509V1CertificateGenerator();
|
||||
certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
|
||||
certGen.setSubjectDN(dnName);
|
||||
certGen.setIssuerDN(dnName);
|
||||
certGen.setNotBefore(validityBeginDate);
|
||||
certGen.setNotAfter(validityEndDate);
|
||||
certGen.setPublicKey(keyPair.getPublic());
|
||||
certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");
|
||||
|
||||
final X509Certificate cert = certGen.generate(keyPair.getPrivate(), "AndroidOpenSSL");
|
||||
return new CertificateAndKey(cert, keyPair.getPrivate());
|
||||
}
|
||||
}
|
||||
134
tests/cts/net/src/android/net/cts/InetAddressesTest.java
Normal file
134
tests/cts/net/src/android/net/cts/InetAddressesTest.java
Normal file
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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 android.net.cts;
|
||||
|
||||
import android.net.InetAddresses;
|
||||
import java.net.InetAddress;
|
||||
import junitparams.JUnitParamsRunner;
|
||||
import junitparams.Parameters;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
@RunWith(JUnitParamsRunner.class)
|
||||
public class InetAddressesTest {
|
||||
|
||||
public static String[][] validNumericAddressesAndStringRepresentation() {
|
||||
return new String[][] {
|
||||
// Regular IPv4.
|
||||
{ "1.2.3.4", "1.2.3.4" },
|
||||
|
||||
// Regular IPv6.
|
||||
{ "2001:4860:800d::68", "2001:4860:800d::68" },
|
||||
{ "1234:5678::9ABC:DEF0", "1234:5678::9abc:def0" },
|
||||
{ "2001:cdba:9abc:5678::", "2001:cdba:9abc:5678::" },
|
||||
{ "::2001:cdba:9abc:5678", "::2001:cdba:9abc:5678" },
|
||||
{ "64:ff9b::1.2.3.4", "64:ff9b::102:304" },
|
||||
|
||||
{ "::9abc:5678", "::154.188.86.120" },
|
||||
|
||||
// Mapped IPv4
|
||||
{ "::ffff:127.0.0.1", "127.0.0.1" },
|
||||
|
||||
// Android does not recognize Octal (leading 0) cases: they are treated as decimal.
|
||||
{ "0177.00.00.01", "177.0.0.1" },
|
||||
|
||||
// Verify that examples from JavaDoc work correctly.
|
||||
{ "192.0.2.1", "192.0.2.1" },
|
||||
{ "2001:db8::1:2", "2001:db8::1:2" },
|
||||
};
|
||||
}
|
||||
|
||||
public static String[] invalidNumericAddresses() {
|
||||
return new String[] {
|
||||
"",
|
||||
" ",
|
||||
"\t",
|
||||
"\n",
|
||||
"1.2.3.4.",
|
||||
"1.2.3",
|
||||
"1.2",
|
||||
"1",
|
||||
"1234",
|
||||
"0",
|
||||
"0x1.0x2.0x3.0x4",
|
||||
"0x7f.0x00.0x00.0x01",
|
||||
"0256.00.00.01",
|
||||
"fred",
|
||||
"www.google.com",
|
||||
// IPv6 encoded for use in URL as defined in RFC 2732
|
||||
"[fe80::6:2222]",
|
||||
};
|
||||
}
|
||||
|
||||
@Parameters(method = "validNumericAddressesAndStringRepresentation")
|
||||
@Test
|
||||
public void parseNumericAddress(String address, String expectedString) {
|
||||
InetAddress inetAddress = InetAddresses.parseNumericAddress(address);
|
||||
assertEquals(expectedString, inetAddress.getHostAddress());
|
||||
}
|
||||
|
||||
@Parameters(method = "invalidNumericAddresses")
|
||||
@Test
|
||||
public void test_parseNonNumericAddress(String address) {
|
||||
try {
|
||||
InetAddress inetAddress = InetAddresses.parseNumericAddress(address);
|
||||
fail(String.format(
|
||||
"Address %s is not numeric but was parsed as %s", address, inetAddress));
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertThat(e.getMessage()).contains(address);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_parseNumericAddress_null() {
|
||||
try {
|
||||
InetAddress inetAddress = InetAddresses.parseNumericAddress(null);
|
||||
fail(String.format("null is not numeric but was parsed as %s", inetAddress));
|
||||
} catch (NullPointerException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
@Parameters(method = "validNumericAddressesAndStringRepresentation")
|
||||
@Test
|
||||
public void test_isNumericAddress(String address, String unused) {
|
||||
assertTrue("expected '" + address + "' to be treated as numeric",
|
||||
InetAddresses.isNumericAddress(address));
|
||||
}
|
||||
|
||||
@Parameters(method = "invalidNumericAddresses")
|
||||
@Test
|
||||
public void test_isNotNumericAddress(String address) {
|
||||
assertFalse("expected '" + address + "' to be treated as non-numeric",
|
||||
InetAddresses.isNumericAddress(address));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_isNumericAddress_null() {
|
||||
try {
|
||||
InetAddresses.isNumericAddress(null);
|
||||
fail("expected null to throw a NullPointerException");
|
||||
} catch (NullPointerException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
}
|
||||
123
tests/cts/net/src/android/net/cts/IpConfigurationTest.java
Normal file
123
tests/cts/net/src/android/net/cts/IpConfigurationTest.java
Normal file
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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 android.net.cts;
|
||||
|
||||
import static com.android.testutils.ParcelUtils.assertParcelSane;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
import android.net.IpConfiguration;
|
||||
import android.net.LinkAddress;
|
||||
import android.net.ProxyInfo;
|
||||
import android.net.StaticIpConfiguration;
|
||||
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import libcore.net.InetAddressUtils;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.ArrayList;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public final class IpConfigurationTest {
|
||||
private static final LinkAddress LINKADDR = new LinkAddress("192.0.2.2/25");
|
||||
private static final InetAddress GATEWAY = InetAddressUtils.parseNumericAddress("192.0.2.1");
|
||||
private static final InetAddress DNS1 = InetAddressUtils.parseNumericAddress("8.8.8.8");
|
||||
private static final InetAddress DNS2 = InetAddressUtils.parseNumericAddress("8.8.4.4");
|
||||
private static final String DOMAINS = "example.com";
|
||||
|
||||
private static final ArrayList<InetAddress> dnsServers = new ArrayList<>();
|
||||
|
||||
private StaticIpConfiguration mStaticIpConfig;
|
||||
private ProxyInfo mProxy;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
dnsServers.add(DNS1);
|
||||
dnsServers.add(DNS2);
|
||||
mStaticIpConfig = new StaticIpConfiguration.Builder()
|
||||
.setIpAddress(LINKADDR)
|
||||
.setGateway(GATEWAY)
|
||||
.setDnsServers(dnsServers)
|
||||
.setDomains(DOMAINS)
|
||||
.build();
|
||||
|
||||
mProxy = ProxyInfo.buildDirectProxy("test", 8888);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructor() {
|
||||
IpConfiguration ipConfig = new IpConfiguration();
|
||||
checkEmpty(ipConfig);
|
||||
assertIpConfigurationEqual(ipConfig, new IpConfiguration());
|
||||
assertIpConfigurationEqual(ipConfig, new IpConfiguration(ipConfig));
|
||||
|
||||
ipConfig.setStaticIpConfiguration(mStaticIpConfig);
|
||||
ipConfig.setHttpProxy(mProxy);
|
||||
|
||||
ipConfig.setIpAssignment(IpConfiguration.IpAssignment.STATIC);
|
||||
ipConfig.setProxySettings(IpConfiguration.ProxySettings.PAC);
|
||||
assertIpConfigurationEqual(ipConfig, new IpConfiguration(ipConfig));
|
||||
|
||||
ipConfig.setIpAssignment(IpConfiguration.IpAssignment.STATIC);
|
||||
ipConfig.setProxySettings(IpConfiguration.ProxySettings.STATIC);
|
||||
assertIpConfigurationEqual(ipConfig, new IpConfiguration(ipConfig));
|
||||
|
||||
ipConfig.setIpAssignment(IpConfiguration.IpAssignment.DHCP);
|
||||
ipConfig.setProxySettings(IpConfiguration.ProxySettings.PAC);
|
||||
assertIpConfigurationEqual(ipConfig, new IpConfiguration(ipConfig));
|
||||
|
||||
ipConfig.setIpAssignment(IpConfiguration.IpAssignment.DHCP);
|
||||
ipConfig.setProxySettings(IpConfiguration.ProxySettings.PAC);
|
||||
assertIpConfigurationEqual(ipConfig, new IpConfiguration(ipConfig));
|
||||
|
||||
ipConfig.setIpAssignment(IpConfiguration.IpAssignment.DHCP);
|
||||
ipConfig.setProxySettings(IpConfiguration.ProxySettings.STATIC);
|
||||
assertIpConfigurationEqual(ipConfig, new IpConfiguration(ipConfig));
|
||||
|
||||
ipConfig.setIpAssignment(IpConfiguration.IpAssignment.DHCP);
|
||||
ipConfig.setProxySettings(IpConfiguration.ProxySettings.NONE);
|
||||
assertIpConfigurationEqual(ipConfig, new IpConfiguration(ipConfig));
|
||||
}
|
||||
|
||||
private void checkEmpty(IpConfiguration config) {
|
||||
assertEquals(IpConfiguration.IpAssignment.UNASSIGNED,
|
||||
config.getIpAssignment().UNASSIGNED);
|
||||
assertEquals(IpConfiguration.ProxySettings.UNASSIGNED,
|
||||
config.getProxySettings().UNASSIGNED);
|
||||
assertNull(config.getStaticIpConfiguration());
|
||||
assertNull(config.getHttpProxy());
|
||||
}
|
||||
|
||||
private void assertIpConfigurationEqual(IpConfiguration source, IpConfiguration target) {
|
||||
assertEquals(source.getIpAssignment(), target.getIpAssignment());
|
||||
assertEquals(source.getProxySettings(), target.getProxySettings());
|
||||
assertEquals(source.getHttpProxy(), target.getHttpProxy());
|
||||
assertEquals(source.getStaticIpConfiguration(), target.getStaticIpConfiguration());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParcel() {
|
||||
final IpConfiguration config = new IpConfiguration();
|
||||
assertParcelSane(config, 4);
|
||||
}
|
||||
}
|
||||
556
tests/cts/net/src/android/net/cts/IpSecBaseTest.java
Normal file
556
tests/cts/net/src/android/net/cts/IpSecBaseTest.java
Normal file
@@ -0,0 +1,556 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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 android.net.cts;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.IpSecAlgorithm;
|
||||
import android.net.IpSecManager;
|
||||
import android.net.IpSecTransform;
|
||||
import android.platform.test.annotations.AppModeFull;
|
||||
import android.system.Os;
|
||||
import android.system.OsConstants;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class IpSecBaseTest {
|
||||
|
||||
private static final String TAG = IpSecBaseTest.class.getSimpleName();
|
||||
|
||||
protected static final String IPV4_LOOPBACK = "127.0.0.1";
|
||||
protected static final String IPV6_LOOPBACK = "::1";
|
||||
protected static final String[] LOOPBACK_ADDRS = new String[] {IPV4_LOOPBACK, IPV6_LOOPBACK};
|
||||
protected static final int[] DIRECTIONS =
|
||||
new int[] {IpSecManager.DIRECTION_IN, IpSecManager.DIRECTION_OUT};
|
||||
|
||||
protected static final byte[] TEST_DATA = "Best test data ever!".getBytes();
|
||||
protected static final int DATA_BUFFER_LEN = 4096;
|
||||
protected static final int SOCK_TIMEOUT = 500;
|
||||
|
||||
private static final byte[] KEY_DATA = {
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
||||
0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
|
||||
0x20, 0x21, 0x22, 0x23
|
||||
};
|
||||
|
||||
protected static final byte[] AUTH_KEY = getKey(256);
|
||||
protected static final byte[] CRYPT_KEY = getKey(256);
|
||||
|
||||
protected ConnectivityManager mCM;
|
||||
protected IpSecManager mISM;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mISM =
|
||||
(IpSecManager)
|
||||
InstrumentationRegistry.getContext()
|
||||
.getSystemService(Context.IPSEC_SERVICE);
|
||||
mCM =
|
||||
(ConnectivityManager)
|
||||
InstrumentationRegistry.getContext()
|
||||
.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
}
|
||||
|
||||
protected static byte[] getKey(int bitLength) {
|
||||
return Arrays.copyOf(KEY_DATA, bitLength / 8);
|
||||
}
|
||||
|
||||
protected static int getDomain(InetAddress address) {
|
||||
int domain;
|
||||
if (address instanceof Inet6Address) {
|
||||
domain = OsConstants.AF_INET6;
|
||||
} else {
|
||||
domain = OsConstants.AF_INET;
|
||||
}
|
||||
return domain;
|
||||
}
|
||||
|
||||
protected static int getPort(FileDescriptor sock) throws Exception {
|
||||
return ((InetSocketAddress) Os.getsockname(sock)).getPort();
|
||||
}
|
||||
|
||||
public static interface GenericSocket extends AutoCloseable {
|
||||
void send(byte[] data) throws Exception;
|
||||
|
||||
byte[] receive() throws Exception;
|
||||
|
||||
int getPort() throws Exception;
|
||||
|
||||
void close() throws Exception;
|
||||
|
||||
void applyTransportModeTransform(
|
||||
IpSecManager ism, int direction, IpSecTransform transform) throws Exception;
|
||||
|
||||
void removeTransportModeTransforms(IpSecManager ism) throws Exception;
|
||||
}
|
||||
|
||||
public static interface GenericTcpSocket extends GenericSocket {}
|
||||
|
||||
public static interface GenericUdpSocket extends GenericSocket {
|
||||
void sendTo(byte[] data, InetAddress dstAddr, int port) throws Exception;
|
||||
}
|
||||
|
||||
public abstract static class NativeSocket implements GenericSocket {
|
||||
public FileDescriptor mFd;
|
||||
|
||||
public NativeSocket(FileDescriptor fd) {
|
||||
mFd = fd;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(byte[] data) throws Exception {
|
||||
Os.write(mFd, data, 0, data.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] receive() throws Exception {
|
||||
byte[] in = new byte[DATA_BUFFER_LEN];
|
||||
AtomicInteger bytesRead = new AtomicInteger(-1);
|
||||
|
||||
Thread readSockThread = new Thread(() -> {
|
||||
long startTime = System.currentTimeMillis();
|
||||
while (bytesRead.get() < 0 && System.currentTimeMillis() < startTime + SOCK_TIMEOUT) {
|
||||
try {
|
||||
bytesRead.set(Os.recvfrom(mFd, in, 0, DATA_BUFFER_LEN, 0, null));
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error encountered reading from socket", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
readSockThread.start();
|
||||
readSockThread.join(SOCK_TIMEOUT);
|
||||
|
||||
if (bytesRead.get() < 0) {
|
||||
throw new IOException("No data received from socket");
|
||||
}
|
||||
|
||||
return Arrays.copyOfRange(in, 0, bytesRead.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPort() throws Exception {
|
||||
return IpSecBaseTest.getPort(mFd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
Os.close(mFd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyTransportModeTransform(
|
||||
IpSecManager ism, int direction, IpSecTransform transform) throws Exception {
|
||||
ism.applyTransportModeTransform(mFd, direction, transform);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeTransportModeTransforms(IpSecManager ism) throws Exception {
|
||||
ism.removeTransportModeTransforms(mFd);
|
||||
}
|
||||
}
|
||||
|
||||
public static class NativeTcpSocket extends NativeSocket implements GenericTcpSocket {
|
||||
public NativeTcpSocket(FileDescriptor fd) {
|
||||
super(fd);
|
||||
}
|
||||
}
|
||||
|
||||
public static class NativeUdpSocket extends NativeSocket implements GenericUdpSocket {
|
||||
public NativeUdpSocket(FileDescriptor fd) {
|
||||
super(fd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendTo(byte[] data, InetAddress dstAddr, int port) throws Exception {
|
||||
Os.sendto(mFd, data, 0, data.length, 0, dstAddr, port);
|
||||
}
|
||||
}
|
||||
|
||||
public static class JavaUdpSocket implements GenericUdpSocket {
|
||||
public final DatagramSocket mSocket;
|
||||
|
||||
public JavaUdpSocket(InetAddress localAddr, int port) {
|
||||
try {
|
||||
mSocket = new DatagramSocket(port, localAddr);
|
||||
mSocket.setSoTimeout(SOCK_TIMEOUT);
|
||||
} catch (SocketException e) {
|
||||
// Fail loudly if we can't set up sockets properly. And without the timeout, we
|
||||
// could easily end up in an endless wait.
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public JavaUdpSocket(InetAddress localAddr) {
|
||||
try {
|
||||
mSocket = new DatagramSocket(0, localAddr);
|
||||
mSocket.setSoTimeout(SOCK_TIMEOUT);
|
||||
} catch (SocketException e) {
|
||||
// Fail loudly if we can't set up sockets properly. And without the timeout, we
|
||||
// could easily end up in an endless wait.
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(byte[] data) throws Exception {
|
||||
mSocket.send(new DatagramPacket(data, data.length));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendTo(byte[] data, InetAddress dstAddr, int port) throws Exception {
|
||||
mSocket.send(new DatagramPacket(data, data.length, dstAddr, port));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPort() throws Exception {
|
||||
return mSocket.getLocalPort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
mSocket.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] receive() throws Exception {
|
||||
DatagramPacket data = new DatagramPacket(new byte[DATA_BUFFER_LEN], DATA_BUFFER_LEN);
|
||||
mSocket.receive(data);
|
||||
return Arrays.copyOfRange(data.getData(), 0, data.getLength());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyTransportModeTransform(
|
||||
IpSecManager ism, int direction, IpSecTransform transform) throws Exception {
|
||||
ism.applyTransportModeTransform(mSocket, direction, transform);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeTransportModeTransforms(IpSecManager ism) throws Exception {
|
||||
ism.removeTransportModeTransforms(mSocket);
|
||||
}
|
||||
}
|
||||
|
||||
public static class JavaTcpSocket implements GenericTcpSocket {
|
||||
public final Socket mSocket;
|
||||
|
||||
public JavaTcpSocket(Socket socket) {
|
||||
mSocket = socket;
|
||||
try {
|
||||
mSocket.setSoTimeout(SOCK_TIMEOUT);
|
||||
} catch (SocketException e) {
|
||||
// Fail loudly if we can't set up sockets properly. And without the timeout, we
|
||||
// could easily end up in an endless wait.
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(byte[] data) throws Exception {
|
||||
mSocket.getOutputStream().write(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] receive() throws Exception {
|
||||
byte[] in = new byte[DATA_BUFFER_LEN];
|
||||
int bytesRead = mSocket.getInputStream().read(in);
|
||||
return Arrays.copyOfRange(in, 0, bytesRead);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPort() throws Exception {
|
||||
return mSocket.getLocalPort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
mSocket.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyTransportModeTransform(
|
||||
IpSecManager ism, int direction, IpSecTransform transform) throws Exception {
|
||||
ism.applyTransportModeTransform(mSocket, direction, transform);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeTransportModeTransforms(IpSecManager ism) throws Exception {
|
||||
ism.removeTransportModeTransforms(mSocket);
|
||||
}
|
||||
}
|
||||
|
||||
public static class SocketPair<T> {
|
||||
public final T mLeftSock;
|
||||
public final T mRightSock;
|
||||
|
||||
public SocketPair(T leftSock, T rightSock) {
|
||||
mLeftSock = leftSock;
|
||||
mRightSock = rightSock;
|
||||
}
|
||||
}
|
||||
|
||||
protected static void applyTransformBidirectionally(
|
||||
IpSecManager ism, IpSecTransform transform, GenericSocket socket) throws Exception {
|
||||
for (int direction : DIRECTIONS) {
|
||||
socket.applyTransportModeTransform(ism, direction, transform);
|
||||
}
|
||||
}
|
||||
|
||||
public static SocketPair<NativeUdpSocket> getNativeUdpSocketPair(
|
||||
InetAddress localAddr, IpSecManager ism, IpSecTransform transform, boolean connected)
|
||||
throws Exception {
|
||||
int domain = getDomain(localAddr);
|
||||
|
||||
NativeUdpSocket leftSock = new NativeUdpSocket(
|
||||
Os.socket(domain, OsConstants.SOCK_DGRAM, OsConstants.IPPROTO_UDP));
|
||||
NativeUdpSocket rightSock = new NativeUdpSocket(
|
||||
Os.socket(domain, OsConstants.SOCK_DGRAM, OsConstants.IPPROTO_UDP));
|
||||
|
||||
for (NativeUdpSocket sock : new NativeUdpSocket[] {leftSock, rightSock}) {
|
||||
applyTransformBidirectionally(ism, transform, sock);
|
||||
Os.bind(sock.mFd, localAddr, 0);
|
||||
}
|
||||
|
||||
if (connected) {
|
||||
Os.connect(leftSock.mFd, localAddr, rightSock.getPort());
|
||||
Os.connect(rightSock.mFd, localAddr, leftSock.getPort());
|
||||
}
|
||||
|
||||
return new SocketPair<>(leftSock, rightSock);
|
||||
}
|
||||
|
||||
public static SocketPair<NativeTcpSocket> getNativeTcpSocketPair(
|
||||
InetAddress localAddr, IpSecManager ism, IpSecTransform transform) throws Exception {
|
||||
int domain = getDomain(localAddr);
|
||||
|
||||
NativeTcpSocket server = new NativeTcpSocket(
|
||||
Os.socket(domain, OsConstants.SOCK_STREAM, OsConstants.IPPROTO_TCP));
|
||||
NativeTcpSocket client = new NativeTcpSocket(
|
||||
Os.socket(domain, OsConstants.SOCK_STREAM, OsConstants.IPPROTO_TCP));
|
||||
|
||||
Os.bind(server.mFd, localAddr, 0);
|
||||
|
||||
applyTransformBidirectionally(ism, transform, server);
|
||||
applyTransformBidirectionally(ism, transform, client);
|
||||
|
||||
Os.listen(server.mFd, 10);
|
||||
Os.connect(client.mFd, localAddr, server.getPort());
|
||||
NativeTcpSocket accepted = new NativeTcpSocket(Os.accept(server.mFd, null));
|
||||
|
||||
applyTransformBidirectionally(ism, transform, accepted);
|
||||
server.close();
|
||||
|
||||
return new SocketPair<>(client, accepted);
|
||||
}
|
||||
|
||||
public static SocketPair<JavaUdpSocket> getJavaUdpSocketPair(
|
||||
InetAddress localAddr, IpSecManager ism, IpSecTransform transform, boolean connected)
|
||||
throws Exception {
|
||||
JavaUdpSocket leftSock = new JavaUdpSocket(localAddr);
|
||||
JavaUdpSocket rightSock = new JavaUdpSocket(localAddr);
|
||||
|
||||
applyTransformBidirectionally(ism, transform, leftSock);
|
||||
applyTransformBidirectionally(ism, transform, rightSock);
|
||||
|
||||
if (connected) {
|
||||
leftSock.mSocket.connect(localAddr, rightSock.mSocket.getLocalPort());
|
||||
rightSock.mSocket.connect(localAddr, leftSock.mSocket.getLocalPort());
|
||||
}
|
||||
|
||||
return new SocketPair<>(leftSock, rightSock);
|
||||
}
|
||||
|
||||
public static SocketPair<JavaTcpSocket> getJavaTcpSocketPair(
|
||||
InetAddress localAddr, IpSecManager ism, IpSecTransform transform) throws Exception {
|
||||
JavaTcpSocket clientSock = new JavaTcpSocket(new Socket());
|
||||
ServerSocket serverSocket = new ServerSocket();
|
||||
serverSocket.bind(new InetSocketAddress(localAddr, 0));
|
||||
|
||||
// While technically the client socket does not need to be bound, the OpenJDK implementation
|
||||
// of Socket only allocates an FD when bind() or connect() or other similar methods are
|
||||
// called. So we call bind to force the FD creation, so that we can apply a transform to it
|
||||
// prior to socket connect.
|
||||
clientSock.mSocket.bind(new InetSocketAddress(localAddr, 0));
|
||||
|
||||
// IpSecService doesn't support serverSockets at the moment; workaround using FD
|
||||
FileDescriptor serverFd = serverSocket.getImpl().getFD$();
|
||||
|
||||
applyTransformBidirectionally(ism, transform, new NativeTcpSocket(serverFd));
|
||||
applyTransformBidirectionally(ism, transform, clientSock);
|
||||
|
||||
clientSock.mSocket.connect(new InetSocketAddress(localAddr, serverSocket.getLocalPort()));
|
||||
JavaTcpSocket acceptedSock = new JavaTcpSocket(serverSocket.accept());
|
||||
|
||||
applyTransformBidirectionally(ism, transform, acceptedSock);
|
||||
serverSocket.close();
|
||||
|
||||
return new SocketPair<>(clientSock, acceptedSock);
|
||||
}
|
||||
|
||||
private void checkSocketPair(GenericSocket left, GenericSocket right) throws Exception {
|
||||
left.send(TEST_DATA);
|
||||
assertArrayEquals(TEST_DATA, right.receive());
|
||||
|
||||
right.send(TEST_DATA);
|
||||
assertArrayEquals(TEST_DATA, left.receive());
|
||||
|
||||
left.close();
|
||||
right.close();
|
||||
}
|
||||
|
||||
private void checkUnconnectedUdpSocketPair(
|
||||
GenericUdpSocket left, GenericUdpSocket right, InetAddress localAddr) throws Exception {
|
||||
left.sendTo(TEST_DATA, localAddr, right.getPort());
|
||||
assertArrayEquals(TEST_DATA, right.receive());
|
||||
|
||||
right.sendTo(TEST_DATA, localAddr, left.getPort());
|
||||
assertArrayEquals(TEST_DATA, left.receive());
|
||||
|
||||
left.close();
|
||||
right.close();
|
||||
}
|
||||
|
||||
protected static IpSecTransform buildIpSecTransform(
|
||||
Context context,
|
||||
IpSecManager.SecurityParameterIndex spi,
|
||||
IpSecManager.UdpEncapsulationSocket encapSocket,
|
||||
InetAddress remoteAddr)
|
||||
throws Exception {
|
||||
IpSecTransform.Builder builder =
|
||||
new IpSecTransform.Builder(context)
|
||||
.setEncryption(new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY))
|
||||
.setAuthentication(
|
||||
new IpSecAlgorithm(
|
||||
IpSecAlgorithm.AUTH_HMAC_SHA256,
|
||||
AUTH_KEY,
|
||||
AUTH_KEY.length * 4));
|
||||
|
||||
if (encapSocket != null) {
|
||||
builder.setIpv4Encapsulation(encapSocket, encapSocket.getPort());
|
||||
}
|
||||
|
||||
return builder.buildTransportModeTransform(remoteAddr, spi);
|
||||
}
|
||||
|
||||
private IpSecTransform buildDefaultTransform(InetAddress localAddr) throws Exception {
|
||||
try (IpSecManager.SecurityParameterIndex spi =
|
||||
mISM.allocateSecurityParameterIndex(localAddr)) {
|
||||
return buildIpSecTransform(InstrumentationRegistry.getContext(), spi, null, localAddr);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@AppModeFull(reason = "Socket cannot bind in instant app mode")
|
||||
public void testJavaTcpSocketPair() throws Exception {
|
||||
for (String addr : LOOPBACK_ADDRS) {
|
||||
InetAddress local = InetAddress.getByName(addr);
|
||||
try (IpSecTransform transform = buildDefaultTransform(local)) {
|
||||
SocketPair<JavaTcpSocket> sockets = getJavaTcpSocketPair(local, mISM, transform);
|
||||
checkSocketPair(sockets.mLeftSock, sockets.mRightSock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@AppModeFull(reason = "Socket cannot bind in instant app mode")
|
||||
public void testJavaUdpSocketPair() throws Exception {
|
||||
for (String addr : LOOPBACK_ADDRS) {
|
||||
InetAddress local = InetAddress.getByName(addr);
|
||||
try (IpSecTransform transform = buildDefaultTransform(local)) {
|
||||
SocketPair<JavaUdpSocket> sockets =
|
||||
getJavaUdpSocketPair(local, mISM, transform, true);
|
||||
checkSocketPair(sockets.mLeftSock, sockets.mRightSock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@AppModeFull(reason = "Socket cannot bind in instant app mode")
|
||||
public void testJavaUdpSocketPairUnconnected() throws Exception {
|
||||
for (String addr : LOOPBACK_ADDRS) {
|
||||
InetAddress local = InetAddress.getByName(addr);
|
||||
try (IpSecTransform transform = buildDefaultTransform(local)) {
|
||||
SocketPair<JavaUdpSocket> sockets =
|
||||
getJavaUdpSocketPair(local, mISM, transform, false);
|
||||
checkUnconnectedUdpSocketPair(sockets.mLeftSock, sockets.mRightSock, local);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@AppModeFull(reason = "Socket cannot bind in instant app mode")
|
||||
public void testNativeTcpSocketPair() throws Exception {
|
||||
for (String addr : LOOPBACK_ADDRS) {
|
||||
InetAddress local = InetAddress.getByName(addr);
|
||||
try (IpSecTransform transform = buildDefaultTransform(local)) {
|
||||
SocketPair<NativeTcpSocket> sockets =
|
||||
getNativeTcpSocketPair(local, mISM, transform);
|
||||
checkSocketPair(sockets.mLeftSock, sockets.mRightSock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@AppModeFull(reason = "Socket cannot bind in instant app mode")
|
||||
public void testNativeUdpSocketPair() throws Exception {
|
||||
for (String addr : LOOPBACK_ADDRS) {
|
||||
InetAddress local = InetAddress.getByName(addr);
|
||||
try (IpSecTransform transform = buildDefaultTransform(local)) {
|
||||
SocketPair<NativeUdpSocket> sockets =
|
||||
getNativeUdpSocketPair(local, mISM, transform, true);
|
||||
checkSocketPair(sockets.mLeftSock, sockets.mRightSock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@AppModeFull(reason = "Socket cannot bind in instant app mode")
|
||||
public void testNativeUdpSocketPairUnconnected() throws Exception {
|
||||
for (String addr : LOOPBACK_ADDRS) {
|
||||
InetAddress local = InetAddress.getByName(addr);
|
||||
try (IpSecTransform transform = buildDefaultTransform(local)) {
|
||||
SocketPair<NativeUdpSocket> sockets =
|
||||
getNativeUdpSocketPair(local, mISM, transform, false);
|
||||
checkUnconnectedUdpSocketPair(sockets.mLeftSock, sockets.mRightSock, local);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1189
tests/cts/net/src/android/net/cts/IpSecManagerTest.java
Normal file
1189
tests/cts/net/src/android/net/cts/IpSecManagerTest.java
Normal file
File diff suppressed because it is too large
Load Diff
899
tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java
Normal file
899
tests/cts/net/src/android/net/cts/IpSecManagerTunnelTest.java
Normal file
@@ -0,0 +1,899 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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 android.net.cts;
|
||||
|
||||
import static android.app.AppOpsManager.OP_MANAGE_IPSEC_TUNNELS;
|
||||
import static android.net.IpSecManager.UdpEncapsulationSocket;
|
||||
import static android.net.cts.PacketUtils.AES_CBC_BLK_SIZE;
|
||||
import static android.net.cts.PacketUtils.AES_CBC_IV_LEN;
|
||||
import static android.net.cts.PacketUtils.BytePayload;
|
||||
import static android.net.cts.PacketUtils.EspHeader;
|
||||
import static android.net.cts.PacketUtils.IP4_HDRLEN;
|
||||
import static android.net.cts.PacketUtils.IP6_HDRLEN;
|
||||
import static android.net.cts.PacketUtils.IpHeader;
|
||||
import static android.net.cts.PacketUtils.UDP_HDRLEN;
|
||||
import static android.net.cts.PacketUtils.UdpHeader;
|
||||
import static android.net.cts.PacketUtils.getIpHeader;
|
||||
import static android.net.cts.util.CtsNetUtils.TestNetworkCallback;
|
||||
import static android.system.OsConstants.AF_INET;
|
||||
import static android.system.OsConstants.AF_INET6;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.IpSecAlgorithm;
|
||||
import android.net.IpSecManager;
|
||||
import android.net.IpSecTransform;
|
||||
import android.net.LinkAddress;
|
||||
import android.net.Network;
|
||||
import android.net.TestNetworkInterface;
|
||||
import android.net.TestNetworkManager;
|
||||
import android.net.cts.PacketUtils.Payload;
|
||||
import android.net.cts.util.CtsNetUtils;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.platform.test.annotations.AppModeFull;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@AppModeFull(reason = "MANAGE_TEST_NETWORKS permission can't be granted to instant apps")
|
||||
public class IpSecManagerTunnelTest extends IpSecBaseTest {
|
||||
private static final String TAG = IpSecManagerTunnelTest.class.getSimpleName();
|
||||
|
||||
private static final InetAddress LOCAL_OUTER_4 = InetAddress.parseNumericAddress("192.0.2.1");
|
||||
private static final InetAddress REMOTE_OUTER_4 = InetAddress.parseNumericAddress("192.0.2.2");
|
||||
private static final InetAddress LOCAL_OUTER_6 =
|
||||
InetAddress.parseNumericAddress("2001:db8:1::1");
|
||||
private static final InetAddress REMOTE_OUTER_6 =
|
||||
InetAddress.parseNumericAddress("2001:db8:1::2");
|
||||
|
||||
private static final InetAddress LOCAL_INNER_4 =
|
||||
InetAddress.parseNumericAddress("198.51.100.1");
|
||||
private static final InetAddress REMOTE_INNER_4 =
|
||||
InetAddress.parseNumericAddress("198.51.100.2");
|
||||
private static final InetAddress LOCAL_INNER_6 =
|
||||
InetAddress.parseNumericAddress("2001:db8:2::1");
|
||||
private static final InetAddress REMOTE_INNER_6 =
|
||||
InetAddress.parseNumericAddress("2001:db8:2::2");
|
||||
|
||||
private static final int IP4_PREFIX_LEN = 32;
|
||||
private static final int IP6_PREFIX_LEN = 128;
|
||||
|
||||
private static final int TIMEOUT_MS = 500;
|
||||
|
||||
// Static state to reduce setup/teardown
|
||||
private static ConnectivityManager sCM;
|
||||
private static TestNetworkManager sTNM;
|
||||
private static ParcelFileDescriptor sTunFd;
|
||||
private static TestNetworkCallback sTunNetworkCallback;
|
||||
private static Network sTunNetwork;
|
||||
private static TunUtils sTunUtils;
|
||||
|
||||
private static Context sContext = InstrumentationRegistry.getContext();
|
||||
private static final CtsNetUtils mCtsNetUtils = new CtsNetUtils(sContext);
|
||||
|
||||
@BeforeClass
|
||||
public static void setUpBeforeClass() throws Exception {
|
||||
InstrumentationRegistry.getInstrumentation()
|
||||
.getUiAutomation()
|
||||
.adoptShellPermissionIdentity();
|
||||
sCM = (ConnectivityManager) sContext.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
sTNM = (TestNetworkManager) sContext.getSystemService(Context.TEST_NETWORK_SERVICE);
|
||||
|
||||
// Under normal circumstances, the MANAGE_IPSEC_TUNNELS appop would be auto-granted, and
|
||||
// a standard permission is insufficient. So we shell out the appop, to give us the
|
||||
// right appop permissions.
|
||||
mCtsNetUtils.setAppopPrivileged(OP_MANAGE_IPSEC_TUNNELS, true);
|
||||
|
||||
TestNetworkInterface testIface =
|
||||
sTNM.createTunInterface(
|
||||
new LinkAddress[] {
|
||||
new LinkAddress(LOCAL_OUTER_4, IP4_PREFIX_LEN),
|
||||
new LinkAddress(LOCAL_OUTER_6, IP6_PREFIX_LEN)
|
||||
});
|
||||
|
||||
sTunFd = testIface.getFileDescriptor();
|
||||
sTunNetworkCallback = mCtsNetUtils.setupAndGetTestNetwork(testIface.getInterfaceName());
|
||||
sTunNetworkCallback.waitForAvailable();
|
||||
sTunNetwork = sTunNetworkCallback.currentNetwork;
|
||||
|
||||
sTunUtils = new TunUtils(sTunFd);
|
||||
}
|
||||
|
||||
@Before
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
// Set to true before every run; some tests flip this.
|
||||
mCtsNetUtils.setAppopPrivileged(OP_MANAGE_IPSEC_TUNNELS, true);
|
||||
|
||||
// Clear sTunUtils state
|
||||
sTunUtils.reset();
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDownAfterClass() throws Exception {
|
||||
mCtsNetUtils.setAppopPrivileged(OP_MANAGE_IPSEC_TUNNELS, false);
|
||||
|
||||
sCM.unregisterNetworkCallback(sTunNetworkCallback);
|
||||
|
||||
sTNM.teardownTestNetwork(sTunNetwork);
|
||||
sTunFd.close();
|
||||
|
||||
InstrumentationRegistry.getInstrumentation()
|
||||
.getUiAutomation()
|
||||
.dropShellPermissionIdentity();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecurityExceptionCreateTunnelInterfaceWithoutAppop() throws Exception {
|
||||
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
|
||||
|
||||
// Ensure we don't have the appop. Permission is not requested in the Manifest
|
||||
mCtsNetUtils.setAppopPrivileged(OP_MANAGE_IPSEC_TUNNELS, false);
|
||||
|
||||
// Security exceptions are thrown regardless of IPv4/IPv6. Just test one
|
||||
try {
|
||||
mISM.createIpSecTunnelInterface(LOCAL_INNER_6, REMOTE_INNER_6, sTunNetwork);
|
||||
fail("Did not throw SecurityException for Tunnel creation without appop");
|
||||
} catch (SecurityException expected) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecurityExceptionBuildTunnelTransformWithoutAppop() throws Exception {
|
||||
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
|
||||
|
||||
// Ensure we don't have the appop. Permission is not requested in the Manifest
|
||||
mCtsNetUtils.setAppopPrivileged(OP_MANAGE_IPSEC_TUNNELS, false);
|
||||
|
||||
// Security exceptions are thrown regardless of IPv4/IPv6. Just test one
|
||||
try (IpSecManager.SecurityParameterIndex spi =
|
||||
mISM.allocateSecurityParameterIndex(LOCAL_INNER_4);
|
||||
IpSecTransform transform =
|
||||
new IpSecTransform.Builder(sContext)
|
||||
.buildTunnelModeTransform(REMOTE_INNER_4, spi)) {
|
||||
fail("Did not throw SecurityException for Transform creation without appop");
|
||||
} catch (SecurityException expected) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Test runnables for callbacks after IPsec tunnels are set up. */
|
||||
private abstract class IpSecTunnelTestRunnable {
|
||||
/**
|
||||
* Runs the test code, and returns the inner socket port, if any.
|
||||
*
|
||||
* @param ipsecNetwork The IPsec Interface based Network for binding sockets on
|
||||
* @return the integer port of the inner socket if outbound, or 0 if inbound
|
||||
* IpSecTunnelTestRunnable
|
||||
* @throws Exception if any part of the test failed.
|
||||
*/
|
||||
public abstract int run(Network ipsecNetwork) throws Exception;
|
||||
}
|
||||
|
||||
private int getPacketSize(
|
||||
int innerFamily, int outerFamily, boolean useEncap, boolean transportInTunnelMode) {
|
||||
int expectedPacketSize = TEST_DATA.length + UDP_HDRLEN;
|
||||
|
||||
// Inner Transport mode packet size
|
||||
if (transportInTunnelMode) {
|
||||
expectedPacketSize =
|
||||
PacketUtils.calculateEspPacketSize(
|
||||
expectedPacketSize,
|
||||
AES_CBC_IV_LEN,
|
||||
AES_CBC_BLK_SIZE,
|
||||
AUTH_KEY.length * 4);
|
||||
}
|
||||
|
||||
// Inner IP Header
|
||||
expectedPacketSize += innerFamily == AF_INET ? IP4_HDRLEN : IP6_HDRLEN;
|
||||
|
||||
// Tunnel mode transform size
|
||||
expectedPacketSize =
|
||||
PacketUtils.calculateEspPacketSize(
|
||||
expectedPacketSize, AES_CBC_IV_LEN, AES_CBC_BLK_SIZE, AUTH_KEY.length * 4);
|
||||
|
||||
// UDP encap size
|
||||
expectedPacketSize += useEncap ? UDP_HDRLEN : 0;
|
||||
|
||||
// Outer IP Header
|
||||
expectedPacketSize += outerFamily == AF_INET ? IP4_HDRLEN : IP6_HDRLEN;
|
||||
|
||||
return expectedPacketSize;
|
||||
}
|
||||
|
||||
private interface IpSecTunnelTestRunnableFactory {
|
||||
IpSecTunnelTestRunnable getIpSecTunnelTestRunnable(
|
||||
boolean transportInTunnelMode,
|
||||
int spi,
|
||||
InetAddress localInner,
|
||||
InetAddress remoteInner,
|
||||
InetAddress localOuter,
|
||||
InetAddress remoteOuter,
|
||||
IpSecTransform inTransportTransform,
|
||||
IpSecTransform outTransportTransform,
|
||||
int encapPort,
|
||||
int innerSocketPort,
|
||||
int expectedPacketSize)
|
||||
throws Exception;
|
||||
}
|
||||
|
||||
private class OutputIpSecTunnelTestRunnableFactory implements IpSecTunnelTestRunnableFactory {
|
||||
public IpSecTunnelTestRunnable getIpSecTunnelTestRunnable(
|
||||
boolean transportInTunnelMode,
|
||||
int spi,
|
||||
InetAddress localInner,
|
||||
InetAddress remoteInner,
|
||||
InetAddress localOuter,
|
||||
InetAddress remoteOuter,
|
||||
IpSecTransform inTransportTransform,
|
||||
IpSecTransform outTransportTransform,
|
||||
int encapPort,
|
||||
int unusedInnerSocketPort,
|
||||
int expectedPacketSize) {
|
||||
return new IpSecTunnelTestRunnable() {
|
||||
@Override
|
||||
public int run(Network ipsecNetwork) throws Exception {
|
||||
// Build a socket and send traffic
|
||||
JavaUdpSocket socket = new JavaUdpSocket(localInner);
|
||||
ipsecNetwork.bindSocket(socket.mSocket);
|
||||
int innerSocketPort = socket.getPort();
|
||||
|
||||
// For Transport-In-Tunnel mode, apply transform to socket
|
||||
if (transportInTunnelMode) {
|
||||
mISM.applyTransportModeTransform(
|
||||
socket.mSocket, IpSecManager.DIRECTION_IN, inTransportTransform);
|
||||
mISM.applyTransportModeTransform(
|
||||
socket.mSocket, IpSecManager.DIRECTION_OUT, outTransportTransform);
|
||||
}
|
||||
|
||||
socket.sendTo(TEST_DATA, remoteInner, socket.getPort());
|
||||
|
||||
// Verify that an encrypted packet is sent. As of right now, checking encrypted
|
||||
// body is not possible, due to the test not knowing some of the fields of the
|
||||
// inner IP header (flow label, flags, etc)
|
||||
sTunUtils.awaitEspPacketNoPlaintext(
|
||||
spi, TEST_DATA, encapPort != 0, expectedPacketSize);
|
||||
|
||||
socket.close();
|
||||
|
||||
return innerSocketPort;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private class InputReflectedIpSecTunnelTestRunnableFactory
|
||||
implements IpSecTunnelTestRunnableFactory {
|
||||
public IpSecTunnelTestRunnable getIpSecTunnelTestRunnable(
|
||||
boolean transportInTunnelMode,
|
||||
int spi,
|
||||
InetAddress localInner,
|
||||
InetAddress remoteInner,
|
||||
InetAddress localOuter,
|
||||
InetAddress remoteOuter,
|
||||
IpSecTransform inTransportTransform,
|
||||
IpSecTransform outTransportTransform,
|
||||
int encapPort,
|
||||
int innerSocketPort,
|
||||
int expectedPacketSize)
|
||||
throws Exception {
|
||||
return new IpSecTunnelTestRunnable() {
|
||||
@Override
|
||||
public int run(Network ipsecNetwork) throws Exception {
|
||||
// Build a socket and receive traffic
|
||||
JavaUdpSocket socket = new JavaUdpSocket(localInner, innerSocketPort);
|
||||
ipsecNetwork.bindSocket(socket.mSocket);
|
||||
|
||||
// For Transport-In-Tunnel mode, apply transform to socket
|
||||
if (transportInTunnelMode) {
|
||||
mISM.applyTransportModeTransform(
|
||||
socket.mSocket, IpSecManager.DIRECTION_IN, outTransportTransform);
|
||||
mISM.applyTransportModeTransform(
|
||||
socket.mSocket, IpSecManager.DIRECTION_OUT, inTransportTransform);
|
||||
}
|
||||
|
||||
sTunUtils.reflectPackets();
|
||||
|
||||
// Receive packet from socket, and validate that the payload is correct
|
||||
receiveAndValidatePacket(socket);
|
||||
|
||||
socket.close();
|
||||
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private class InputPacketGeneratorIpSecTunnelTestRunnableFactory
|
||||
implements IpSecTunnelTestRunnableFactory {
|
||||
public IpSecTunnelTestRunnable getIpSecTunnelTestRunnable(
|
||||
boolean transportInTunnelMode,
|
||||
int spi,
|
||||
InetAddress localInner,
|
||||
InetAddress remoteInner,
|
||||
InetAddress localOuter,
|
||||
InetAddress remoteOuter,
|
||||
IpSecTransform inTransportTransform,
|
||||
IpSecTransform outTransportTransform,
|
||||
int encapPort,
|
||||
int innerSocketPort,
|
||||
int expectedPacketSize)
|
||||
throws Exception {
|
||||
return new IpSecTunnelTestRunnable() {
|
||||
@Override
|
||||
public int run(Network ipsecNetwork) throws Exception {
|
||||
// Build a socket and receive traffic
|
||||
JavaUdpSocket socket = new JavaUdpSocket(localInner);
|
||||
ipsecNetwork.bindSocket(socket.mSocket);
|
||||
|
||||
// For Transport-In-Tunnel mode, apply transform to socket
|
||||
if (transportInTunnelMode) {
|
||||
mISM.applyTransportModeTransform(
|
||||
socket.mSocket, IpSecManager.DIRECTION_IN, outTransportTransform);
|
||||
mISM.applyTransportModeTransform(
|
||||
socket.mSocket, IpSecManager.DIRECTION_OUT, inTransportTransform);
|
||||
}
|
||||
|
||||
byte[] pkt;
|
||||
if (transportInTunnelMode) {
|
||||
pkt =
|
||||
getTransportInTunnelModePacket(
|
||||
spi,
|
||||
spi,
|
||||
remoteInner,
|
||||
localInner,
|
||||
remoteOuter,
|
||||
localOuter,
|
||||
socket.getPort(),
|
||||
encapPort);
|
||||
} else {
|
||||
pkt =
|
||||
getTunnelModePacket(
|
||||
spi,
|
||||
remoteInner,
|
||||
localInner,
|
||||
remoteOuter,
|
||||
localOuter,
|
||||
socket.getPort(),
|
||||
encapPort);
|
||||
}
|
||||
sTunUtils.injectPacket(pkt);
|
||||
|
||||
// Receive packet from socket, and validate
|
||||
receiveAndValidatePacket(socket);
|
||||
|
||||
socket.close();
|
||||
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private void checkTunnelOutput(
|
||||
int innerFamily, int outerFamily, boolean useEncap, boolean transportInTunnelMode)
|
||||
throws Exception {
|
||||
checkTunnel(
|
||||
innerFamily,
|
||||
outerFamily,
|
||||
useEncap,
|
||||
transportInTunnelMode,
|
||||
new OutputIpSecTunnelTestRunnableFactory());
|
||||
}
|
||||
|
||||
private void checkTunnelInput(
|
||||
int innerFamily, int outerFamily, boolean useEncap, boolean transportInTunnelMode)
|
||||
throws Exception {
|
||||
checkTunnel(
|
||||
innerFamily,
|
||||
outerFamily,
|
||||
useEncap,
|
||||
transportInTunnelMode,
|
||||
new InputPacketGeneratorIpSecTunnelTestRunnableFactory());
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the kernel can talk to itself.
|
||||
*
|
||||
* <p>This test takes an outbound IPsec packet, reflects it (by flipping IP src/dst), and
|
||||
* injects it back into the TUN. This test then verifies that a packet with the correct payload
|
||||
* is found on the specified socket/port.
|
||||
*/
|
||||
public void checkTunnelReflected(
|
||||
int innerFamily, int outerFamily, boolean useEncap, boolean transportInTunnelMode)
|
||||
throws Exception {
|
||||
InetAddress localInner = innerFamily == AF_INET ? LOCAL_INNER_4 : LOCAL_INNER_6;
|
||||
InetAddress remoteInner = innerFamily == AF_INET ? REMOTE_INNER_4 : REMOTE_INNER_6;
|
||||
|
||||
InetAddress localOuter = outerFamily == AF_INET ? LOCAL_OUTER_4 : LOCAL_OUTER_6;
|
||||
InetAddress remoteOuter = outerFamily == AF_INET ? REMOTE_OUTER_4 : REMOTE_OUTER_6;
|
||||
|
||||
// Preselect both SPI and encap port, to be used for both inbound and outbound tunnels.
|
||||
int spi = getRandomSpi(localOuter, remoteOuter);
|
||||
int expectedPacketSize =
|
||||
getPacketSize(innerFamily, outerFamily, useEncap, transportInTunnelMode);
|
||||
|
||||
try (IpSecManager.SecurityParameterIndex inTransportSpi =
|
||||
mISM.allocateSecurityParameterIndex(localInner, spi);
|
||||
IpSecManager.SecurityParameterIndex outTransportSpi =
|
||||
mISM.allocateSecurityParameterIndex(remoteInner, spi);
|
||||
IpSecTransform inTransportTransform =
|
||||
buildIpSecTransform(sContext, inTransportSpi, null, remoteInner);
|
||||
IpSecTransform outTransportTransform =
|
||||
buildIpSecTransform(sContext, outTransportSpi, null, localInner);
|
||||
UdpEncapsulationSocket encapSocket = mISM.openUdpEncapsulationSocket()) {
|
||||
|
||||
// Run output direction tests
|
||||
IpSecTunnelTestRunnable outputIpSecTunnelTestRunnable =
|
||||
new OutputIpSecTunnelTestRunnableFactory()
|
||||
.getIpSecTunnelTestRunnable(
|
||||
transportInTunnelMode,
|
||||
spi,
|
||||
localInner,
|
||||
remoteInner,
|
||||
localOuter,
|
||||
remoteOuter,
|
||||
inTransportTransform,
|
||||
outTransportTransform,
|
||||
useEncap ? encapSocket.getPort() : 0,
|
||||
0,
|
||||
expectedPacketSize);
|
||||
int innerSocketPort =
|
||||
buildTunnelNetworkAndRunTests(
|
||||
localInner,
|
||||
remoteInner,
|
||||
localOuter,
|
||||
remoteOuter,
|
||||
spi,
|
||||
useEncap ? encapSocket : null,
|
||||
outputIpSecTunnelTestRunnable);
|
||||
|
||||
// Input direction tests, with matching inner socket ports.
|
||||
IpSecTunnelTestRunnable inputIpSecTunnelTestRunnable =
|
||||
new InputReflectedIpSecTunnelTestRunnableFactory()
|
||||
.getIpSecTunnelTestRunnable(
|
||||
transportInTunnelMode,
|
||||
spi,
|
||||
remoteInner,
|
||||
localInner,
|
||||
localOuter,
|
||||
remoteOuter,
|
||||
inTransportTransform,
|
||||
outTransportTransform,
|
||||
useEncap ? encapSocket.getPort() : 0,
|
||||
innerSocketPort,
|
||||
expectedPacketSize);
|
||||
buildTunnelNetworkAndRunTests(
|
||||
remoteInner,
|
||||
localInner,
|
||||
localOuter,
|
||||
remoteOuter,
|
||||
spi,
|
||||
useEncap ? encapSocket : null,
|
||||
inputIpSecTunnelTestRunnable);
|
||||
}
|
||||
}
|
||||
|
||||
public void checkTunnel(
|
||||
int innerFamily,
|
||||
int outerFamily,
|
||||
boolean useEncap,
|
||||
boolean transportInTunnelMode,
|
||||
IpSecTunnelTestRunnableFactory factory)
|
||||
throws Exception {
|
||||
|
||||
InetAddress localInner = innerFamily == AF_INET ? LOCAL_INNER_4 : LOCAL_INNER_6;
|
||||
InetAddress remoteInner = innerFamily == AF_INET ? REMOTE_INNER_4 : REMOTE_INNER_6;
|
||||
|
||||
InetAddress localOuter = outerFamily == AF_INET ? LOCAL_OUTER_4 : LOCAL_OUTER_6;
|
||||
InetAddress remoteOuter = outerFamily == AF_INET ? REMOTE_OUTER_4 : REMOTE_OUTER_6;
|
||||
|
||||
// Preselect both SPI and encap port, to be used for both inbound and outbound tunnels.
|
||||
// Re-uses the same SPI to ensure that even in cases of symmetric SPIs shared across tunnel
|
||||
// and transport mode, packets are encrypted/decrypted properly based on the src/dst.
|
||||
int spi = getRandomSpi(localOuter, remoteOuter);
|
||||
int expectedPacketSize =
|
||||
getPacketSize(innerFamily, outerFamily, useEncap, transportInTunnelMode);
|
||||
|
||||
try (IpSecManager.SecurityParameterIndex inTransportSpi =
|
||||
mISM.allocateSecurityParameterIndex(localInner, spi);
|
||||
IpSecManager.SecurityParameterIndex outTransportSpi =
|
||||
mISM.allocateSecurityParameterIndex(remoteInner, spi);
|
||||
IpSecTransform inTransportTransform =
|
||||
buildIpSecTransform(sContext, inTransportSpi, null, remoteInner);
|
||||
IpSecTransform outTransportTransform =
|
||||
buildIpSecTransform(sContext, outTransportSpi, null, localInner);
|
||||
UdpEncapsulationSocket encapSocket = mISM.openUdpEncapsulationSocket()) {
|
||||
|
||||
buildTunnelNetworkAndRunTests(
|
||||
localInner,
|
||||
remoteInner,
|
||||
localOuter,
|
||||
remoteOuter,
|
||||
spi,
|
||||
useEncap ? encapSocket : null,
|
||||
factory.getIpSecTunnelTestRunnable(
|
||||
transportInTunnelMode,
|
||||
spi,
|
||||
localInner,
|
||||
remoteInner,
|
||||
localOuter,
|
||||
remoteOuter,
|
||||
inTransportTransform,
|
||||
outTransportTransform,
|
||||
useEncap ? encapSocket.getPort() : 0,
|
||||
0,
|
||||
expectedPacketSize));
|
||||
}
|
||||
}
|
||||
|
||||
private int buildTunnelNetworkAndRunTests(
|
||||
InetAddress localInner,
|
||||
InetAddress remoteInner,
|
||||
InetAddress localOuter,
|
||||
InetAddress remoteOuter,
|
||||
int spi,
|
||||
UdpEncapsulationSocket encapSocket,
|
||||
IpSecTunnelTestRunnable test)
|
||||
throws Exception {
|
||||
int innerPrefixLen = localInner instanceof Inet6Address ? IP6_PREFIX_LEN : IP4_PREFIX_LEN;
|
||||
TestNetworkCallback testNetworkCb = null;
|
||||
int innerSocketPort;
|
||||
|
||||
try (IpSecManager.SecurityParameterIndex inSpi =
|
||||
mISM.allocateSecurityParameterIndex(localOuter, spi);
|
||||
IpSecManager.SecurityParameterIndex outSpi =
|
||||
mISM.allocateSecurityParameterIndex(remoteOuter, spi);
|
||||
IpSecManager.IpSecTunnelInterface tunnelIface =
|
||||
mISM.createIpSecTunnelInterface(localOuter, remoteOuter, sTunNetwork)) {
|
||||
// Build the test network
|
||||
tunnelIface.addAddress(localInner, innerPrefixLen);
|
||||
testNetworkCb = mCtsNetUtils.setupAndGetTestNetwork(tunnelIface.getInterfaceName());
|
||||
testNetworkCb.waitForAvailable();
|
||||
Network testNetwork = testNetworkCb.currentNetwork;
|
||||
|
||||
// Check interface was created
|
||||
assertNotNull(NetworkInterface.getByName(tunnelIface.getInterfaceName()));
|
||||
|
||||
// Verify address was added
|
||||
final NetworkInterface netIface = NetworkInterface.getByInetAddress(localInner);
|
||||
assertNotNull(netIface);
|
||||
assertEquals(tunnelIface.getInterfaceName(), netIface.getDisplayName());
|
||||
|
||||
// Configure Transform parameters
|
||||
IpSecTransform.Builder transformBuilder = new IpSecTransform.Builder(sContext);
|
||||
transformBuilder.setEncryption(
|
||||
new IpSecAlgorithm(IpSecAlgorithm.CRYPT_AES_CBC, CRYPT_KEY));
|
||||
transformBuilder.setAuthentication(
|
||||
new IpSecAlgorithm(
|
||||
IpSecAlgorithm.AUTH_HMAC_SHA256, AUTH_KEY, AUTH_KEY.length * 4));
|
||||
|
||||
if (encapSocket != null) {
|
||||
transformBuilder.setIpv4Encapsulation(encapSocket, encapSocket.getPort());
|
||||
}
|
||||
|
||||
// Apply transform and check that traffic is properly encrypted
|
||||
try (IpSecTransform inTransform =
|
||||
transformBuilder.buildTunnelModeTransform(remoteOuter, inSpi);
|
||||
IpSecTransform outTransform =
|
||||
transformBuilder.buildTunnelModeTransform(localOuter, outSpi)) {
|
||||
mISM.applyTunnelModeTransform(tunnelIface, IpSecManager.DIRECTION_IN, inTransform);
|
||||
mISM.applyTunnelModeTransform(
|
||||
tunnelIface, IpSecManager.DIRECTION_OUT, outTransform);
|
||||
|
||||
innerSocketPort = test.run(testNetwork);
|
||||
}
|
||||
|
||||
// Teardown the test network
|
||||
sTNM.teardownTestNetwork(testNetwork);
|
||||
|
||||
// Remove addresses and check that interface is still present, but fails lookup-by-addr
|
||||
tunnelIface.removeAddress(localInner, innerPrefixLen);
|
||||
assertNotNull(NetworkInterface.getByName(tunnelIface.getInterfaceName()));
|
||||
assertNull(NetworkInterface.getByInetAddress(localInner));
|
||||
|
||||
// Check interface was cleaned up
|
||||
tunnelIface.close();
|
||||
assertNull(NetworkInterface.getByName(tunnelIface.getInterfaceName()));
|
||||
} finally {
|
||||
if (testNetworkCb != null) {
|
||||
sCM.unregisterNetworkCallback(testNetworkCb);
|
||||
}
|
||||
}
|
||||
|
||||
return innerSocketPort;
|
||||
}
|
||||
|
||||
private static void receiveAndValidatePacket(JavaUdpSocket socket) throws Exception {
|
||||
byte[] socketResponseBytes = socket.receive();
|
||||
assertArrayEquals(TEST_DATA, socketResponseBytes);
|
||||
}
|
||||
|
||||
private int getRandomSpi(InetAddress localOuter, InetAddress remoteOuter) throws Exception {
|
||||
// Try to allocate both in and out SPIs using the same requested SPI value.
|
||||
try (IpSecManager.SecurityParameterIndex inSpi =
|
||||
mISM.allocateSecurityParameterIndex(localOuter);
|
||||
IpSecManager.SecurityParameterIndex outSpi =
|
||||
mISM.allocateSecurityParameterIndex(remoteOuter, inSpi.getSpi()); ) {
|
||||
return inSpi.getSpi();
|
||||
}
|
||||
}
|
||||
|
||||
private EspHeader buildTransportModeEspPacket(
|
||||
int spi, InetAddress src, InetAddress dst, int port, Payload payload) throws Exception {
|
||||
IpHeader preEspIpHeader = getIpHeader(payload.getProtocolId(), src, dst, payload);
|
||||
|
||||
return new EspHeader(
|
||||
payload.getProtocolId(),
|
||||
spi,
|
||||
1, // sequence number
|
||||
CRYPT_KEY, // Same key for auth and crypt
|
||||
payload.getPacketBytes(preEspIpHeader));
|
||||
}
|
||||
|
||||
private EspHeader buildTunnelModeEspPacket(
|
||||
int spi,
|
||||
InetAddress srcInner,
|
||||
InetAddress dstInner,
|
||||
InetAddress srcOuter,
|
||||
InetAddress dstOuter,
|
||||
int port,
|
||||
int encapPort,
|
||||
Payload payload)
|
||||
throws Exception {
|
||||
IpHeader innerIp = getIpHeader(payload.getProtocolId(), srcInner, dstInner, payload);
|
||||
return new EspHeader(
|
||||
innerIp.getProtocolId(),
|
||||
spi,
|
||||
1, // sequence number
|
||||
CRYPT_KEY, // Same key for auth and crypt
|
||||
innerIp.getPacketBytes());
|
||||
}
|
||||
|
||||
private IpHeader maybeEncapPacket(
|
||||
InetAddress src, InetAddress dst, int encapPort, EspHeader espPayload)
|
||||
throws Exception {
|
||||
|
||||
Payload payload = espPayload;
|
||||
if (encapPort != 0) {
|
||||
payload = new UdpHeader(encapPort, encapPort, espPayload);
|
||||
}
|
||||
|
||||
return getIpHeader(payload.getProtocolId(), src, dst, payload);
|
||||
}
|
||||
|
||||
private byte[] getTunnelModePacket(
|
||||
int spi,
|
||||
InetAddress srcInner,
|
||||
InetAddress dstInner,
|
||||
InetAddress srcOuter,
|
||||
InetAddress dstOuter,
|
||||
int port,
|
||||
int encapPort)
|
||||
throws Exception {
|
||||
UdpHeader udp = new UdpHeader(port, port, new BytePayload(TEST_DATA));
|
||||
|
||||
EspHeader espPayload =
|
||||
buildTunnelModeEspPacket(
|
||||
spi, srcInner, dstInner, srcOuter, dstOuter, port, encapPort, udp);
|
||||
return maybeEncapPacket(srcOuter, dstOuter, encapPort, espPayload).getPacketBytes();
|
||||
}
|
||||
|
||||
private byte[] getTransportInTunnelModePacket(
|
||||
int spiInner,
|
||||
int spiOuter,
|
||||
InetAddress srcInner,
|
||||
InetAddress dstInner,
|
||||
InetAddress srcOuter,
|
||||
InetAddress dstOuter,
|
||||
int port,
|
||||
int encapPort)
|
||||
throws Exception {
|
||||
UdpHeader udp = new UdpHeader(port, port, new BytePayload(TEST_DATA));
|
||||
|
||||
EspHeader espPayload = buildTransportModeEspPacket(spiInner, srcInner, dstInner, port, udp);
|
||||
espPayload =
|
||||
buildTunnelModeEspPacket(
|
||||
spiOuter,
|
||||
srcInner,
|
||||
dstInner,
|
||||
srcOuter,
|
||||
dstOuter,
|
||||
port,
|
||||
encapPort,
|
||||
espPayload);
|
||||
return maybeEncapPacket(srcOuter, dstOuter, encapPort, espPayload).getPacketBytes();
|
||||
}
|
||||
|
||||
// Transport-in-Tunnel mode tests
|
||||
@Test
|
||||
public void testTransportInTunnelModeV4InV4() throws Exception {
|
||||
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
|
||||
checkTunnelOutput(AF_INET, AF_INET, false, true);
|
||||
checkTunnelInput(AF_INET, AF_INET, false, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransportInTunnelModeV4InV4Reflected() throws Exception {
|
||||
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
|
||||
checkTunnelReflected(AF_INET, AF_INET, false, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransportInTunnelModeV4InV4UdpEncap() throws Exception {
|
||||
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
|
||||
checkTunnelOutput(AF_INET, AF_INET, true, true);
|
||||
checkTunnelInput(AF_INET, AF_INET, true, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransportInTunnelModeV4InV4UdpEncapReflected() throws Exception {
|
||||
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
|
||||
checkTunnelReflected(AF_INET, AF_INET, false, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransportInTunnelModeV4InV6() throws Exception {
|
||||
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
|
||||
checkTunnelOutput(AF_INET, AF_INET6, false, true);
|
||||
checkTunnelInput(AF_INET, AF_INET6, false, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransportInTunnelModeV4InV6Reflected() throws Exception {
|
||||
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
|
||||
checkTunnelReflected(AF_INET, AF_INET, false, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransportInTunnelModeV6InV4() throws Exception {
|
||||
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
|
||||
checkTunnelOutput(AF_INET6, AF_INET, false, true);
|
||||
checkTunnelInput(AF_INET6, AF_INET, false, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransportInTunnelModeV6InV4Reflected() throws Exception {
|
||||
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
|
||||
checkTunnelReflected(AF_INET, AF_INET, false, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransportInTunnelModeV6InV4UdpEncap() throws Exception {
|
||||
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
|
||||
checkTunnelOutput(AF_INET6, AF_INET, true, true);
|
||||
checkTunnelInput(AF_INET6, AF_INET, true, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransportInTunnelModeV6InV4UdpEncapReflected() throws Exception {
|
||||
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
|
||||
checkTunnelReflected(AF_INET, AF_INET, false, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransportInTunnelModeV6InV6() throws Exception {
|
||||
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
|
||||
checkTunnelOutput(AF_INET, AF_INET6, false, true);
|
||||
checkTunnelInput(AF_INET, AF_INET6, false, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransportInTunnelModeV6InV6Reflected() throws Exception {
|
||||
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
|
||||
checkTunnelReflected(AF_INET, AF_INET, false, true);
|
||||
}
|
||||
|
||||
// Tunnel mode tests
|
||||
@Test
|
||||
public void testTunnelV4InV4() throws Exception {
|
||||
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
|
||||
checkTunnelOutput(AF_INET, AF_INET, false, false);
|
||||
checkTunnelInput(AF_INET, AF_INET, false, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTunnelV4InV4Reflected() throws Exception {
|
||||
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
|
||||
checkTunnelReflected(AF_INET, AF_INET, false, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTunnelV4InV4UdpEncap() throws Exception {
|
||||
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
|
||||
checkTunnelOutput(AF_INET, AF_INET, true, false);
|
||||
checkTunnelInput(AF_INET, AF_INET, true, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTunnelV4InV4UdpEncapReflected() throws Exception {
|
||||
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
|
||||
checkTunnelReflected(AF_INET, AF_INET, true, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTunnelV4InV6() throws Exception {
|
||||
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
|
||||
checkTunnelOutput(AF_INET, AF_INET6, false, false);
|
||||
checkTunnelInput(AF_INET, AF_INET6, false, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTunnelV4InV6Reflected() throws Exception {
|
||||
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
|
||||
checkTunnelReflected(AF_INET, AF_INET6, false, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTunnelV6InV4() throws Exception {
|
||||
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
|
||||
checkTunnelOutput(AF_INET6, AF_INET, false, false);
|
||||
checkTunnelInput(AF_INET6, AF_INET, false, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTunnelV6InV4Reflected() throws Exception {
|
||||
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
|
||||
checkTunnelReflected(AF_INET6, AF_INET, false, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTunnelV6InV4UdpEncap() throws Exception {
|
||||
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
|
||||
checkTunnelOutput(AF_INET6, AF_INET, true, false);
|
||||
checkTunnelInput(AF_INET6, AF_INET, true, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTunnelV6InV4UdpEncapReflected() throws Exception {
|
||||
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
|
||||
checkTunnelReflected(AF_INET6, AF_INET, true, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTunnelV6InV6() throws Exception {
|
||||
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
|
||||
checkTunnelOutput(AF_INET6, AF_INET6, false, false);
|
||||
checkTunnelInput(AF_INET6, AF_INET6, false, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTunnelV6InV6Reflected() throws Exception {
|
||||
assumeTrue(mCtsNetUtils.hasIpsecTunnelsFeature());
|
||||
checkTunnelReflected(AF_INET6, AF_INET6, false, false);
|
||||
}
|
||||
}
|
||||
61
tests/cts/net/src/android/net/cts/LocalServerSocketTest.java
Normal file
61
tests/cts/net/src/android/net/cts/LocalServerSocketTest.java
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (C) 2009 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 android.net.cts;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import android.net.LocalServerSocket;
|
||||
import android.net.LocalSocket;
|
||||
import android.net.LocalSocketAddress;
|
||||
|
||||
public class LocalServerSocketTest extends TestCase {
|
||||
|
||||
public void testLocalServerSocket() throws IOException {
|
||||
String address = "com.android.net.LocalServerSocketTest_testLocalServerSocket";
|
||||
LocalServerSocket localServerSocket = new LocalServerSocket(address);
|
||||
assertNotNull(localServerSocket.getLocalSocketAddress());
|
||||
|
||||
// create client socket
|
||||
LocalSocket clientSocket = new LocalSocket();
|
||||
|
||||
// establish connection between client and server
|
||||
clientSocket.connect(new LocalSocketAddress(address));
|
||||
LocalSocket serverSocket = localServerSocket.accept();
|
||||
|
||||
assertTrue(serverSocket.isConnected());
|
||||
assertTrue(serverSocket.isBound());
|
||||
|
||||
// send data from client to server
|
||||
OutputStream clientOutStream = clientSocket.getOutputStream();
|
||||
clientOutStream.write(12);
|
||||
InputStream serverInStream = serverSocket.getInputStream();
|
||||
assertEquals(12, serverInStream.read());
|
||||
|
||||
// send data from server to client
|
||||
OutputStream serverOutStream = serverSocket.getOutputStream();
|
||||
serverOutStream.write(3);
|
||||
InputStream clientInStream = clientSocket.getInputStream();
|
||||
assertEquals(3, clientInStream.read());
|
||||
|
||||
// close server socket
|
||||
assertNotNull(localServerSocket.getFileDescriptor());
|
||||
localServerSocket.close();
|
||||
assertNull(localServerSocket.getFileDescriptor());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2008 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 android.net.cts;
|
||||
|
||||
import android.net.LocalSocketAddress;
|
||||
import android.net.LocalSocketAddress.Namespace;
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
public class LocalSocketAddressTest extends AndroidTestCase {
|
||||
|
||||
public void testNewLocalSocketAddressWithDefaultNamespace() {
|
||||
// default namespace
|
||||
LocalSocketAddress localSocketAddress = new LocalSocketAddress("name");
|
||||
assertEquals("name", localSocketAddress.getName());
|
||||
assertEquals(Namespace.ABSTRACT, localSocketAddress.getNamespace());
|
||||
|
||||
// specify the namespace
|
||||
LocalSocketAddress localSocketAddress2 =
|
||||
new LocalSocketAddress("name2", Namespace.ABSTRACT);
|
||||
assertEquals("name2", localSocketAddress2.getName());
|
||||
assertEquals(Namespace.ABSTRACT, localSocketAddress2.getNamespace());
|
||||
|
||||
LocalSocketAddress localSocketAddress3 =
|
||||
new LocalSocketAddress("name3", Namespace.FILESYSTEM);
|
||||
assertEquals("name3", localSocketAddress3.getName());
|
||||
assertEquals(Namespace.FILESYSTEM, localSocketAddress3.getNamespace());
|
||||
|
||||
LocalSocketAddress localSocketAddress4 =
|
||||
new LocalSocketAddress("name4", Namespace.RESERVED);
|
||||
assertEquals("name4", localSocketAddress4.getName());
|
||||
assertEquals(Namespace.RESERVED, localSocketAddress4.getNamespace());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) 2009 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 android.net.cts;
|
||||
|
||||
import android.net.LocalSocketAddress.Namespace;
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
public class LocalSocketAddress_NamespaceTest extends AndroidTestCase {
|
||||
|
||||
public void testValueOf() {
|
||||
assertEquals(Namespace.ABSTRACT, Namespace.valueOf("ABSTRACT"));
|
||||
assertEquals(Namespace.RESERVED, Namespace.valueOf("RESERVED"));
|
||||
assertEquals(Namespace.FILESYSTEM, Namespace.valueOf("FILESYSTEM"));
|
||||
}
|
||||
|
||||
public void testValues() {
|
||||
Namespace[] expected = Namespace.values();
|
||||
assertEquals(Namespace.ABSTRACT, expected[0]);
|
||||
assertEquals(Namespace.RESERVED, expected[1]);
|
||||
assertEquals(Namespace.FILESYSTEM, expected[2]);
|
||||
}
|
||||
}
|
||||
470
tests/cts/net/src/android/net/cts/LocalSocketTest.java
Normal file
470
tests/cts/net/src/android/net/cts/LocalSocketTest.java
Normal file
@@ -0,0 +1,470 @@
|
||||
/*
|
||||
* Copyright (C) 2008 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 android.net.cts;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import android.net.Credentials;
|
||||
import android.net.LocalServerSocket;
|
||||
import android.net.LocalSocket;
|
||||
import android.net.LocalSocketAddress;
|
||||
import android.system.Os;
|
||||
import android.system.OsConstants;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class LocalSocketTest extends TestCase {
|
||||
private final static String ADDRESS_PREFIX = "com.android.net.LocalSocketTest";
|
||||
|
||||
public void testLocalConnections() throws IOException {
|
||||
String address = ADDRESS_PREFIX + "_testLocalConnections";
|
||||
// create client and server socket
|
||||
LocalServerSocket localServerSocket = new LocalServerSocket(address);
|
||||
LocalSocket clientSocket = new LocalSocket();
|
||||
|
||||
// establish connection between client and server
|
||||
LocalSocketAddress locSockAddr = new LocalSocketAddress(address);
|
||||
assertFalse(clientSocket.isConnected());
|
||||
clientSocket.connect(locSockAddr);
|
||||
assertTrue(clientSocket.isConnected());
|
||||
|
||||
LocalSocket serverSocket = localServerSocket.accept();
|
||||
assertTrue(serverSocket.isConnected());
|
||||
assertTrue(serverSocket.isBound());
|
||||
try {
|
||||
serverSocket.bind(localServerSocket.getLocalSocketAddress());
|
||||
fail("Cannot bind a LocalSocket from accept()");
|
||||
} catch (IOException expected) {
|
||||
}
|
||||
try {
|
||||
serverSocket.connect(locSockAddr);
|
||||
fail("Cannot connect a LocalSocket from accept()");
|
||||
} catch (IOException expected) {
|
||||
}
|
||||
|
||||
Credentials credent = clientSocket.getPeerCredentials();
|
||||
assertTrue(0 != credent.getPid());
|
||||
|
||||
// send data from client to server
|
||||
OutputStream clientOutStream = clientSocket.getOutputStream();
|
||||
clientOutStream.write(12);
|
||||
InputStream serverInStream = serverSocket.getInputStream();
|
||||
assertEquals(12, serverInStream.read());
|
||||
|
||||
//send data from server to client
|
||||
OutputStream serverOutStream = serverSocket.getOutputStream();
|
||||
serverOutStream.write(3);
|
||||
InputStream clientInStream = clientSocket.getInputStream();
|
||||
assertEquals(3, clientInStream.read());
|
||||
|
||||
// Test sending and receiving file descriptors
|
||||
clientSocket.setFileDescriptorsForSend(new FileDescriptor[]{FileDescriptor.in});
|
||||
clientOutStream.write(32);
|
||||
assertEquals(32, serverInStream.read());
|
||||
|
||||
FileDescriptor[] out = serverSocket.getAncillaryFileDescriptors();
|
||||
assertEquals(1, out.length);
|
||||
FileDescriptor fd = clientSocket.getFileDescriptor();
|
||||
assertTrue(fd.valid());
|
||||
|
||||
//shutdown input stream of client
|
||||
clientSocket.shutdownInput();
|
||||
assertEquals(-1, clientInStream.read());
|
||||
|
||||
//shutdown output stream of client
|
||||
clientSocket.shutdownOutput();
|
||||
try {
|
||||
clientOutStream.write(10);
|
||||
fail("testLocalSocket shouldn't come to here");
|
||||
} catch (IOException e) {
|
||||
// expected
|
||||
}
|
||||
|
||||
//shutdown input stream of server
|
||||
serverSocket.shutdownInput();
|
||||
assertEquals(-1, serverInStream.read());
|
||||
|
||||
//shutdown output stream of server
|
||||
serverSocket.shutdownOutput();
|
||||
try {
|
||||
serverOutStream.write(10);
|
||||
fail("testLocalSocket shouldn't come to here");
|
||||
} catch (IOException e) {
|
||||
// expected
|
||||
}
|
||||
|
||||
//close client socket
|
||||
clientSocket.close();
|
||||
try {
|
||||
clientInStream.read();
|
||||
fail("testLocalSocket shouldn't come to here");
|
||||
} catch (IOException e) {
|
||||
// expected
|
||||
}
|
||||
|
||||
//close server socket
|
||||
serverSocket.close();
|
||||
try {
|
||||
serverInStream.read();
|
||||
fail("testLocalSocket shouldn't come to here");
|
||||
} catch (IOException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
public void testAccessors() throws IOException {
|
||||
String address = ADDRESS_PREFIX + "_testAccessors";
|
||||
LocalSocket socket = new LocalSocket();
|
||||
LocalSocketAddress addr = new LocalSocketAddress(address);
|
||||
|
||||
assertFalse(socket.isBound());
|
||||
socket.bind(addr);
|
||||
assertTrue(socket.isBound());
|
||||
assertEquals(addr, socket.getLocalSocketAddress());
|
||||
|
||||
String str = socket.toString();
|
||||
assertTrue(str.contains("impl:android.net.LocalSocketImpl"));
|
||||
|
||||
socket.setReceiveBufferSize(1999);
|
||||
assertEquals(1999 << 1, socket.getReceiveBufferSize());
|
||||
|
||||
socket.setSendBufferSize(3998);
|
||||
assertEquals(3998 << 1, socket.getSendBufferSize());
|
||||
|
||||
assertEquals(0, socket.getSoTimeout());
|
||||
socket.setSoTimeout(1996);
|
||||
assertTrue(socket.getSoTimeout() > 0);
|
||||
|
||||
try {
|
||||
socket.getRemoteSocketAddress();
|
||||
fail("testLocalSocketSecondary shouldn't come to here");
|
||||
} catch (UnsupportedOperationException e) {
|
||||
// expected
|
||||
}
|
||||
|
||||
try {
|
||||
socket.isClosed();
|
||||
fail("testLocalSocketSecondary shouldn't come to here");
|
||||
} catch (UnsupportedOperationException e) {
|
||||
// expected
|
||||
}
|
||||
|
||||
try {
|
||||
socket.isInputShutdown();
|
||||
fail("testLocalSocketSecondary shouldn't come to here");
|
||||
} catch (UnsupportedOperationException e) {
|
||||
// expected
|
||||
}
|
||||
|
||||
try {
|
||||
socket.isOutputShutdown();
|
||||
fail("testLocalSocketSecondary shouldn't come to here");
|
||||
} catch (UnsupportedOperationException e) {
|
||||
// expected
|
||||
}
|
||||
|
||||
try {
|
||||
socket.connect(addr, 2005);
|
||||
fail("testLocalSocketSecondary shouldn't come to here");
|
||||
} catch (UnsupportedOperationException e) {
|
||||
// expected
|
||||
}
|
||||
|
||||
socket.close();
|
||||
}
|
||||
|
||||
// http://b/31205169
|
||||
public void testSetSoTimeout_readTimeout() throws Exception {
|
||||
String address = ADDRESS_PREFIX + "_testSetSoTimeout_readTimeout";
|
||||
|
||||
try (LocalSocketPair socketPair = LocalSocketPair.createConnectedSocketPair(address)) {
|
||||
final LocalSocket clientSocket = socketPair.clientSocket;
|
||||
|
||||
// Set the timeout in millis.
|
||||
int timeoutMillis = 1000;
|
||||
clientSocket.setSoTimeout(timeoutMillis);
|
||||
|
||||
// Avoid blocking the test run if timeout doesn't happen by using a separate thread.
|
||||
Callable<Result> reader = () -> {
|
||||
try {
|
||||
clientSocket.getInputStream().read();
|
||||
return Result.noException("Did not block");
|
||||
} catch (IOException e) {
|
||||
return Result.exception(e);
|
||||
}
|
||||
};
|
||||
// Allow the configured timeout, plus some slop.
|
||||
int allowedTime = timeoutMillis + 2000;
|
||||
Result result = runInSeparateThread(allowedTime, reader);
|
||||
|
||||
// Check the message was a timeout, it's all we have to go on.
|
||||
String expectedMessage = Os.strerror(OsConstants.EAGAIN);
|
||||
result.assertThrewIOException(expectedMessage);
|
||||
}
|
||||
}
|
||||
|
||||
// http://b/31205169
|
||||
public void testSetSoTimeout_writeTimeout() throws Exception {
|
||||
String address = ADDRESS_PREFIX + "_testSetSoTimeout_writeTimeout";
|
||||
|
||||
try (LocalSocketPair socketPair = LocalSocketPair.createConnectedSocketPair(address)) {
|
||||
final LocalSocket clientSocket = socketPair.clientSocket;
|
||||
|
||||
// Set the timeout in millis.
|
||||
int timeoutMillis = 1000;
|
||||
clientSocket.setSoTimeout(timeoutMillis);
|
||||
|
||||
// Set a small buffer size so we know we can flood it.
|
||||
clientSocket.setSendBufferSize(100);
|
||||
final int bufferSize = clientSocket.getSendBufferSize();
|
||||
|
||||
// Avoid blocking the test run if timeout doesn't happen by using a separate thread.
|
||||
Callable<Result> writer = () -> {
|
||||
try {
|
||||
byte[] toWrite = new byte[bufferSize * 2];
|
||||
clientSocket.getOutputStream().write(toWrite);
|
||||
return Result.noException("Did not block");
|
||||
} catch (IOException e) {
|
||||
return Result.exception(e);
|
||||
}
|
||||
};
|
||||
// Allow the configured timeout, plus some slop.
|
||||
int allowedTime = timeoutMillis + 2000;
|
||||
|
||||
Result result = runInSeparateThread(allowedTime, writer);
|
||||
|
||||
// Check the message was a timeout, it's all we have to go on.
|
||||
String expectedMessage = Os.strerror(OsConstants.EAGAIN);
|
||||
result.assertThrewIOException(expectedMessage);
|
||||
}
|
||||
}
|
||||
|
||||
public void testAvailable() throws Exception {
|
||||
String address = ADDRESS_PREFIX + "_testAvailable";
|
||||
|
||||
try (LocalSocketPair socketPair = LocalSocketPair.createConnectedSocketPair(address)) {
|
||||
LocalSocket clientSocket = socketPair.clientSocket;
|
||||
LocalSocket serverSocket = socketPair.serverSocket.accept();
|
||||
|
||||
OutputStream clientOutputStream = clientSocket.getOutputStream();
|
||||
InputStream serverInputStream = serverSocket.getInputStream();
|
||||
assertEquals(0, serverInputStream.available());
|
||||
|
||||
byte[] buffer = new byte[50];
|
||||
clientOutputStream.write(buffer);
|
||||
assertEquals(50, serverInputStream.available());
|
||||
|
||||
InputStream clientInputStream = clientSocket.getInputStream();
|
||||
OutputStream serverOutputStream = serverSocket.getOutputStream();
|
||||
assertEquals(0, clientInputStream.available());
|
||||
serverOutputStream.write(buffer);
|
||||
assertEquals(50, serverInputStream.available());
|
||||
|
||||
serverSocket.close();
|
||||
}
|
||||
}
|
||||
|
||||
// http://b/34095140
|
||||
public void testLocalSocketCreatedFromFileDescriptor() throws Exception {
|
||||
String address = ADDRESS_PREFIX + "_testLocalSocketCreatedFromFileDescriptor";
|
||||
|
||||
// Establish connection between a local client and server to get a valid client socket file
|
||||
// descriptor.
|
||||
try (LocalSocketPair socketPair = LocalSocketPair.createConnectedSocketPair(address)) {
|
||||
// Extract the client FileDescriptor we can use.
|
||||
FileDescriptor fileDescriptor = socketPair.clientSocket.getFileDescriptor();
|
||||
assertTrue(fileDescriptor.valid());
|
||||
|
||||
// Create the LocalSocket we want to test.
|
||||
LocalSocket clientSocketCreatedFromFileDescriptor =
|
||||
LocalSocket.createConnectedLocalSocket(fileDescriptor);
|
||||
assertTrue(clientSocketCreatedFromFileDescriptor.isConnected());
|
||||
assertTrue(clientSocketCreatedFromFileDescriptor.isBound());
|
||||
|
||||
// Test the LocalSocket can be used for communication.
|
||||
LocalSocket serverSocket = socketPair.serverSocket.accept();
|
||||
OutputStream clientOutputStream =
|
||||
clientSocketCreatedFromFileDescriptor.getOutputStream();
|
||||
InputStream serverInputStream = serverSocket.getInputStream();
|
||||
|
||||
clientOutputStream.write(12);
|
||||
assertEquals(12, serverInputStream.read());
|
||||
|
||||
// Closing clientSocketCreatedFromFileDescriptor does not close the file descriptor.
|
||||
clientSocketCreatedFromFileDescriptor.close();
|
||||
assertTrue(fileDescriptor.valid());
|
||||
|
||||
// .. while closing the LocalSocket that owned the file descriptor does.
|
||||
socketPair.clientSocket.close();
|
||||
assertFalse(fileDescriptor.valid());
|
||||
}
|
||||
}
|
||||
|
||||
public void testFlush() throws Exception {
|
||||
String address = ADDRESS_PREFIX + "_testFlush";
|
||||
|
||||
try (LocalSocketPair socketPair = LocalSocketPair.createConnectedSocketPair(address)) {
|
||||
LocalSocket clientSocket = socketPair.clientSocket;
|
||||
LocalSocket serverSocket = socketPair.serverSocket.accept();
|
||||
|
||||
OutputStream clientOutputStream = clientSocket.getOutputStream();
|
||||
InputStream serverInputStream = serverSocket.getInputStream();
|
||||
testFlushWorks(clientOutputStream, serverInputStream);
|
||||
|
||||
OutputStream serverOutputStream = serverSocket.getOutputStream();
|
||||
InputStream clientInputStream = clientSocket.getInputStream();
|
||||
testFlushWorks(serverOutputStream, clientInputStream);
|
||||
|
||||
serverSocket.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void testFlushWorks(OutputStream outputStream, InputStream inputStream)
|
||||
throws Exception {
|
||||
final int bytesToTransfer = 50;
|
||||
StreamReader inputStreamReader = new StreamReader(inputStream, bytesToTransfer);
|
||||
|
||||
byte[] buffer = new byte[bytesToTransfer];
|
||||
outputStream.write(buffer);
|
||||
assertEquals(bytesToTransfer, inputStream.available());
|
||||
|
||||
// Start consuming the data.
|
||||
inputStreamReader.start();
|
||||
|
||||
// This doesn't actually flush any buffers, it just polls until the reader has read all the
|
||||
// bytes.
|
||||
outputStream.flush();
|
||||
|
||||
inputStreamReader.waitForCompletion(5000);
|
||||
inputStreamReader.assertBytesRead(bytesToTransfer);
|
||||
assertEquals(0, inputStream.available());
|
||||
}
|
||||
|
||||
private static class StreamReader extends Thread {
|
||||
private final InputStream is;
|
||||
private final int expectedByteCount;
|
||||
private final CountDownLatch completeLatch = new CountDownLatch(1);
|
||||
|
||||
private volatile Exception exception;
|
||||
private int bytesRead;
|
||||
|
||||
private StreamReader(InputStream is, int expectedByteCount) {
|
||||
this.is = is;
|
||||
this.expectedByteCount = expectedByteCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
byte[] buffer = new byte[10];
|
||||
int readCount;
|
||||
while ((readCount = is.read(buffer)) >= 0) {
|
||||
bytesRead += readCount;
|
||||
if (bytesRead >= expectedByteCount) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
exception = e;
|
||||
} finally {
|
||||
completeLatch.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
public void waitForCompletion(long waitMillis) throws Exception {
|
||||
if (!completeLatch.await(waitMillis, TimeUnit.MILLISECONDS)) {
|
||||
fail("Timeout waiting for completion");
|
||||
}
|
||||
if (exception != null) {
|
||||
throw new Exception("Read failed", exception);
|
||||
}
|
||||
}
|
||||
|
||||
public void assertBytesRead(int expected) {
|
||||
assertEquals(expected, bytesRead);
|
||||
}
|
||||
}
|
||||
|
||||
private static class Result {
|
||||
private final String type;
|
||||
private final Exception e;
|
||||
|
||||
private Result(String type, Exception e) {
|
||||
this.type = type;
|
||||
this.e = e;
|
||||
}
|
||||
|
||||
static Result noException(String description) {
|
||||
return new Result(description, null);
|
||||
}
|
||||
|
||||
static Result exception(Exception e) {
|
||||
return new Result(e.getClass().getName(), e);
|
||||
}
|
||||
|
||||
void assertThrewIOException(String expectedMessage) {
|
||||
assertEquals("Unexpected result type", IOException.class.getName(), type);
|
||||
assertEquals("Unexpected exception message", expectedMessage, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static Result runInSeparateThread(int allowedTime, final Callable<Result> callable)
|
||||
throws Exception {
|
||||
ExecutorService service = Executors.newSingleThreadScheduledExecutor();
|
||||
Future<Result> future = service.submit(callable);
|
||||
Result result = future.get(allowedTime, TimeUnit.MILLISECONDS);
|
||||
if (!future.isDone()) {
|
||||
fail("Worker thread appears blocked");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static class LocalSocketPair implements AutoCloseable {
|
||||
static LocalSocketPair createConnectedSocketPair(String address) throws Exception {
|
||||
LocalServerSocket localServerSocket = new LocalServerSocket(address);
|
||||
final LocalSocket clientSocket = new LocalSocket();
|
||||
|
||||
// Establish connection between client and server
|
||||
LocalSocketAddress locSockAddr = new LocalSocketAddress(address);
|
||||
clientSocket.connect(locSockAddr);
|
||||
assertTrue(clientSocket.isConnected());
|
||||
return new LocalSocketPair(localServerSocket, clientSocket);
|
||||
}
|
||||
|
||||
final LocalServerSocket serverSocket;
|
||||
final LocalSocket clientSocket;
|
||||
|
||||
LocalSocketPair(LocalServerSocket serverSocket, LocalSocket clientSocket) {
|
||||
this.serverSocket = serverSocket;
|
||||
this.clientSocket = clientSocket;
|
||||
}
|
||||
|
||||
public void close() throws Exception {
|
||||
serverSocket.close();
|
||||
clientSocket.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user