Initial commit of the host-side tests for MediaSessionManager
Bug: 36227938
Test: MediaSessionManagerHostTest.testGetActiveSessions passes with the
fix, but fails without the fix
Change-Id: I5ac04a7b4752013ebcea1818696eb71b3f51125b
diff --git a/hostsidetests/media/Android.mk b/hostsidetests/media/Android.mk
new file mode 100644
index 0000000..36e9066
--- /dev/null
+++ b/hostsidetests/media/Android.mk
@@ -0,0 +1,36 @@
+#
+# 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_SRC_FILES := \
+ $(call all-java-files-under, common) \
+ $(call all-java-files-under, src)
+
+LOCAL_MODULE_TAGS := tests
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_MODULE := CtsMediaHostTestCases
+
+LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed compatibility-host-util
+
+include $(BUILD_CTS_HOST_JAVA_LIBRARY)
+
+include $(call first-makefiles-under,$(LOCAL_PATH))
+
diff --git a/hostsidetests/media/AndroidTest.xml b/hostsidetests/media/AndroidTest.xml
new file mode 100644
index 0000000..0a6410c
--- /dev/null
+++ b/hostsidetests/media/AndroidTest.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+<configuration description="Config for CTS media host test cases">
+ <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+ <option name="jar" value="CtsMediaHostTestCases.jar" />
+ <option name="runtime-hint" value="30m" />
+ </test>
+</configuration>
+
diff --git a/hostsidetests/media/app/MediaSessionTest/Android.mk b/hostsidetests/media/app/MediaSessionTest/Android.mk
new file mode 100644
index 0000000..eb9059d
--- /dev/null
+++ b/hostsidetests/media/app/MediaSessionTest/Android.mk
@@ -0,0 +1,36 @@
+# 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_COMPATIBILITY_SUITE := cts
+
+LOCAL_PACKAGE_NAME := CtsMediaSessionHostTestApp
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, src) \
+ $(call all-java-files-under, ../../common)
+
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test compatibility-device-util
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/media/app/MediaSessionTest/AndroidManifest.xml b/hostsidetests/media/app/MediaSessionTest/AndroidManifest.xml
new file mode 100644
index 0000000..60e82dd
--- /dev/null
+++ b/hostsidetests/media/app/MediaSessionTest/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?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="android.media.session.cts">
+
+ <uses-sdk android:minSdkVersion="26"/>
+
+ <application />
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.media.session.cts"
+ android:label="MediaSession multi-user case CTS Tests" />
+
+</manifest>
diff --git a/hostsidetests/media/app/MediaSessionTest/src/android/media/session/cts/MediaSessionManagerTest.java b/hostsidetests/media/app/MediaSessionTest/src/android/media/session/cts/MediaSessionManagerTest.java
new file mode 100644
index 0000000..2186038
--- /dev/null
+++ b/hostsidetests/media/app/MediaSessionTest/src/android/media/session/cts/MediaSessionManagerTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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 android.media.session.cts;
+
+import static android.media.cts.MediaSessionTestHelperConstants.MEDIA_SESSION_TEST_HELPER_PKG;
+
+import android.content.Context;
+import android.content.ComponentName;
+import android.test.AndroidTestCase;
+import android.media.session.MediaSessionManager;
+import android.media.session.MediaController;
+
+import java.util.List;
+
+/**
+ * Tests {@link MediaSessionManager} with the multi-user environment.
+ * <p>Don't run tests here directly. They aren't stand-alone tests and each test will be run
+ * indirectly by the host-side test CtsMediaHostTestCases after the proper device setup.
+ */
+public class MediaSessionManagerTest extends AndroidTestCase {
+ private MediaSessionManager mMediaSessionManager;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ mMediaSessionManager = (MediaSessionManager) getContext().getSystemService(
+ Context.MEDIA_SESSION_SERVICE);
+ }
+
+ /**
+ * Tests if the MediaSessionTestHelper doesn't have an active media session.
+ */
+ public void testGetActiveSessions_noMediaSessionFromMediaSessionTestHelper() throws Exception {
+ List<MediaController> controllers = mMediaSessionManager.getActiveSessions(
+ createFakeNotificationListener());
+ for (MediaController controller : controllers) {
+ if (controller.getPackageName().equals(MEDIA_SESSION_TEST_HELPER_PKG)) {
+ fail("Media session for the media session app shouldn't be available");
+ return;
+ }
+ }
+ }
+
+ /**
+ * Tests if the MediaSessionTestHelper has an active media session.
+ */
+ public void testGetActiveSessions_hasMediaSessionFromMediaSessionTestHelper() throws Exception {
+ List<MediaController> controllers = mMediaSessionManager.getActiveSessions(
+ createFakeNotificationListener());
+ for (MediaController controller : controllers) {
+ if (controller.getPackageName().equals(MEDIA_SESSION_TEST_HELPER_PKG)) {
+ // Test success
+ return;
+ }
+ }
+ fail("Media session for the media session app is expected");
+ }
+
+ /**
+ * Tests if there's no media session.
+ */
+ public void testGetActiveSessions_noMediaSession() throws Exception {
+ List<MediaController> controllers = mMediaSessionManager.getActiveSessions(
+ createFakeNotificationListener());
+ assertTrue(controllers.isEmpty());
+ }
+
+ /**
+ * Returns the ComponentName of the notification listener for this test.
+ * <p>Notification listener will be enabled by the host-side test.
+ */
+ private ComponentName createFakeNotificationListener() {
+ return new ComponentName(getContext(), MediaSessionManagerTest.class);
+ }
+}
+
diff --git a/hostsidetests/media/app/MediaSessionTestHelper/Android.mk b/hostsidetests/media/app/MediaSessionTestHelper/Android.mk
new file mode 100644
index 0000000..3944cb6
--- /dev/null
+++ b/hostsidetests/media/app/MediaSessionTestHelper/Android.mk
@@ -0,0 +1,40 @@
+# 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)
+
+# Don't include this package in any target
+LOCAL_MODULE_TAGS := tests
+# When built, explicitly put it in the data partition.
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_DEX_PREOPT := false
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, src) \
+ $(call all-java-files-under, ../../common)
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_PACKAGE_NAME := CtsMediaSessionTestHelper
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
+
diff --git a/hostsidetests/media/app/MediaSessionTestHelper/AndroidManifest.xml b/hostsidetests/media/app/MediaSessionTestHelper/AndroidManifest.xml
new file mode 100644
index 0000000..1e29eca
--- /dev/null
+++ b/hostsidetests/media/app/MediaSessionTestHelper/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?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="android.media.app.media_session_test_helper"
+ android:versionCode="1"
+ android:versionName="1.0">
+
+ <application android:label="@string/label">
+ <service android:name=".MediaSessionTestHelperService">
+ <intent-filter>
+ <action android:name="android.media.app.media_session_test_helper.ACTION_CONTROL" />
+ </intent-filter>
+ </service>
+ </application>
+</manifest>
diff --git a/hostsidetests/media/app/MediaSessionTestHelper/res/values/strings.xml b/hostsidetests/media/app/MediaSessionTestHelper/res/values/strings.xml
new file mode 100644
index 0000000..fb75fdb
--- /dev/null
+++ b/hostsidetests/media/app/MediaSessionTestHelper/res/values/strings.xml
@@ -0,0 +1,18 @@
+<?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.
+-->
+<resources>
+ <string name="label">MediaSessionTestHelper for media host-side CTS tests</string>
+</resources>
diff --git a/hostsidetests/media/app/MediaSessionTestHelper/src/android/media/app/media_session_test_helper/MediaSessionTestHelperService.java b/hostsidetests/media/app/MediaSessionTestHelper/src/android/media/app/media_session_test_helper/MediaSessionTestHelperService.java
new file mode 100644
index 0000000..a161ba5
--- /dev/null
+++ b/hostsidetests/media/app/MediaSessionTestHelper/src/android/media/app/media_session_test_helper/MediaSessionTestHelperService.java
@@ -0,0 +1,103 @@
+/*
+ * 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 android.media.app.media_session_test_helper;
+
+import android.app.Notification;
+import android.app.Service;
+import android.content.Intent;
+import android.media.session.MediaSession;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import android.media.cts.MediaSessionTestHelperConstants;
+
+/**
+ * Service of the media session's host-side CTS helper.
+ * <p>This is the foreground service to prevent this process from being killed by the OOM killer
+ * while the host-side tests are running.
+ */
+public class MediaSessionTestHelperService extends Service {
+ private static final String TAG = "MediaSessionTestHelperService";
+
+ private static final int NOTIFICATION_ID = 100;
+
+ private MediaSession mMediaSession;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ // Build notification UI to make this a foreground service.
+ Notification notification = new Notification.Builder(this)
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setContentTitle(getString(R.string.label)).build();
+ startForeground(NOTIFICATION_ID, notification);
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ super.onStartCommand(intent, flags, startId);
+ if (!TextUtils.equals(intent.getAction(), MediaSessionTestHelperConstants.ACTION_CONTROL)) {
+ Log.e(TAG, "Invalid action " + intent.getAction() + ". Test may fail");
+ return START_STICKY;
+ }
+ int flag = intent.getIntExtra(MediaSessionTestHelperConstants.EXTRA_CONTROL_COMMAND, 0);
+ if ((flag & MediaSessionTestHelperConstants.FLAG_CREATE_MEDIA_SESSION) != 0) {
+ if (mMediaSession == null) {
+ mMediaSession = new MediaSession(this, TAG);
+ }
+ }
+ if (mMediaSession != null) {
+ if ((flag & MediaSessionTestHelperConstants.FLAG_SET_MEDIA_SESSION_ACTIVE) != 0) {
+ mMediaSession.setActive(true);
+ }
+ if ((flag & MediaSessionTestHelperConstants.FLAG_SET_MEDIA_SESSION_INACTIVE) != 0) {
+ mMediaSession.setActive(false);
+ }
+ }
+ if ((flag & MediaSessionTestHelperConstants.FLAG_RELEASE_MEDIA_SESSION) != 0) {
+ releaseMediaSession();
+ }
+ return START_STICKY;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ // It's not a bind service.
+ return null;
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ releaseMediaSession();
+ }
+
+ private void releaseMediaSession() {
+ if (mMediaSession != null) {
+ mMediaSession.release();
+ mMediaSession = null;
+ }
+ }
+}
diff --git a/hostsidetests/media/common/android/media/cts/MediaSessionTestHelperConstants.java b/hostsidetests/media/common/android/media/cts/MediaSessionTestHelperConstants.java
new file mode 100644
index 0000000..8c8dc54
--- /dev/null
+++ b/hostsidetests/media/common/android/media/cts/MediaSessionTestHelperConstants.java
@@ -0,0 +1,90 @@
+/*
+ * 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 android.media.cts;
+
+/**
+ * Defines constants for controlling the media session test helper app, which can start media
+ * playback and create the media session for test.
+ * <p>Any change in these constants should also be applied to the media session test helper app.
+ * <p>Don't add Android specific imports because this will be used by the both host-side and
+ * device-side.
+ */
+public class MediaSessionTestHelperConstants {
+ /**
+ * Package name of the media session test helper.
+ */
+ public static final String MEDIA_SESSION_TEST_HELPER_PKG =
+ "android.media.app.media_session_test_helper";
+ /**
+ * Package binary file name of the media session test helper`.
+ */
+ public static final String MEDIA_SESSION_TEST_HELPER_APK = "CtsMediaSessionTestHelper.apk";
+
+ /**
+ * Intent action name to control media sesion test helper.
+ */
+ public static final String ACTION_CONTROL =
+ "android.media.app.media_session_test_helper.ACTION_CONTROL";
+ /**
+ * Intent extra key name to control media session test helper.
+ */
+ public static final String EXTRA_CONTROL_COMMAND =
+ "android.media.app.media_session_test_helper.EXTRA_CONTROL_COMMAND";
+
+ /**
+ * Intent extra value for the key {@link #EXTRA_CONTROL_COMMAND} to create the media session
+ * if it doesn't exist.
+ * @see buildControlCommand
+ */
+ public static final int FLAG_CREATE_MEDIA_SESSION = 0x01;
+ /**
+ * Intent extra value for the key {@link #EXTRA_CONTROL_COMMAND} to set the media session active
+ * if it exists.
+ * @see buildControlCommand
+ */
+ public static final int FLAG_SET_MEDIA_SESSION_ACTIVE = 0x02;
+ /**
+ * Intent extra value for the key {@link #EXTRA_CONTROL_COMMAND} to set the media session
+ * inactive if it exists.
+ * @see buildControlCommand
+ */
+ public static final int FLAG_SET_MEDIA_SESSION_INACTIVE = 0x04;
+ /**
+ * Intent extra value for the key {@link #EXTRA_CONTROL_COMMAND} to release the media session
+ * if it was created.
+ * @see buildControlCommand
+ */
+ public static final int FLAG_RELEASE_MEDIA_SESSION = 0x08;
+
+ private MediaSessionTestHelperConstants() {
+ // Prevent from the instantiation.
+ }
+
+ /**
+ * Builds the control command for the media session test helper app.
+ *
+ * @param userId user id to send the command
+ * @param flag bit masked flag among {@link #FLAG_CREATE_MEDIA_SESSION},
+ * {@link #FLAG_SET_MEDIA_SESSION_ACTIVE}, {@link #FLAG_SET_MEDIA_SESSION_INACTIVE},
+ * and {@link #FLAG_RELEASE_MEDIA_SESSION}. If multiple flags are specificed,
+ * operations will be exceuted in order.
+ **/
+ public static String buildControlCommand(int userId, int flag) {
+ return "am start-foreground-service --user " + userId + " -a " + ACTION_CONTROL + " --ei "
+ + EXTRA_CONTROL_COMMAND + " " + flag + " " + MEDIA_SESSION_TEST_HELPER_PKG;
+ }
+}
diff --git a/hostsidetests/media/src/android/media/cts/BaseMultiUserTest.java b/hostsidetests/media/src/android/media/cts/BaseMultiUserTest.java
new file mode 100644
index 0000000..ef8ba5b
--- /dev/null
+++ b/hostsidetests/media/src/android/media/cts/BaseMultiUserTest.java
@@ -0,0 +1,330 @@
+/*
+ * 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 android.media.cts;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
+import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.ddmlib.testrunner.TestResult;
+import com.android.ddmlib.testrunner.TestResult.TestStatus;
+import com.android.ddmlib.testrunner.TestRunResult;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.CollectingTestListener;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+/**
+ * Base class for host-side tests for multi-user aware media APIs.
+ */
+public class BaseMultiUserTest extends DeviceTestCase implements IBuildReceiver {
+ private static final String RUNNER = "android.support.test.runner.AndroidJUnitRunner";
+
+ /**
+ * The defined timeout (in milliseconds) is used as a maximum waiting time when expecting the
+ * command output from the device. At any time, if the shell command does not output anything
+ * for a period longer than the defined timeout the Tradefed run terminates.
+ */
+ private static final long DEFAULT_SHELL_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(5);
+
+ /**
+ * Instrumentation test runner argument key used for individual test timeout
+ **/
+ protected static final String TEST_TIMEOUT_INST_ARGS_KEY = "timeout_msec";
+
+ /**
+ * Sets timeout (in milliseconds) that will be applied to each test. In the
+ * event of a test timeout it will log the results and proceed with executing the next test.
+ */
+ private static final long DEFAULT_TEST_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(5);
+ private static final String SETTINGS_PACKAGE_VERIFIER_NAMESPACE = "global";
+ private static final String SETTINGS_PACKAGE_VERIFIER_NAME = "package_verifier_enable";
+
+ /**
+ * User ID for all users.
+ * The value is from the UserHandle class.
+ */
+ protected static final int USER_ALL = -1;
+
+ /**
+ * User ID for the system user.
+ * The value is from the UserHandle class.
+ */
+ protected static final int USER_SYSTEM = 0;
+
+ private IBuildInfo mCtsBuild;
+ private String mPackageVerifier;
+
+ private Set<String> mExistingPackages;
+ private List<Integer> mExistingUsers;
+
+ @Override
+ protected void setUp() throws Exception {
+ // Ensure that build has been set before test is run.
+ assertNotNull(mCtsBuild);
+ mExistingPackages = getDevice().getInstalledPackageNames();
+
+ // Disable the package verifier to avoid the dialog when installing an app
+ mPackageVerifier = getSettings(SETTINGS_PACKAGE_VERIFIER_NAMESPACE,
+ SETTINGS_PACKAGE_VERIFIER_NAME, USER_ALL);
+ putSettings(SETTINGS_PACKAGE_VERIFIER_NAMESPACE,
+ SETTINGS_PACKAGE_VERIFIER_NAME, "0", USER_ALL);
+
+ mExistingUsers = new ArrayList();
+ int primaryUserId = getDevice().getPrimaryUserId();
+ mExistingUsers.add(primaryUserId);
+ mExistingUsers.add(USER_SYSTEM);
+
+ executeShellCommand("am switch-user " + primaryUserId);
+ executeShellCommand("wm dismiss-keyguard");
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ // Reset the package verifier setting to its original value.
+ putSettings(SETTINGS_PACKAGE_VERIFIER_NAMESPACE, SETTINGS_PACKAGE_VERIFIER_NAME,
+ mPackageVerifier, USER_ALL);
+
+ // Remove users created during the test.
+ for (int userId : getDevice().listUsers()) {
+ if (!mExistingUsers.contains(userId)) {
+ removeUser(userId);
+ }
+ }
+ // Remove packages installed during the test.
+ for (String packageName : getDevice().getUninstallablePackageNames()) {
+ if (mExistingPackages.contains(packageName)) {
+ continue;
+ }
+ CLog.d("Removing leftover package: " + packageName);
+ getDevice().uninstallPackage(packageName);
+ }
+ super.tearDown();
+ }
+
+ @Override
+ public void setBuild(IBuildInfo buildInfo) {
+ mCtsBuild = buildInfo;
+ }
+
+ /**
+ * Installs the app as if the user of the ID {@param userId} has installed the app.
+ *
+ * @param appFileName file name of the app.
+ * @param userId user ID to install the app against.
+ */
+ protected void installAppAsUser(String appFileName, int userId)
+ throws FileNotFoundException, DeviceNotAvailableException {
+ CLog.d("Installing app " + appFileName + " for user " + userId);
+ CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
+ String result = getDevice().installPackageForUser(
+ buildHelper.getTestFile(appFileName), true, true, userId, "-t");
+ assertNull("Failed to install " + appFileName + " for user " + userId + ": " + result,
+ result);
+ }
+
+ /**
+ * Excutes shell command and returns the result.
+ *
+ * @param command command to run.
+ * @return result from the command. If the result was {@code null}, empty string ("") will be
+ * returned instead. Otherwise, trimmed result will be returned.
+ */
+ protected @Nonnull String executeShellCommand(final String command) throws Exception {
+ CLog.d("Starting command " + command);
+ String commandOutput = getDevice().executeShellCommand(command);
+ CLog.d("Output for command " + command + ": " + commandOutput);
+ return commandOutput != null ? commandOutput.trim() : "";
+ }
+
+ private int createAndStartUser(String extraParam) throws Exception {
+ String command = "pm create-user" + extraParam + " TestUser_" + System.currentTimeMillis();
+ String commandOutput = executeShellCommand(command);
+
+ String[] tokens = commandOutput.split("\\s+");
+ assertTrue(tokens.length > 0);
+ assertEquals("Success:", tokens[0]);
+ int userId = Integer.parseInt(tokens[tokens.length-1]);
+
+ // Start user for MediaSessionService to notice the created user.
+ getDevice().startUser(userId);
+ return userId;
+ }
+
+ /**
+ * Creates and starts a new user.
+ */
+ protected int createAndStartUser() throws Exception {
+ return createAndStartUser("");
+ }
+
+ /**
+ * Creates and starts a restricted profile for the {@param parentUserId}.
+ *
+ * @param parentUserId parent user id.
+ */
+ protected int createAndStartRestrictedProfile(int parentUserId) throws Exception {
+ return createAndStartUser(" --profileOf " + parentUserId + " --restricted");
+ }
+
+ /**
+ * Creates and starts a managed profile for the {@param parentUserId}.
+ *
+ * @param parentUserId parent user id.
+ */
+ protected int createAndStartManagedProfile(int parentUserId) throws Exception {
+ return createAndStartUser(" --profileOf " + parentUserId + " --managed");
+ }
+
+ /**
+ * Removes the user that is created during the test.
+ * <p>It will be no-op if the user cannot be removed or doesn't exist.
+ *
+ * @param userId user ID to remove.
+ */
+ protected void removeUser(int userId) throws Exception {
+ if (getDevice().listUsers().contains(userId) && userId != USER_SYSTEM
+ && !mExistingUsers.contains(userId)) {
+ // Don't log output, as tests sometimes set no debug user restriction, which
+ // causes this to fail, we should still continue and remove the user.
+ String stopUserCommand = "am stop-user -w -f " + userId;
+ CLog.d("Stopping and removing user " + userId);
+ getDevice().executeShellCommand(stopUserCommand);
+ assertTrue("Couldn't remove user", getDevice().removeUser(userId));
+ }
+ }
+
+ /**
+ * Runs tests on the device as if it's {@param userId}.
+ *
+ * @param pkgName test package file name that contains the {@link AndroidTestCase}
+ * @param testClassName Class name to test within the test package. Can be {@code null} if you
+ * want to run all test classes in the package.
+ * @param testMethodName Method name to test within the test class. Can be {@code null} if you
+ * want to run all test methods in the class. Will be ignored if {@param testClassName} is
+ * {@code null}.
+ * @param userId user ID to run the tests as.
+ */
+ protected void runDeviceTestsAsUser(
+ String pkgName, @Nullable String testClassName,
+ @Nullable String testMethodName, int userId) throws DeviceNotAvailableException {
+ if (testClassName != null && testClassName.startsWith(".")) {
+ testClassName = pkgName + testClassName;
+ }
+
+ RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(
+ pkgName, RUNNER, getDevice().getIDevice());
+ testRunner.setMaxTimeToOutputResponse(DEFAULT_SHELL_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+ testRunner.addInstrumentationArg(
+ TEST_TIMEOUT_INST_ARGS_KEY, Long.toString(DEFAULT_TEST_TIMEOUT_MILLIS));
+ if (testClassName != null && testMethodName != null) {
+ testRunner.setMethodName(testClassName, testMethodName);
+ } else if (testClassName != null) {
+ testRunner.setClassName(testClassName);
+ }
+
+ CollectingTestListener listener = new CollectingTestListener();
+ assertTrue(getDevice().runInstrumentationTestsAsUser(testRunner, userId, 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.getNumTests() == 0) {
+ throw new AssertionError("No tests were run on the device");
+ }
+
+ if (result.hasFailedTests()) {
+ // Build a meaningful error message
+ StringBuilder errorBuilder = new StringBuilder("On-device tests failed:\n");
+ for (Map.Entry<TestIdentifier, 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());
+ }
+ }
+
+ /**
+ * Checks whether it is possible to create the desired number of users.
+ */
+ protected boolean canCreateAdditionalUsers(int numberOfUsers)
+ throws DeviceNotAvailableException {
+ return getDevice().listUsers().size() + numberOfUsers <=
+ getDevice().getMaxNumberOfUsersSupported();
+ }
+
+ /**
+ * Gets the system setting as a string from the system settings provider for the user.
+ *
+ * @param namespace namespace of the setting.
+ * @param name name of the setting.
+ * @param userId user ID to query the setting. Can be {@link #USER_ALL}.
+ * @return value of the system setting provider with the given namespace and name.
+ * {@code null}, empty string, or "null" will be returned to the empty string ("") instead.
+ */
+ protected @Nonnull String getSettings(@Nonnull String namespace, @Nonnull String name,
+ int userId) throws Exception {
+ String userFlag = (userId == USER_ALL) ? "" : " --user " + userId;
+ String commandOutput = executeShellCommand(
+ "settings" + userFlag + " get " + namespace + " " + name);
+ if (commandOutput == null || commandOutput.isEmpty() || commandOutput.equals("null")) {
+ commandOutput = "";
+ }
+ return commandOutput;
+ }
+
+ /**
+ * Puts the string to the system settings provider for the user.
+ * <p>This deletes the setting for an empty {@param value} as 'settings put' doesn't allow
+ * putting empty value.
+ *
+ * @param namespace namespace of the setting.
+ * @param name name of the setting.
+ * @param value value of the system setting provider with the given namespace and name.
+ * @param userId user ID to set the setting. Can be {@link #USER_ALL}.
+ */
+ protected void putSettings(@Nonnull String namespace, @Nonnull String name,
+ @Nullable String value, int userId) throws Exception {
+ if (value == null || value.isEmpty()) {
+ // Delete the setting if the value is null or empty as 'settings put' doesn't accept
+ // them.
+ // Ignore userId here because 'settings delete' doesn't support it.
+ executeShellCommand("settings delete " + namespace + " " + name);
+ } else {
+ String userFlag = (userId == USER_ALL) ? "" : " --user " + userId;
+ executeShellCommand("settings" + userFlag + " put " + namespace + " " + name
+ + " " + value);
+ }
+ }
+}
diff --git a/hostsidetests/media/src/android/media/session/cts/MediaSessionManagerHostTest.java b/hostsidetests/media/src/android/media/session/cts/MediaSessionManagerHostTest.java
new file mode 100644
index 0000000..d665f5f
--- /dev/null
+++ b/hostsidetests/media/src/android/media/session/cts/MediaSessionManagerHostTest.java
@@ -0,0 +1,184 @@
+/*
+ * 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 android.media.session.cts;
+
+import static android.media.cts.MediaSessionTestHelperConstants.FLAG_CREATE_MEDIA_SESSION;
+import static android.media.cts.MediaSessionTestHelperConstants.FLAG_SET_MEDIA_SESSION_ACTIVE;
+import static android.media.cts.MediaSessionTestHelperConstants.MEDIA_SESSION_TEST_HELPER_APK;
+import static android.media.cts.MediaSessionTestHelperConstants.MEDIA_SESSION_TEST_HELPER_PKG;
+
+import android.media.cts.BaseMultiUserTest;
+import android.media.cts.MediaSessionTestHelperConstants;
+
+import android.platform.test.annotations.RequiresDevice;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil.CLog;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.StringJoiner;
+import java.util.StringTokenizer;
+
+/**
+ * Host-side test for the media session manager that installs and runs device-side tests after the
+ * proper device setup.
+ * <p>Corresponding device-side tests are written in the {@link #DEVICE_SIDE_TEST_CLASS}
+ * which is in the {@link #DEVICE_SIDE_TEST_APK}.
+ */
+public class MediaSessionManagerHostTest extends BaseMultiUserTest {
+ /**
+ * Package name of the device-side tests.
+ */
+ private static final String DEVICE_SIDE_TEST_PKG = "android.media.session.cts";
+ /**
+ * Package file name (.apk) for the device-side tests.
+ */
+ private static final String DEVICE_SIDE_TEST_APK = "CtsMediaSessionHostTestApp.apk";
+ /**
+ * Fully qualified class name for the device-side tests.
+ */
+ private static final String DEVICE_SIDE_TEST_CLASS =
+ "android.media.session.cts.MediaSessionManagerTest";
+
+ private static final String SETTINGS_NOTIFICATION_LISTENER_NAMESPACE = "secure";
+ private static final String SETTINGS_NOTIFICATION_LISTENER_NAME =
+ "enabled_notification_listeners";
+
+ // Keep the original notification listener list to clean up.
+ private Map<Integer, String> mNotificationListeners;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ mNotificationListeners = new HashMap<>();
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ // Cleanup
+ for (int userId : mNotificationListeners.keySet()) {
+ String notificationListener = mNotificationListeners.get(userId);
+ putSettings(SETTINGS_NOTIFICATION_LISTENER_NAMESPACE,
+ SETTINGS_NOTIFICATION_LISTENER_NAME, notificationListener, userId);
+ }
+ super.tearDown();
+ }
+
+ /**
+ * Tests {@link MediaSessionManager#getActiveSessions} with the multi-users environment.
+ */
+ @RequiresDevice
+ public void testGetActiveSessions() throws Exception {
+ // Ensure that the previously running media session test helper app doesn't exist.
+ getDevice().uninstallPackage(MEDIA_SESSION_TEST_HELPER_PKG);
+
+ int primaryUserId = getDevice().getPrimaryUserId();
+
+ allowGetActiveSessionForTest(primaryUserId);
+ installAppAsUser(DEVICE_SIDE_TEST_APK, primaryUserId);
+ runTest("testGetActiveSessions_noMediaSessionFromMediaSessionTestHelper");
+
+ installAppAsUser(MEDIA_SESSION_TEST_HELPER_APK, primaryUserId);
+ sendControlCommand(primaryUserId, FLAG_CREATE_MEDIA_SESSION);
+ runTest("testGetActiveSessions_noMediaSessionFromMediaSessionTestHelper");
+
+ sendControlCommand(primaryUserId, FLAG_SET_MEDIA_SESSION_ACTIVE);
+ runTest("testGetActiveSessions_hasMediaSessionFromMediaSessionTestHelper");
+
+ if (!canCreateAdditionalUsers(1)) {
+ CLog.w("Cannot create a new user. Skipping multi-user test cases.");
+ return;
+ }
+
+ // Test if another user can get the session.
+ int newUser = createAndStartUser();
+ installAppAsUser(DEVICE_SIDE_TEST_APK, newUser);
+ allowGetActiveSessionForTest(newUser);
+ runTestAsUser("testGetActiveSessions_noMediaSession", newUser);
+ removeUser(newUser);
+
+ // Test if another managed profile can get the session.
+ // Remove the created user first not to exceed system's user number limit.
+ newUser = createAndStartManagedProfile(primaryUserId);
+ installAppAsUser(DEVICE_SIDE_TEST_APK, newUser);
+ allowGetActiveSessionForTest(newUser);
+ runTestAsUser("testGetActiveSessions_noMediaSession", newUser);
+ removeUser(newUser);
+
+ // Test if another restricted profile can get the session.
+ // Remove the created user first not to exceed system's user number limit.
+ newUser = createAndStartRestrictedProfile(primaryUserId);
+ installAppAsUser(DEVICE_SIDE_TEST_APK, newUser);
+ allowGetActiveSessionForTest(newUser);
+ runTestAsUser("testGetActiveSessions_noMediaSession", newUser);
+ }
+
+ private void runTest(String testMethodName) throws DeviceNotAvailableException {
+ runTestAsUser(testMethodName, getDevice().getPrimaryUserId());
+ }
+
+ private void runTestAsUser(String testMethodName, int userId)
+ throws DeviceNotAvailableException {
+ runDeviceTestsAsUser(DEVICE_SIDE_TEST_PKG, DEVICE_SIDE_TEST_CLASS,
+ testMethodName, userId);
+ }
+
+ /**
+ * Allows the {@link #DEVICE_SIDE_TEST_CLASS} to call
+ * {@link MediaSessionManager#getActiveSessions} for testing.
+ * <p>{@link MediaSessionManager#getActiveSessions} bypasses the permission check if the
+ * caller is the enabled notification listener. This method uses the behavior by making
+ * {@link #DEVICE_SIDE_TEST_CLASS} as the notification listener. So any change in this
+ * should be also applied to the class.
+ * <p>Note that the device-side test {@link android.media.cts.MediaSessionManagerTest} already
+ * covers the test for failing {@link MediaSessionManager#getActiveSessions} without the
+ * permission nor the notification listener.
+ */
+ private void allowGetActiveSessionForTest(int userId) throws Exception {
+ final String NOTIFICATION_LISTENER_DELIM = ":";
+ if (mNotificationListeners.get(userId) != null) {
+ // Already enabled.
+ return;
+ }
+ String list = getSettings(SETTINGS_NOTIFICATION_LISTENER_NAMESPACE,
+ SETTINGS_NOTIFICATION_LISTENER_NAME, userId);
+
+ String notificationListener = DEVICE_SIDE_TEST_PKG + "/" + DEVICE_SIDE_TEST_CLASS;
+ // Ensure that the list doesn't contain notificationListener already.
+ // This can happen if the test is killed while running.
+ StringTokenizer tokenizer = new StringTokenizer(list, NOTIFICATION_LISTENER_DELIM);
+ StringJoiner joiner = new StringJoiner(NOTIFICATION_LISTENER_DELIM);
+ while (tokenizer.hasMoreTokens()) {
+ String token = tokenizer.nextToken();
+ if (!token.isEmpty() && !token.equals(notificationListener)) {
+ joiner.add(token);
+ }
+ }
+ list = joiner.toString();
+ // Preserve the original list.
+ mNotificationListeners.put(userId, list);
+ // Allow get active sessions by setting notification listener.
+ joiner.add(notificationListener);
+ putSettings(SETTINGS_NOTIFICATION_LISTENER_NAMESPACE,
+ SETTINGS_NOTIFICATION_LISTENER_NAME, joiner.toString(), userId);
+ }
+
+ private void sendControlCommand(int userId, int flag) throws Exception {
+ executeShellCommand(MediaSessionTestHelperConstants.buildControlCommand(userId, flag));
+ }
+}