| /* |
| * Copyright (C) 2014 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 android.media.AudioManager; |
| import android.platform.test.annotations.AppModeFull; |
| import com.android.compatibility.common.util.ApiLevelUtil; |
| import com.android.compatibility.common.util.MediaUtils; |
| import com.android.compatibility.common.util.SystemUtil; |
| |
| import android.content.ComponentName; |
| import android.Manifest; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.pm.PackageManager; |
| import android.content.res.Resources; |
| import android.media.MediaSession2; |
| import android.media.Session2CommandGroup; |
| import android.media.Session2Token; |
| import android.media.session.MediaController; |
| import android.media.session.MediaSession; |
| import android.media.session.MediaSessionManager; |
| import android.media.session.PlaybackState; |
| import android.os.Build; |
| import android.os.Handler; |
| import android.os.HandlerThread; |
| import android.os.Looper; |
| import android.os.Process; |
| import android.platform.test.annotations.AppModeFull; |
| import android.test.InstrumentationTestCase; |
| import android.test.UiThreadTest; |
| import android.util.Log; |
| import android.view.KeyEvent; |
| |
| import com.android.compatibility.common.util.SystemUtil; |
| |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.Executor; |
| import java.util.concurrent.Executors; |
| import java.util.concurrent.TimeUnit; |
| |
| @AppModeFull(reason = "TODO: evaluate and port to instant") |
| public class MediaSessionManagerTest extends InstrumentationTestCase { |
| private static final String TAG = "MediaSessionManagerTest"; |
| private static final int TIMEOUT_MS = 3000; |
| private static final int WAIT_MS = 500; |
| |
| private AudioManager mAudioManager; |
| private MediaSessionManager mSessionManager; |
| |
| private static boolean sIsAtLeastS = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S); |
| |
| @Override |
| protected void setUp() throws Exception { |
| super.setUp(); |
| mAudioManager = (AudioManager) getInstrumentation().getTargetContext() |
| .getSystemService(Context.AUDIO_SERVICE); |
| mSessionManager = (MediaSessionManager) getInstrumentation().getTargetContext() |
| .getSystemService(Context.MEDIA_SESSION_SERVICE); |
| } |
| |
| @Override |
| protected void tearDown() throws Exception { |
| getInstrumentation().getUiAutomation().dropShellPermissionIdentity(); |
| super.tearDown(); |
| } |
| |
| public void testGetActiveSessions() throws Exception { |
| try { |
| List<MediaController> controllers = mSessionManager.getActiveSessions(null); |
| fail("Expected security exception for unauthorized call to getActiveSessions"); |
| } catch (SecurityException e) { |
| // Expected |
| } |
| // TODO enable a notification listener, test again, disable, test again |
| } |
| |
| public void testGetMediaKeyEventSession_throwsSecurityException() throws Exception { |
| if (!MediaUtils.check(sIsAtLeastS, "test invalid before Android 12")) return; |
| try { |
| mSessionManager.getMediaKeyEventSession(); |
| fail("Expected security exception for call to getMediaKeyEventSession"); |
| } catch (SecurityException ex) { |
| // Expected |
| } |
| } |
| |
| public void testOnMediaKeyEventSessionChangedListener() throws Exception { |
| // The permission can be held only on S+ |
| if (!MediaUtils.check(sIsAtLeastS, "test invalid before Android 12")) return; |
| |
| getInstrumentation().getUiAutomation().adoptShellPermissionIdentity( |
| Manifest.permission.MEDIA_CONTENT_CONTROL, |
| Manifest.permission.MANAGE_EXTERNAL_STORAGE); |
| |
| MediaKeyEventSessionListener keyEventSessionListener = new MediaKeyEventSessionListener(); |
| mSessionManager.addOnMediaKeyEventSessionChangedListener( |
| Executors.newSingleThreadExecutor(), keyEventSessionListener); |
| |
| MediaSession session = new MediaSession(getInstrumentation().getTargetContext(), TAG); |
| session.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS |
| | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS); |
| PlaybackState state = new PlaybackState.Builder() |
| .setState(PlaybackState.STATE_PLAYING, 0, 1.0f).build(); |
| // Fake the media session service so this session can take the media key events. |
| session.setPlaybackState(state); |
| session.setActive(true); |
| Utils.assertMediaPlaybackStarted(getInstrumentation().getTargetContext()); |
| |
| assertTrue(keyEventSessionListener.mCountDownLatch |
| .await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); |
| |
| assertEquals(session.getSessionToken(), mSessionManager.getMediaKeyEventSession()); |
| |
| mSessionManager.removeOnMediaKeyEventSessionChangedListener(keyEventSessionListener); |
| keyEventSessionListener.resetCountDownLatch(); |
| |
| session.release(); |
| // This shouldn't be called because the callback is removed |
| assertFalse(keyEventSessionListener.mCountDownLatch.await(WAIT_MS, TimeUnit.MILLISECONDS)); |
| } |
| |
| public void testOnMediaKeyEventDispatchedListener() throws Exception { |
| // The permission can be held only on S+ |
| if (!MediaUtils.check(sIsAtLeastS, "test invalid before Android 12")) return; |
| |
| getInstrumentation().getUiAutomation().adoptShellPermissionIdentity( |
| Manifest.permission.MEDIA_CONTENT_CONTROL, |
| Manifest.permission.MANAGE_EXTERNAL_STORAGE); |
| |
| MediaKeyEventDispatchedListener keyEventDispatchedListener = |
| new MediaKeyEventDispatchedListener(); |
| mSessionManager.addOnMediaKeyEventDispatchedListener(Executors.newSingleThreadExecutor(), |
| keyEventDispatchedListener); |
| |
| MediaSession session = new MediaSession(getInstrumentation().getTargetContext(), TAG); |
| session.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS |
| | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS); |
| PlaybackState state = new PlaybackState.Builder() |
| .setState(PlaybackState.STATE_PLAYING, 0, 1.0f).build(); |
| // Fake the media session service so this session can take the media key events. |
| session.setPlaybackState(state); |
| session.setActive(true); |
| Utils.assertMediaPlaybackStarted(getInstrumentation().getTargetContext()); |
| |
| final int keyCode = KeyEvent.KEYCODE_MEDIA_PLAY; |
| simulateMediaKeyInput(keyCode); |
| assertTrue(keyEventDispatchedListener.mCountDownLatch |
| .await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); |
| |
| assertEquals(keyCode, keyEventDispatchedListener.mKeyEvent.getKeyCode()); |
| assertEquals(getInstrumentation().getTargetContext().getPackageName(), |
| keyEventDispatchedListener.mPackageName); |
| assertEquals(session.getSessionToken(), keyEventDispatchedListener.mSessionToken); |
| |
| mSessionManager.removeOnMediaKeyEventDispatchedListener(keyEventDispatchedListener); |
| keyEventDispatchedListener.resetCountDownLatch(); |
| |
| simulateMediaKeyInput(keyCode); |
| // This shouldn't be called because the callback is removed |
| assertFalse(keyEventDispatchedListener.mCountDownLatch |
| .await(WAIT_MS, TimeUnit.MILLISECONDS)); |
| |
| session.release(); |
| } |
| |
| @UiThreadTest |
| public void testAddOnActiveSessionsListener() throws Exception { |
| if (!MediaUtils.check(sIsAtLeastS, "test invalid before Android 12")) return; |
| try { |
| mSessionManager.addOnActiveSessionsChangedListener(null, null); |
| fail("Expected NPE for call to addOnActiveSessionsChangedListener"); |
| } catch (NullPointerException e) { |
| // Expected |
| } |
| |
| MediaSessionManager.OnActiveSessionsChangedListener listener |
| = new MediaSessionManager.OnActiveSessionsChangedListener() { |
| @Override |
| public void onActiveSessionsChanged(List<MediaController> controllers) { |
| |
| } |
| }; |
| try { |
| mSessionManager.addOnActiveSessionsChangedListener(listener, null); |
| fail("Expected security exception for call to addOnActiveSessionsChangedListener"); |
| } catch (SecurityException e) { |
| // Expected |
| } |
| } |
| |
| private void assertKeyEventEquals(KeyEvent lhs, int keyCode, int action, int repeatCount) { |
| assertTrue(lhs.getKeyCode() == keyCode |
| && lhs.getAction() == action |
| && lhs.getRepeatCount() == repeatCount); |
| } |
| |
| private void injectInputEvent(int keyCode, boolean longPress) throws IOException { |
| // Injecting key with instrumentation requires a window/view, but we don't have it. |
| // Inject key event through the adb commend to workaround. |
| final String command = "input keyevent " + (longPress ? "--longpress " : "") + keyCode; |
| SystemUtil.runShellCommand(getInstrumentation(), command); |
| } |
| |
| public void testSetOnVolumeKeyLongPressListener() throws Exception { |
| Context context = getInstrumentation().getTargetContext(); |
| if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK) |
| || context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH) |
| || context.getResources().getBoolean(Resources.getSystem().getIdentifier( |
| "config_handleVolumeKeysInWindowManager", "bool", "android"))) { |
| // Skip this test, because the PhoneWindowManager dispatches volume key |
| // events directly to the audio service to change the system volume. |
| return; |
| } |
| Handler handler = createHandler(); |
| |
| // Ensure that the listener is called for long-press. |
| VolumeKeyLongPressListener listener = new VolumeKeyLongPressListener(3, handler); |
| mSessionManager.setOnVolumeKeyLongPressListener(listener, handler); |
| injectInputEvent(KeyEvent.KEYCODE_VOLUME_DOWN, true); |
| assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); |
| assertEquals(listener.mKeyEvents.size(), 3); |
| assertKeyEventEquals(listener.mKeyEvents.get(0), |
| KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.ACTION_DOWN, 0); |
| assertKeyEventEquals(listener.mKeyEvents.get(1), |
| KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.ACTION_DOWN, 1); |
| assertKeyEventEquals(listener.mKeyEvents.get(2), |
| KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.ACTION_UP, 0); |
| |
| // Ensure the the listener isn't called for short-press. |
| listener = new VolumeKeyLongPressListener(1, handler); |
| mSessionManager.setOnVolumeKeyLongPressListener(listener, handler); |
| injectInputEvent(KeyEvent.KEYCODE_VOLUME_DOWN, false); |
| assertFalse(listener.mCountDownLatch.await(WAIT_MS, TimeUnit.MILLISECONDS)); |
| assertEquals(listener.mKeyEvents.size(), 0); |
| |
| // Ensure that the listener isn't called anymore. |
| mSessionManager.setOnVolumeKeyLongPressListener(null, handler); |
| injectInputEvent(KeyEvent.KEYCODE_VOLUME_DOWN, true); |
| assertFalse(listener.mCountDownLatch.await(WAIT_MS, TimeUnit.MILLISECONDS)); |
| assertEquals(listener.mKeyEvents.size(), 0); |
| |
| removeHandler(handler); |
| } |
| |
| public void testSetOnMediaKeyListener() throws Exception { |
| Handler handler = createHandler(); |
| MediaSession session = null; |
| try { |
| session = new MediaSession(getInstrumentation().getTargetContext(), TAG); |
| MediaSessionCallback callback = new MediaSessionCallback(2, session); |
| session.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS |
| | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS); |
| session.setCallback(callback, handler); |
| PlaybackState state = new PlaybackState.Builder() |
| .setState(PlaybackState.STATE_PLAYING, 0, 1.0f).build(); |
| // Fake the media session service so this session can take the media key events. |
| session.setPlaybackState(state); |
| session.setActive(true); |
| |
| // A media playback is also needed to receive media key events. |
| Utils.assertMediaPlaybackStarted(getInstrumentation().getTargetContext()); |
| |
| // Ensure that the listener is called for media key event, |
| // and any other media sessions don't get the key. |
| MediaKeyListener listener = new MediaKeyListener(2, true, handler); |
| mSessionManager.setOnMediaKeyListener(listener, handler); |
| injectInputEvent(KeyEvent.KEYCODE_HEADSETHOOK, false); |
| assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); |
| assertEquals(listener.mKeyEvents.size(), 2); |
| assertKeyEventEquals(listener.mKeyEvents.get(0), |
| KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_DOWN, 0); |
| assertKeyEventEquals(listener.mKeyEvents.get(1), |
| KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_UP, 0); |
| assertFalse(callback.mCountDownLatch.await(WAIT_MS, TimeUnit.MILLISECONDS)); |
| assertEquals(callback.mKeyEvents.size(), 0); |
| |
| // Ensure that the listener is called for media key event, |
| // and another media session gets the key. |
| listener = new MediaKeyListener(2, false, handler); |
| mSessionManager.setOnMediaKeyListener(listener, handler); |
| injectInputEvent(KeyEvent.KEYCODE_HEADSETHOOK, false); |
| assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); |
| assertEquals(listener.mKeyEvents.size(), 2); |
| assertKeyEventEquals(listener.mKeyEvents.get(0), |
| KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_DOWN, 0); |
| assertKeyEventEquals(listener.mKeyEvents.get(1), |
| KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_UP, 0); |
| assertTrue(callback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); |
| assertEquals(callback.mKeyEvents.size(), 2); |
| assertKeyEventEquals(callback.mKeyEvents.get(0), |
| KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_DOWN, 0); |
| assertKeyEventEquals(callback.mKeyEvents.get(1), |
| KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_UP, 0); |
| |
| // Ensure that the listener isn't called anymore. |
| listener = new MediaKeyListener(1, true, handler); |
| mSessionManager.setOnMediaKeyListener(listener, handler); |
| mSessionManager.setOnMediaKeyListener(null, handler); |
| injectInputEvent(KeyEvent.KEYCODE_HEADSETHOOK, false); |
| assertFalse(listener.mCountDownLatch.await(WAIT_MS, TimeUnit.MILLISECONDS)); |
| assertEquals(listener.mKeyEvents.size(), 0); |
| } finally { |
| if (session != null) { |
| session.release(); |
| } |
| removeHandler(handler); |
| } |
| } |
| |
| public void testRemoteUserInfo() throws Exception { |
| final Context context = getInstrumentation().getTargetContext(); |
| Handler handler = createHandler(); |
| |
| MediaSession session = null; |
| try { |
| session = new MediaSession(context , TAG); |
| MediaSessionCallback callback = new MediaSessionCallback(5, session); |
| session.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS |
| | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS); |
| session.setCallback(callback, handler); |
| PlaybackState state = new PlaybackState.Builder() |
| .setState(PlaybackState.STATE_PLAYING, 0, 1.0f).build(); |
| // Fake the media session service so this session can take the media key events. |
| session.setPlaybackState(state); |
| session.setActive(true); |
| |
| // A media playback is also needed to receive media key events. |
| Utils.assertMediaPlaybackStarted(context); |
| |
| // Dispatch key events 5 times. |
| KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY); |
| // (1), (2): dispatch through key -- this will trigger event twice for up & down. |
| injectInputEvent(KeyEvent.KEYCODE_HEADSETHOOK, false); |
| // (3): dispatch through controller |
| session.getController().dispatchMediaButtonEvent(event); |
| |
| // Creating another controller. |
| MediaController controller = new MediaController(context, session.getSessionToken()); |
| // (4): dispatch through different controller. |
| controller.dispatchMediaButtonEvent(event); |
| // (5): dispatch through the same controller |
| controller.dispatchMediaButtonEvent(event); |
| |
| // Wait. |
| assertTrue(callback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); |
| |
| // Caller of (1) ~ (4) shouldn't be the same as any others. |
| for (int i = 0; i < 4; i ++) { |
| for (int j = 0; j < i; j++) { |
| assertNotSame(callback.mCallers.get(i), callback.mCallers.get(j)); |
| } |
| } |
| // Caller of (5) should be the same as (4), since they're called from the same |
| assertEquals(callback.mCallers.get(3), callback.mCallers.get(4)); |
| } finally { |
| if (session != null) { |
| session.release(); |
| } |
| removeHandler(handler); |
| } |
| } |
| |
| public void testGetSession2Tokens() throws Exception { |
| final Context context = getInstrumentation().getTargetContext(); |
| Handler handler = createHandler(); |
| Executor handlerExecutor = new HandlerExecutor(handler); |
| |
| Session2TokenListener listener = new Session2TokenListener(); |
| mSessionManager.addOnSession2TokensChangedListener(listener, handler); |
| |
| Session2Callback sessionCallback = new Session2Callback(); |
| try (MediaSession2 session = new MediaSession2.Builder(context) |
| .setSessionCallback(handlerExecutor, sessionCallback) |
| .build()) { |
| assertTrue(sessionCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); |
| assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); |
| |
| Session2Token currentToken = session.getToken(); |
| assertTrue(listContainsToken(listener.mTokens, currentToken)); |
| assertTrue(listContainsToken(mSessionManager.getSession2Tokens(), currentToken)); |
| } |
| } |
| |
| public void testGetSession2TokensWithTwoSessions() throws Exception { |
| final Context context = getInstrumentation().getTargetContext(); |
| Handler handler = createHandler(); |
| Executor handlerExecutor = new HandlerExecutor(handler); |
| |
| Session2TokenListener listener = new Session2TokenListener(); |
| mSessionManager.addOnSession2TokensChangedListener(listener, handler); |
| |
| try (MediaSession2 session1 = new MediaSession2.Builder(context) |
| .setSessionCallback(handlerExecutor, new Session2Callback()) |
| .setId("testGetSession2TokensWithTwoSessions_session1") |
| .build()) { |
| |
| assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); |
| Session2Token session1Token = session1.getToken(); |
| assertTrue(listContainsToken(mSessionManager.getSession2Tokens(), session1Token)); |
| |
| // Create another session and check the result of getSession2Token(). |
| listener.resetCountDownLatch(); |
| Session2Token session2Token = null; |
| try (MediaSession2 session2 = new MediaSession2.Builder(context) |
| .setSessionCallback(handlerExecutor, new Session2Callback()) |
| .setId("testGetSession2TokensWithTwoSessions_session2") |
| .build()) { |
| |
| assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); |
| session2Token = session2.getToken(); |
| assertNotNull(session2Token); |
| assertTrue(listContainsToken(mSessionManager.getSession2Tokens(), session1Token)); |
| assertTrue(listContainsToken(mSessionManager.getSession2Tokens(), session2Token)); |
| |
| listener.resetCountDownLatch(); |
| } |
| |
| // Since the session2 is closed, getSession2Tokens() shouldn't include session2's token. |
| assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); |
| assertTrue(listContainsToken(mSessionManager.getSession2Tokens(), session1Token)); |
| assertFalse(listContainsToken(mSessionManager.getSession2Tokens(), session2Token)); |
| } |
| } |
| |
| public void testAddAndRemoveSession2TokensListener() throws Exception { |
| final Context context = getInstrumentation().getTargetContext(); |
| Handler handler = createHandler(); |
| Executor handlerExecutor = new HandlerExecutor(handler); |
| |
| Session2TokenListener listener1 = new Session2TokenListener(); |
| mSessionManager.addOnSession2TokensChangedListener(listener1, handler); |
| |
| Session2Callback sessionCallback = new Session2Callback(); |
| try (MediaSession2 session = new MediaSession2.Builder(context) |
| .setSessionCallback(handlerExecutor, sessionCallback) |
| .build()) { |
| assertTrue(listener1.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); |
| Session2Token currentToken = session.getToken(); |
| assertTrue(listContainsToken(listener1.mTokens, currentToken)); |
| |
| // Test removing listener |
| listener1.resetCountDownLatch(); |
| Session2TokenListener listener2 = new Session2TokenListener(); |
| mSessionManager.addOnSession2TokensChangedListener(listener2, handler); |
| mSessionManager.removeOnSession2TokensChangedListener(listener1); |
| |
| session.close(); |
| assertFalse(listener1.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); |
| assertTrue(listener2.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); |
| } |
| } |
| |
| public void testSession2TokensNotChangedBySession1() throws Exception { |
| final Context context = getInstrumentation().getTargetContext(); |
| Handler handler = createHandler(); |
| |
| Session2TokenListener listener = new Session2TokenListener(); |
| List<Session2Token> initialSession2Tokens = mSessionManager.getSession2Tokens(); |
| mSessionManager.addOnSession2TokensChangedListener(listener, handler); |
| MediaSession session = null; |
| try { |
| session = new MediaSession(context, TAG); |
| session.setActive(true); |
| session.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS |
| | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS); |
| assertFalse(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); |
| List<Session2Token> laterSession2Tokens = mSessionManager.getSession2Tokens(); |
| |
| assertEquals(initialSession2Tokens.size(), laterSession2Tokens.size()); |
| } finally { |
| if (session != null) { |
| session.release(); |
| } |
| } |
| } |
| |
| public void testCustomClassConfigValuesAreValid() throws Exception { |
| if (!MediaUtils.check(sIsAtLeastS, "test invalid before Android 12")) return; |
| final Context context = getInstrumentation().getTargetContext(); |
| String customMediaKeyDispatcher = context.getString( |
| android.R.string.config_customMediaKeyDispatcher); |
| String customMediaSessionPolicyProvider = context.getString( |
| android.R.string.config_customMediaSessionPolicyProvider); |
| // MediaSessionService will call Class.forName(String) with the existing config value. |
| // If the config value is not valid (i.e. given class doesn't exist), the following |
| // methods will return false. |
| if (!customMediaKeyDispatcher.isEmpty()) { |
| assertTrue(mSessionManager.hasCustomMediaKeyDispatcher(customMediaKeyDispatcher)); |
| } |
| if (!customMediaSessionPolicyProvider.isEmpty()) { |
| assertTrue(mSessionManager.hasCustomMediaSessionPolicyProvider( |
| customMediaSessionPolicyProvider)); |
| } |
| } |
| |
| private boolean listContainsToken(List<Session2Token> tokens, Session2Token token) { |
| for (int i = 0; i < tokens.size(); i++) { |
| if (tokens.get(i).equals(token)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private Handler createHandler() { |
| HandlerThread handlerThread = new HandlerThread("MediaSessionManagerTest"); |
| handlerThread.start(); |
| return new Handler(handlerThread.getLooper()); |
| } |
| |
| private void removeHandler(Handler handler) { |
| if (handler == null) { |
| return; |
| } |
| handler.getLooper().quitSafely(); |
| } |
| |
| // This uses public APIs to dispatch key events, so sessions would consider this as |
| // 'media key event from this application'. |
| private void simulateMediaKeyInput(int keyCode) { |
| long downTime = System.currentTimeMillis(); |
| mAudioManager.dispatchMediaKeyEvent( |
| new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN, keyCode, 0)); |
| mAudioManager.dispatchMediaKeyEvent( |
| new KeyEvent(downTime, System.currentTimeMillis(), KeyEvent.ACTION_UP, keyCode, 0)); |
| } |
| |
| private class VolumeKeyLongPressListener |
| implements MediaSessionManager.OnVolumeKeyLongPressListener { |
| private final List<KeyEvent> mKeyEvents = new ArrayList<>(); |
| private final CountDownLatch mCountDownLatch; |
| private final Handler mHandler; |
| |
| public VolumeKeyLongPressListener(int count, Handler handler) { |
| mCountDownLatch = new CountDownLatch(count); |
| mHandler = handler; |
| } |
| |
| @Override |
| public void onVolumeKeyLongPress(KeyEvent event) { |
| mKeyEvents.add(event); |
| // Ensure the listener is called on the thread. |
| assertEquals(mHandler.getLooper(), Looper.myLooper()); |
| mCountDownLatch.countDown(); |
| } |
| } |
| |
| private class MediaKeyListener implements MediaSessionManager.OnMediaKeyListener { |
| private final CountDownLatch mCountDownLatch; |
| private final boolean mConsume; |
| private final Handler mHandler; |
| private final List<KeyEvent> mKeyEvents = new ArrayList<>(); |
| |
| public MediaKeyListener(int count, boolean consume, Handler handler) { |
| mCountDownLatch = new CountDownLatch(count); |
| mConsume = consume; |
| mHandler = handler; |
| } |
| |
| @Override |
| public boolean onMediaKey(KeyEvent event) { |
| mKeyEvents.add(event); |
| // Ensure the listener is called on the thread. |
| assertEquals(mHandler.getLooper(), Looper.myLooper()); |
| mCountDownLatch.countDown(); |
| return mConsume; |
| } |
| } |
| |
| private class MediaSessionCallback extends MediaSession.Callback { |
| private final CountDownLatch mCountDownLatch; |
| private final MediaSession mSession; |
| private final List<KeyEvent> mKeyEvents = new ArrayList<>(); |
| private final List<MediaSessionManager.RemoteUserInfo> mCallers = new ArrayList<>(); |
| |
| private MediaSessionCallback(int count, MediaSession session) { |
| mCountDownLatch = new CountDownLatch(count); |
| mSession = session; |
| } |
| |
| public boolean onMediaButtonEvent(Intent mediaButtonIntent) { |
| KeyEvent event = (KeyEvent) mediaButtonIntent.getParcelableExtra( |
| Intent.EXTRA_KEY_EVENT); |
| assertNotNull(event); |
| mKeyEvents.add(event); |
| mCallers.add(mSession.getCurrentControllerInfo()); |
| mCountDownLatch.countDown(); |
| return true; |
| } |
| } |
| |
| private class Session2Callback extends MediaSession2.SessionCallback { |
| private CountDownLatch mCountDownLatch; |
| |
| private Session2Callback() { |
| mCountDownLatch = new CountDownLatch(1); |
| } |
| |
| @Override |
| public Session2CommandGroup onConnect(MediaSession2 session, |
| MediaSession2.ControllerInfo controller) { |
| if (controller.getUid() == Process.SYSTEM_UID) { |
| // System server will try to connect here for monitor session. |
| mCountDownLatch.countDown(); |
| } |
| return new Session2CommandGroup.Builder().build(); |
| } |
| } |
| |
| private class Session2TokenListener implements |
| MediaSessionManager.OnSession2TokensChangedListener { |
| private CountDownLatch mCountDownLatch; |
| private List<Session2Token> mTokens; |
| |
| private Session2TokenListener() { |
| mCountDownLatch = new CountDownLatch(1); |
| } |
| |
| @Override |
| public void onSession2TokensChanged(List<Session2Token> tokens) { |
| mTokens = tokens; |
| mCountDownLatch.countDown(); |
| } |
| |
| public void resetCountDownLatch() { |
| mCountDownLatch = new CountDownLatch(1); |
| } |
| } |
| |
| private class MediaKeyEventSessionListener |
| implements MediaSessionManager.OnMediaKeyEventSessionChangedListener { |
| CountDownLatch mCountDownLatch; |
| MediaSession.Token mSessionToken; |
| |
| MediaKeyEventSessionListener() { |
| mCountDownLatch = new CountDownLatch(1); |
| } |
| |
| void resetCountDownLatch() { |
| mCountDownLatch = new CountDownLatch(1); |
| } |
| |
| @Override |
| public void onMediaKeyEventSessionChanged(String packageName, |
| MediaSession.Token sessionToken) { |
| mSessionToken = sessionToken; |
| mCountDownLatch.countDown(); |
| } |
| } |
| |
| private class MediaKeyEventDispatchedListener |
| implements MediaSessionManager.OnMediaKeyEventDispatchedListener { |
| CountDownLatch mCountDownLatch; |
| KeyEvent mKeyEvent; |
| String mPackageName; |
| MediaSession.Token mSessionToken; |
| |
| MediaKeyEventDispatchedListener() { |
| mCountDownLatch = new CountDownLatch(1); |
| } |
| |
| void resetCountDownLatch() { |
| mCountDownLatch = new CountDownLatch(1); |
| } |
| |
| @Override |
| public void onMediaKeyEventDispatched(KeyEvent event, String packageName, |
| MediaSession.Token sessionToken) { |
| mKeyEvent = event; |
| mPackageName = packageName; |
| mSessionToken = sessionToken; |
| |
| mCountDownLatch.countDown(); |
| } |
| } |
| |
| private static class HandlerExecutor implements Executor { |
| private final Handler mHandler; |
| |
| HandlerExecutor(Handler handler) { |
| mHandler = handler; |
| } |
| |
| @Override |
| public void execute(Runnable command) { |
| mHandler.post(command); |
| } |
| } |
| } |