diff options
9 files changed, 727 insertions, 0 deletions
diff --git a/services/tests/servicestests/Android.mk b/services/tests/servicestests/Android.mk index bb4507d8386a..d47a67c56317 100644 --- a/services/tests/servicestests/Android.mk +++ b/services/tests/servicestests/Android.mk @@ -27,6 +27,10 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ 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 205c8dea18b0..cc682c4ee985 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 000000000000..0c9b83962833 --- /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 000000000000..ca9fc4c439d2 --- /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 Binary files differnew file mode 100644 index 000000000000..6093303658b5 --- /dev/null +++ b/services/tests/servicestests/res/raw/conntestapp 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 000000000000..f9719711cad9 --- /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 000000000000..02afe83efb99 --- /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 000000000000..0da3562c9afa --- /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 000000000000..11ebfca67ad4 --- /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 |