Merge history of CTS
BUG: 167962976
Test: TH
Merged-In: I30d52c4df571c894b7797300e8f56ddd4b2cc2dd
Change-Id: Id343f5a31604abfe70140343bffab20503cd2705
diff --git a/tests/cts/hostside/app2/Android.bp b/tests/cts/hostside/app2/Android.bp
new file mode 100644
index 0000000..8e27931
--- /dev/null
+++ b/tests/cts/hostside/app2/Android.bp
@@ -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",
+}
diff --git a/tests/cts/hostside/app2/AndroidManifest.xml b/tests/cts/hostside/app2/AndroidManifest.xml
new file mode 100644
index 0000000..ad270b3
--- /dev/null
+++ b/tests/cts/hostside/app2/AndroidManifest.xml
@@ -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>
diff --git a/tests/cts/hostside/app2/res/drawable/ic_notification.png b/tests/cts/hostside/app2/res/drawable/ic_notification.png
new file mode 100644
index 0000000..6ae570b
--- /dev/null
+++ b/tests/cts/hostside/app2/res/drawable/ic_notification.png
Binary files differ
diff --git a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/Common.java b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/Common.java
new file mode 100644
index 0000000..351733e
--- /dev/null
+++ b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/Common.java
@@ -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);
+ }
+ });
+ }
+ }
+}
diff --git a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyActivity.java b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyActivity.java
new file mode 100644
index 0000000..286cc2f
--- /dev/null
+++ b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyActivity.java
@@ -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();
+ }
+}
diff --git a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyBroadcastReceiver.java b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyBroadcastReceiver.java
new file mode 100644
index 0000000..aa54075
--- /dev/null
+++ b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyBroadcastReceiver.java
@@ -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");
+ }
+}
diff --git a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyForegroundService.java b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyForegroundService.java
new file mode 100644
index 0000000..ff4ba65
--- /dev/null
+++ b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyForegroundService.java
@@ -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;
+ }
+}
diff --git a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java
new file mode 100644
index 0000000..590e17e
--- /dev/null
+++ b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyService.java
@@ -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();
+ }
+}
diff --git a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/RemoteSocketFactoryService.java b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/RemoteSocketFactoryService.java
new file mode 100644
index 0000000..b1b7d77
--- /dev/null
+++ b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/RemoteSocketFactoryService.java
@@ -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;
+ }
+}