Merge "Add tests for verifying network availability on activity start." into oc-dev
diff --git a/services/tests/servicestests/Android.mk b/services/tests/servicestests/Android.mk
index bb4507d..d47a67c 100644
--- a/services/tests/servicestests/Android.mk
+++ b/services/tests/servicestests/Android.mk
@@ -27,6 +27,10 @@
ShortcutManagerTestUtils \
truth-prebuilt
+LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/aidl
+
+LOCAL_SRC_FILES += aidl/com/android/servicestests/aidl/INetworkStateObserver.aidl
+
LOCAL_JAVA_LIBRARIES := android.test.runner
LOCAL_PACKAGE_NAME := FrameworksServicesTests
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 205c8de..cc682c4 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -48,6 +48,8 @@
<uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" />
<uses-permission android:name="android.permission.INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
+ <uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" />
+ <uses-permission android:name="android.permission.DELETE_PACKAGES" />
<application>
<uses-library android:name="android.test.runner" />
diff --git a/services/tests/servicestests/aidl/Android.mk b/services/tests/servicestests/aidl/Android.mk
new file mode 100644
index 0000000..0c9b839
--- /dev/null
+++ b/services/tests/servicestests/aidl/Android.mk
@@ -0,0 +1,23 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := tests
+LOCAL_SDK_VERSION := current
+LOCAL_SRC_FILES := \
+ com/android/servicestests/aidl/INetworkStateObserver.aidl
+LOCAL_MODULE := servicestests-aidl
+include $(BUILD_STATIC_JAVA_LIBRARY)
\ No newline at end of file
diff --git a/services/tests/servicestests/aidl/com/android/servicestests/aidl/INetworkStateObserver.aidl b/services/tests/servicestests/aidl/com/android/servicestests/aidl/INetworkStateObserver.aidl
new file mode 100644
index 0000000..ca9fc4c
--- /dev/null
+++ b/services/tests/servicestests/aidl/com/android/servicestests/aidl/INetworkStateObserver.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+package com.android.servicestests.aidl;
+
+oneway interface INetworkStateObserver {
+ /**
+ * {@param resultData} will be in the format
+ * NetinfoState|NetinfoDetailedState|RealConnectionCheck|RealConnectionCheckDetails|Netinfo.
+ * For detailed info, see
+ * servicestests/test-apps/ConnTestApp/.../ConnTestActivity#checkNetworkStatus
+ */
+ void onNetworkStateChecked(String resultData);
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/res/raw/conntestapp b/services/tests/servicestests/res/raw/conntestapp
new file mode 100644
index 0000000..6093303
--- /dev/null
+++ b/services/tests/servicestests/res/raw/conntestapp
Binary files differ
diff --git a/services/tests/servicestests/src/com/android/server/net/ConnOnActivityStartTest.java b/services/tests/servicestests/src/com/android/server/net/ConnOnActivityStartTest.java
new file mode 100644
index 0000000..f971971
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/net/ConnOnActivityStartTest.java
@@ -0,0 +1,443 @@
+/*
+ * 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.
+ */
+
+package com.android.server.net;
+
+import static android.util.DebugUtils.valueToString;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.frameworks.servicestests.R;
+import com.android.servicestests.aidl.INetworkStateObserver;
+
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.content.pm.IPackageDeleteObserver;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageManager;
+import android.net.NetworkInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.UiDevice;
+import android.util.Log;
+
+import libcore.io.IoUtils;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests for verifying network availability on activity start.
+ *
+ * To run the tests, use
+ *
+ * runtest -c com.android.server.net.ConnOnActivityStartTest frameworks-services
+ *
+ * or the following steps:
+ *
+ * Build: m FrameworksServicesTests
+ * Install: adb install -r \
+ * ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk
+ * Run: adb shell am instrument -e class com.android.server.net.ConnOnActivityStartTest -w \
+ * com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
+ */
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class ConnOnActivityStartTest {
+ private static final String TAG = ConnOnActivityStartTest.class.getSimpleName();
+
+ private static final String ACTION_INSTALL_COMPLETE = "com.android.server.net.INSTALL_COMPLETE";
+
+ private static final String TEST_APP_URI =
+ "android.resource://com.android.frameworks.servicestests/raw/conntestapp";
+ private static final String TEST_PKG = "com.android.servicestests.apps.conntestapp";
+ private static final String TEST_ACTIVITY_CLASS = TEST_PKG + ".ConnTestActivity";
+
+ private static final String ACTION_FINISH_ACTIVITY = TEST_PKG + ".FINISH";
+
+ private static final String EXTRA_NETWORK_STATE_OBSERVER = TEST_PKG + ".observer";
+
+ private static final int WAIT_FOR_INSTALL_TIMEOUT_MS = 2000; // 2 sec
+
+ private static final int NETWORK_CHECK_TIMEOUT_MS = 6000; // 6 sec
+
+ private static final int SCREEN_ON_DELAY_MS = 500; // 0.5 sec
+
+ private static final String NETWORK_STATUS_SEPARATOR = "\\|";
+
+ private static final int REPEAT_TEST_COUNT = 5;
+
+ private static Context mContext;
+ private static UiDevice mUiDevice;
+ private static int mTestPkgUid;
+
+ @BeforeClass
+ public static void setUpOnce() throws Exception {
+ mContext = InstrumentationRegistry.getContext();
+ mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+
+ installAppAndAssertInstalled();
+ mContext.getPackageManager().setApplicationEnabledSetting(TEST_PKG,
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
+ mTestPkgUid = mContext.getPackageManager().getPackageUid(TEST_PKG, 0);
+ }
+
+ @AfterClass
+ public static void tearDownOnce() {
+ mContext.getPackageManager().deletePackage(TEST_PKG,
+ new IPackageDeleteObserver.Stub() {
+ @Override
+ public void packageDeleted(String packageName, int returnCode)
+ throws RemoteException {
+ Log.e(TAG, packageName + " deleted, returnCode: " + returnCode);
+ }
+ }, 0);
+ }
+
+ @Test
+ public void testStartActivity_batterySaver() throws Exception {
+ setBatterySaverMode(true);
+ try {
+ testConnOnActivityStart("testStartActivity_batterySaver");
+ } finally {
+ setBatterySaverMode(false);
+ }
+ }
+
+ @Test
+ public void testStartActivity_dataSaver() throws Exception {
+ setDataSaverMode(true);
+ try {
+ testConnOnActivityStart("testStartActivity_dataSaver");
+ } finally {
+ setDataSaverMode(false);
+ }
+ }
+
+ @Test
+ public void testStartActivity_dozeMode() throws Exception {
+ setDozeMode(true);
+ try {
+ testConnOnActivityStart("testStartActivity_dozeMode");
+ } finally {
+ setDozeMode(false);
+ }
+ }
+
+ @Test
+ public void testStartActivity_appStandby() throws Exception {
+ try{
+ turnBatteryOff();
+ setAppIdle(true);
+ SystemClock.sleep(30000);
+ turnScreenOn();
+ startActivityAndCheckNetworkAccess();
+ } finally {
+ turnBatteryOn();
+ setAppIdle(false);
+ }
+ }
+
+ @Test
+ public void testStartActivity_backgroundRestrict() throws Exception {
+ updateRestrictBackgroundBlacklist(true);
+ try {
+ testConnOnActivityStart("testStartActivity_backgroundRestrict");
+ } finally {
+ updateRestrictBackgroundBlacklist(false);
+ }
+ }
+
+ private void testConnOnActivityStart(String testName) throws Exception {
+ for (int i = 1; i <= REPEAT_TEST_COUNT; ++i) {
+ try {
+ Log.d(TAG, testName + " Start #" + i);
+ turnScreenOn();
+ SystemClock.sleep(SCREEN_ON_DELAY_MS);
+ startActivityAndCheckNetworkAccess();
+ Log.d(TAG, testName + " end #" + i);
+ } finally {
+ finishActivity();
+ }
+ }
+ }
+
+ // TODO: Some of these methods are also used in CTS, so instead of duplicating code,
+ // create a static library which can be used by both servicestests and cts.
+ private void setBatterySaverMode(boolean enabled) throws Exception {
+ if (enabled) {
+ turnBatteryOff();
+ executeCommand("settings put global low_power 1");
+ } else {
+ executeCommand("settings put global low_power 0");
+ turnBatteryOn();
+ }
+ final String result = executeCommand("settings get global low_power");
+ assertEquals(enabled ? "1" : "0", result);
+ }
+
+ private void setDataSaverMode(boolean enabled) throws Exception {
+ executeCommand("cmd netpolicy set restrict-background " + enabled);
+ final String output = executeCommand("cmd netpolicy get restrict-background");
+ final String expectedSuffix = enabled ? "enabled" : "disabled";
+ assertTrue("output '" + output + "' should end with '" + expectedSuffix + "'",
+ output.endsWith(expectedSuffix));
+ }
+
+ private void setDozeMode(boolean enabled) throws Exception {
+ if (enabled) {
+ turnBatteryOff();
+ turnScreenOff();
+ executeCommand("dumpsys deviceidle force-idle deep");
+ } else {
+ turnScreenOn();
+ turnBatteryOn();
+ executeCommand("dumpsys deviceidle unforce");
+ }
+ assertDelayedCommandResult("dumpsys deviceidle get deep", enabled ? "IDLE" : "ACTIVE",
+ 5 /* maxTries */, 500 /* napTimeMs */);
+ }
+
+ private void setAppIdle(boolean enabled) throws Exception {
+ executeCommand("am set-inactive " + TEST_PKG + " " + enabled);
+ assertDelayedCommandResult("am get-inactive " + TEST_PKG, "Idle=" + enabled,
+ 10 /* maxTries */, 2000 /* napTimeMs */);
+ }
+
+ private void updateRestrictBackgroundBlacklist(boolean add) throws Exception {
+ if (add) {
+ executeCommand("cmd netpolicy add restrict-background-blacklist " + mTestPkgUid);
+ } else {
+ executeCommand("cmd netpolicy remove restrict-background-blacklist " + mTestPkgUid);
+ }
+ assertRestrictBackground("restrict-background-blacklist", mTestPkgUid, add);
+ }
+
+ 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 = executeCommand("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(1000);
+ }
+ fail(list + " check for uid " + uid + " failed: expected " + expected + ", got " + actual
+ + ". Full list: " + uids);
+ }
+
+ private void turnBatteryOff() throws Exception {
+ executeCommand("cmd battery unplug");
+ }
+
+ private void turnBatteryOn() throws Exception {
+ executeCommand("cmd battery reset");
+ }
+
+ private void turnScreenOff() throws Exception {
+ executeCommand("input keyevent KEYCODE_SLEEP");
+ }
+
+ private void turnScreenOn() throws Exception {
+ executeCommand("input keyevent KEYCODE_WAKEUP");
+ executeCommand("wm dismiss-keyguard");
+ }
+
+ private String executeCommand(String cmd) throws IOException {
+ final String result = mUiDevice.executeShellCommand(cmd).trim();
+ Log.d(TAG, String.format("Result for '%s': %s", cmd, result));
+ return result;
+ }
+
+ private void assertDelayedCommandResult(String cmd, String expectedResult,
+ int maxTries, int napTimeMs) throws IOException {
+ String result = "";
+ for (int i = 1; i <= maxTries; ++i) {
+ result = executeCommand(cmd);
+ if (expectedResult.equals(result)) {
+ return;
+ }
+ Log.v(TAG, "Command '" + cmd + "' returned '" + result + " instead of '"
+ + expectedResult + "' on attempt #" + i
+ + "; sleeping " + napTimeMs + "ms before trying again");
+ SystemClock.sleep(napTimeMs);
+ }
+ fail("Command '" + cmd + "' did not return '" + expectedResult + "' after "
+ + maxTries + " attempts. Last result: '" + result + "'");
+ }
+
+ private void startActivityAndCheckNetworkAccess() throws Exception {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final Intent launchIntent = new Intent().setComponent(
+ new ComponentName(TEST_PKG, TEST_ACTIVITY_CLASS));
+ final Bundle extras = new Bundle();
+ final String[] errors = new String[] {null};
+ extras.putBinder(EXTRA_NETWORK_STATE_OBSERVER, new INetworkStateObserver.Stub() {
+ @Override
+ public void onNetworkStateChecked(String resultData) {
+ errors[0] = checkForAvailability(resultData);
+ latch.countDown();
+ }
+ });
+ launchIntent.putExtras(extras);
+ mContext.startActivity(launchIntent);
+ if (latch.await(NETWORK_CHECK_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ if (!errors[0].isEmpty()) {
+ fail("Network not available for test app " + mTestPkgUid);
+ }
+ } else {
+ fail("Timed out waiting for network availability status from test app " + mTestPkgUid);
+ }
+ }
+
+ private void finishActivity() {
+ final Intent finishIntent = new Intent(ACTION_FINISH_ACTIVITY)
+ .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ mContext.sendBroadcast(finishIntent);
+ }
+
+ private String checkForAvailability(String resultData) {
+ if (resultData == null) {
+ assertNotNull("Network status from app2 is null, Uid: " + mTestPkgUid, resultData);
+ }
+ // Network status format is described on MyBroadcastReceiver.checkNetworkStatus()
+ final String[] parts = resultData.split(NETWORK_STATUS_SEPARATOR);
+ assertEquals("Wrong network status: " + resultData + ", Uid: " + mTestPkgUid,
+ 5, parts.length); // Sanity check
+ final NetworkInfo.State state = parts[0].equals("null")
+ ? null : NetworkInfo.State.valueOf(parts[0]);
+ final NetworkInfo.DetailedState detailedState = parts[1].equals("null")
+ ? null : NetworkInfo.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 NetworkInfo.State expectedState = NetworkInfo.State.CONNECTED;
+ final NetworkInfo.DetailedState expectedDetailedState = NetworkInfo.DetailedState.CONNECTED;
+
+ if (true != connected) {
+ errors.append(String.format("External site connection failed: expected %s, got %s\n",
+ true, 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();
+ }
+
+ private static void installAppAndAssertInstalled() throws Exception {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final int[] result = {PackageInstaller.STATUS_SUCCESS};
+ final BroadcastReceiver installStatusReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String pkgName = intent.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME);
+ if (!TEST_PKG.equals(pkgName)) {
+ return;
+ }
+ result[0] = intent.getIntExtra(PackageInstaller.EXTRA_STATUS,
+ PackageInstaller.STATUS_FAILURE);
+ latch.countDown();
+ }
+ };
+ mContext.registerReceiver(installStatusReceiver, new IntentFilter(ACTION_INSTALL_COMPLETE));
+ try {
+ installApp();
+ if (latch.await(WAIT_FOR_INSTALL_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ if (result[0] != PackageInstaller.STATUS_SUCCESS) {
+ fail("Couldn't install test app, result: "
+ + valueToString(PackageInstaller.class, "STATUS_", result[0]));
+ }
+ } else {
+ fail("Timed out waiting for the test app to install");
+ }
+ } finally {
+ mContext.unregisterReceiver(installStatusReceiver);
+ }
+ }
+
+ private static void installApp() throws Exception {
+ final Uri packageUri = Uri.parse(TEST_APP_URI);
+ final InputStream in = mContext.getContentResolver().openInputStream(packageUri);
+
+ final PackageInstaller packageInstaller
+ = mContext.getPackageManager().getPackageInstaller();
+ final PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
+ PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+ params.setAppPackageName(TEST_PKG);
+
+ final int sessionId = packageInstaller.createSession(params);
+ final PackageInstaller.Session session = packageInstaller.openSession(sessionId);
+
+ OutputStream out = null;
+ try {
+ out = session.openWrite(TAG, 0, -1);
+ final byte[] buffer = new byte[65536];
+ int c;
+ while ((c = in.read(buffer)) != -1) {
+ out.write(buffer, 0, c);
+ }
+ session.fsync(out);
+ } finally {
+ IoUtils.closeQuietly(in);
+ IoUtils.closeQuietly(out);
+ }
+ session.commit(createIntentSender(mContext, sessionId));
+ }
+
+ private static IntentSender createIntentSender(Context context, int sessionId) {
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(
+ context, sessionId, new Intent(ACTION_INSTALL_COMPLETE), 0);
+ return pendingIntent.getIntentSender();
+ }
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/test-apps/ConnTestApp/Android.mk b/services/tests/servicestests/test-apps/ConnTestApp/Android.mk
new file mode 100644
index 0000000..02afe83
--- /dev/null
+++ b/services/tests/servicestests/test-apps/ConnTestApp/Android.mk
@@ -0,0 +1,30 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_SDK_VERSION := current
+
+LOCAL_STATIC_JAVA_LIBRARIES := servicestests-aidl
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := ConnTestApp
+LOCAL_CERTIFICATE := platform
+LOCAL_DEX_PREOPT := false
+LOCAL_PROGUARD_ENABLED := disabled
+
+include $(BUILD_PACKAGE)
\ No newline at end of file
diff --git a/services/tests/servicestests/test-apps/ConnTestApp/AndroidManifest.xml b/services/tests/servicestests/test-apps/ConnTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..0da3562
--- /dev/null
+++ b/services/tests/servicestests/test-apps/ConnTestApp/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.servicestests.apps.conntestapp">
+
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.INTERNET" />
+
+ <application>
+ <activity android:name=".ConnTestActivity"
+ android:exported="true" />
+ </application>
+
+</manifest>
\ No newline at end of file
diff --git a/services/tests/servicestests/test-apps/ConnTestApp/src/com/android/servicestests/apps/conntestapp/ConnTestActivity.java b/services/tests/servicestests/test-apps/ConnTestApp/src/com/android/servicestests/apps/conntestapp/ConnTestActivity.java
new file mode 100644
index 0000000..11ebfca
--- /dev/null
+++ b/services/tests/servicestests/test-apps/ConnTestApp/src/com/android/servicestests/apps/conntestapp/ConnTestActivity.java
@@ -0,0 +1,170 @@
+/*
+ * 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.
+ */
+
+package com.android.servicestests.apps.conntestapp;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.servicestests.aidl.INetworkStateObserver;
+
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+public class ConnTestActivity extends Activity {
+ private static final String TAG = ConnTestActivity.class.getSimpleName();
+
+ private static final String TEST_PKG = ConnTestActivity.class.getPackage().getName();
+ private static final String ACTION_FINISH_ACTIVITY = TEST_PKG + ".FINISH";
+ private static final String EXTRA_NETWORK_STATE_OBSERVER = TEST_PKG + ".observer";
+
+ private static final int NETWORK_TIMEOUT_MS = 5 * 1000;
+
+ private static final String NETWORK_STATUS_TEMPLATE = "%s|%s|%s|%s|%s";
+
+ private BroadcastReceiver finishCommandReceiver = null;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ notifyNetworkStateObserver();
+
+ finishCommandReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ ConnTestActivity.this.finish();
+ }
+ };
+ registerReceiver(finishCommandReceiver, new IntentFilter(ACTION_FINISH_ACTIVITY));
+ }
+
+ @Override
+ public void onStop() {
+ if (finishCommandReceiver != null) {
+ unregisterReceiver(finishCommandReceiver);
+ }
+ super.onStop();
+ }
+
+ private void notifyNetworkStateObserver() {
+ if (getIntent() == null) {
+ return;
+ }
+
+ final Bundle extras = getIntent().getExtras();
+ if (extras == null) {
+ return;
+ }
+ final INetworkStateObserver observer = INetworkStateObserver.Stub.asInterface(
+ extras.getBinder(EXTRA_NETWORK_STATE_OBSERVER));
+ if (observer != null) {
+ AsyncTask.execute(() -> {
+ try {
+ observer.onNetworkStateChecked(checkNetworkStatus(ConnTestActivity.this));
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error occured while notifying the observer: " + e);
+ }
+ });
+ }
+ }
+
+ /**
+ * 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>
+ */
+ private String checkNetworkStatus(Context context) {
+ final ConnectivityManager cm =
+ (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ 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;
+ }
+
+ private int getUid(Context context) {
+ final String packageName = context.getPackageName();
+ try {
+ return context.getPackageManager().getPackageUid(packageName, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new IllegalStateException("Could not get UID for " + packageName, e);
+ }
+ }
+}
\ No newline at end of file