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));
+    }
+}