• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package android.media.cts;
17 
18 import android.platform.test.annotations.AppModeFull;
19 import com.android.compatibility.common.util.SystemUtil;
20 
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.pm.PackageManager;
24 import android.content.res.Resources;
25 import android.media.MediaSession2;
26 import android.media.Session2CommandGroup;
27 import android.media.Session2Token;
28 import android.media.session.MediaController;
29 import android.media.session.MediaSession;
30 import android.media.session.MediaSessionManager;
31 import android.media.session.PlaybackState;
32 import android.os.Handler;
33 import android.os.HandlerThread;
34 import android.os.Looper;
35 import android.os.Process;
36 import android.test.InstrumentationTestCase;
37 import android.test.UiThreadTest;
38 import android.view.KeyEvent;
39 
40 import java.io.IOException;
41 import java.util.ArrayList;
42 import java.util.List;
43 import java.util.concurrent.CountDownLatch;
44 import java.util.concurrent.Executor;
45 import java.util.concurrent.TimeUnit;
46 
47 @AppModeFull(reason = "TODO: evaluate and port to instant")
48 public class MediaSessionManagerTest extends InstrumentationTestCase {
49     private static final String TAG = "MediaSessionManagerTest";
50     private static final int TIMEOUT_MS = 3000;
51     private static final int WAIT_MS = 500;
52 
53     private MediaSessionManager mSessionManager;
54 
55     @Override
setUp()56     protected void setUp() throws Exception {
57         super.setUp();
58         mSessionManager = (MediaSessionManager) getInstrumentation().getTargetContext()
59                 .getSystemService(Context.MEDIA_SESSION_SERVICE);
60     }
61 
62     @Override
tearDown()63     protected void tearDown() throws Exception {
64         super.tearDown();
65     }
66 
testGetActiveSessions()67     public void testGetActiveSessions() throws Exception {
68         try {
69             List<MediaController> controllers = mSessionManager.getActiveSessions(null);
70             fail("Expected security exception for unauthorized call to getActiveSessions");
71         } catch (SecurityException e) {
72             // Expected
73         }
74         // TODO enable a notification listener, test again, disable, test again
75     }
76 
77     @UiThreadTest
testAddOnActiveSessionsListener()78     public void testAddOnActiveSessionsListener() throws Exception {
79         try {
80             mSessionManager.addOnActiveSessionsChangedListener(null, null);
81             fail("Expected IAE for call to addOnActiveSessionsChangedListener");
82         } catch (IllegalArgumentException e) {
83             // Expected
84         }
85 
86         MediaSessionManager.OnActiveSessionsChangedListener listener
87                 = new MediaSessionManager.OnActiveSessionsChangedListener() {
88             @Override
89             public void onActiveSessionsChanged(List<MediaController> controllers) {
90 
91             }
92         };
93         try {
94             mSessionManager.addOnActiveSessionsChangedListener(listener, null);
95             fail("Expected security exception for call to addOnActiveSessionsChangedListener");
96         } catch (SecurityException e) {
97             // Expected
98         }
99 
100         // TODO enable a notification listener, test again, disable, verify
101         // updates stopped
102     }
103 
assertKeyEventEquals(KeyEvent lhs, int keyCode, int action, int repeatCount)104     private void assertKeyEventEquals(KeyEvent lhs, int keyCode, int action, int repeatCount) {
105         assertTrue(lhs.getKeyCode() == keyCode
106                 && lhs.getAction() == action
107                 && lhs.getRepeatCount() == repeatCount);
108     }
109 
injectInputEvent(int keyCode, boolean longPress)110     private void injectInputEvent(int keyCode, boolean longPress) throws IOException {
111         // Injecting key with instrumentation requires a window/view, but we don't have it.
112         // Inject key event through the adb commend to workaround.
113         final String command = "input keyevent " + (longPress ? "--longpress " : "") + keyCode;
114         SystemUtil.runShellCommand(getInstrumentation(), command);
115     }
116 
testSetOnVolumeKeyLongPressListener()117     public void testSetOnVolumeKeyLongPressListener() throws Exception {
118         Context context = getInstrumentation().getTargetContext();
119         if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)
120                 || context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)
121                 || context.getResources().getBoolean(Resources.getSystem().getIdentifier(
122                         "config_handleVolumeKeysInWindowManager", "bool", "android"))) {
123             // Skip this test, because the PhoneWindowManager dispatches volume key
124             // events directly to the audio service to change the system volume.
125             return;
126         }
127         Handler handler = createHandler();
128 
129         // Ensure that the listener is called for long-press.
130         VolumeKeyLongPressListener listener = new VolumeKeyLongPressListener(3, handler);
131         mSessionManager.setOnVolumeKeyLongPressListener(listener, handler);
132         injectInputEvent(KeyEvent.KEYCODE_VOLUME_DOWN, true);
133         assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
134         assertEquals(listener.mKeyEvents.size(), 3);
135         assertKeyEventEquals(listener.mKeyEvents.get(0),
136                 KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.ACTION_DOWN, 0);
137         assertKeyEventEquals(listener.mKeyEvents.get(1),
138                 KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.ACTION_DOWN, 1);
139         assertKeyEventEquals(listener.mKeyEvents.get(2),
140                 KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.ACTION_UP, 0);
141 
142         // Ensure the the listener isn't called for short-press.
143         listener = new VolumeKeyLongPressListener(1, handler);
144         mSessionManager.setOnVolumeKeyLongPressListener(listener, handler);
145         injectInputEvent(KeyEvent.KEYCODE_VOLUME_DOWN, false);
146         assertFalse(listener.mCountDownLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
147         assertEquals(listener.mKeyEvents.size(), 0);
148 
149         // Ensure that the listener isn't called anymore.
150         mSessionManager.setOnVolumeKeyLongPressListener(null, handler);
151         injectInputEvent(KeyEvent.KEYCODE_VOLUME_DOWN, true);
152         assertFalse(listener.mCountDownLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
153         assertEquals(listener.mKeyEvents.size(), 0);
154 
155         removeHandler(handler);
156     }
157 
testSetOnMediaKeyListener()158     public void testSetOnMediaKeyListener() throws Exception {
159         Handler handler = createHandler();
160         MediaSession session = null;
161         try {
162             session = new MediaSession(getInstrumentation().getTargetContext(), TAG);
163             MediaSessionCallback callback = new MediaSessionCallback(2, session);
164             session.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS
165                     | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
166             session.setCallback(callback, handler);
167             PlaybackState state = new PlaybackState.Builder()
168                     .setState(PlaybackState.STATE_PLAYING, 0, 1.0f).build();
169             // Fake the media session service so this session can take the media key events.
170             session.setPlaybackState(state);
171             session.setActive(true);
172 
173             // A media playback is also needed to receive media key events.
174             Utils.assertMediaPlaybackStarted(getInstrumentation().getTargetContext());
175 
176             // Ensure that the listener is called for media key event,
177             // and any other media sessions don't get the key.
178             MediaKeyListener listener = new MediaKeyListener(2, true, handler);
179             mSessionManager.setOnMediaKeyListener(listener, handler);
180             injectInputEvent(KeyEvent.KEYCODE_HEADSETHOOK, false);
181             assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
182             assertEquals(listener.mKeyEvents.size(), 2);
183             assertKeyEventEquals(listener.mKeyEvents.get(0),
184                     KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_DOWN, 0);
185             assertKeyEventEquals(listener.mKeyEvents.get(1),
186                     KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_UP, 0);
187             assertFalse(callback.mCountDownLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
188             assertEquals(callback.mKeyEvents.size(), 0);
189 
190             // Ensure that the listener is called for media key event,
191             // and another media session gets the key.
192             listener = new MediaKeyListener(2, false, handler);
193             mSessionManager.setOnMediaKeyListener(listener, handler);
194             injectInputEvent(KeyEvent.KEYCODE_HEADSETHOOK, false);
195             assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
196             assertEquals(listener.mKeyEvents.size(), 2);
197             assertKeyEventEquals(listener.mKeyEvents.get(0),
198                     KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_DOWN, 0);
199             assertKeyEventEquals(listener.mKeyEvents.get(1),
200                     KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_UP, 0);
201             assertTrue(callback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
202             assertEquals(callback.mKeyEvents.size(), 2);
203             assertKeyEventEquals(callback.mKeyEvents.get(0),
204                     KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_DOWN, 0);
205             assertKeyEventEquals(callback.mKeyEvents.get(1),
206                     KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_UP, 0);
207 
208             // Ensure that the listener isn't called anymore.
209             listener = new MediaKeyListener(1, true, handler);
210             mSessionManager.setOnMediaKeyListener(listener, handler);
211             mSessionManager.setOnMediaKeyListener(null, handler);
212             injectInputEvent(KeyEvent.KEYCODE_HEADSETHOOK, false);
213             assertFalse(listener.mCountDownLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
214             assertEquals(listener.mKeyEvents.size(), 0);
215         } finally {
216             if (session != null) {
217                 session.release();
218             }
219             removeHandler(handler);
220         }
221     }
222 
testRemoteUserInfo()223     public void testRemoteUserInfo() throws Exception {
224         final Context context = getInstrumentation().getTargetContext();
225         Handler handler = createHandler();
226 
227         MediaSession session = null;
228         try {
229             session = new MediaSession(context , TAG);
230             MediaSessionCallback callback = new MediaSessionCallback(5, session);
231             session.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS
232                     | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
233             session.setCallback(callback, handler);
234             PlaybackState state = new PlaybackState.Builder()
235                     .setState(PlaybackState.STATE_PLAYING, 0, 1.0f).build();
236             // Fake the media session service so this session can take the media key events.
237             session.setPlaybackState(state);
238             session.setActive(true);
239 
240             // A media playback is also needed to receive media key events.
241             Utils.assertMediaPlaybackStarted(context);
242 
243             // Dispatch key events 5 times.
244             KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY);
245             // (1), (2): dispatch through key -- this will trigger event twice for up & down.
246             injectInputEvent(KeyEvent.KEYCODE_HEADSETHOOK, false);
247             // (3): dispatch through controller
248             session.getController().dispatchMediaButtonEvent(event);
249 
250             // Creating another controller.
251             MediaController controller = new MediaController(context, session.getSessionToken());
252             // (4): dispatch through different controller.
253             controller.dispatchMediaButtonEvent(event);
254             // (5): dispatch through the same controller
255             controller.dispatchMediaButtonEvent(event);
256 
257             // Wait.
258             assertTrue(callback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
259 
260             // Caller of (1) ~ (4) shouldn't be the same as any others.
261             for (int i = 0; i < 4; i ++) {
262                 for (int j = 0; j < i; j++) {
263                     assertNotSame(callback.mCallers.get(i), callback.mCallers.get(j));
264                 }
265             }
266             // Caller of (5) should be the same as (4), since they're called from the same
267             assertEquals(callback.mCallers.get(3), callback.mCallers.get(4));
268         } finally {
269             if (session != null) {
270                 session.release();
271             }
272             removeHandler(handler);
273         }
274     }
275 
testGetSession2Tokens()276     public void testGetSession2Tokens() throws Exception {
277         final Context context = getInstrumentation().getTargetContext();
278         Handler handler = createHandler();
279         Executor handlerExecutor = (runnable) -> {
280             if (handler != null) {
281                 handler.post(() -> {
282                     runnable.run();
283                 });
284             }
285         };
286 
287         Session2TokenListener listener = new Session2TokenListener();
288         mSessionManager.addOnSession2TokensChangedListener(listener, handler);
289 
290         Session2Callback sessionCallback = new Session2Callback();
291         try (MediaSession2 session = new MediaSession2.Builder(context)
292                 .setSessionCallback(handlerExecutor, sessionCallback)
293                 .build()) {
294             assertTrue(sessionCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
295             assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
296 
297             Session2Token currentToken = session.getToken();
298             assertTrue(listContainsToken(listener.mTokens, currentToken));
299             assertTrue(listContainsToken(mSessionManager.getSession2Tokens(), currentToken));
300         }
301     }
302 
testGetSession2TokensWithTwoSessions()303     public void testGetSession2TokensWithTwoSessions() throws Exception {
304         final Context context = getInstrumentation().getTargetContext();
305         Handler handler = createHandler();
306         Executor handlerExecutor = (runnable) -> {
307             if (handler != null) {
308                 handler.post(() -> {
309                     runnable.run();
310                 });
311             }
312         };
313 
314         Session2TokenListener listener = new Session2TokenListener();
315         mSessionManager.addOnSession2TokensChangedListener(listener, handler);
316 
317         try (MediaSession2 session1 = new MediaSession2.Builder(context)
318                 .setSessionCallback(handlerExecutor, new Session2Callback())
319                 .setId("testGetSession2TokensWithTwoSessions_session1")
320                 .build()) {
321 
322             assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
323             Session2Token session1Token = session1.getToken();
324             assertTrue(listContainsToken(mSessionManager.getSession2Tokens(), session1Token));
325 
326             // Create another session and check the result of getSession2Token().
327             listener.resetCountDownLatch();
328             Session2Token session2Token = null;
329             try (MediaSession2 session2 = new MediaSession2.Builder(context)
330                     .setSessionCallback(handlerExecutor, new Session2Callback())
331                     .setId("testGetSession2TokensWithTwoSessions_session2")
332                     .build()) {
333 
334                 assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
335                 session2Token = session2.getToken();
336                 assertNotNull(session2Token);
337                 assertTrue(listContainsToken(mSessionManager.getSession2Tokens(), session1Token));
338                 assertTrue(listContainsToken(mSessionManager.getSession2Tokens(), session2Token));
339 
340                 listener.resetCountDownLatch();
341             }
342 
343             // Since the session2 is closed, getSession2Tokens() shouldn't include session2's token.
344             assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
345             assertTrue(listContainsToken(mSessionManager.getSession2Tokens(), session1Token));
346             assertFalse(listContainsToken(mSessionManager.getSession2Tokens(), session2Token));
347         }
348     }
349 
testAddAndRemoveSession2TokensListener()350     public void testAddAndRemoveSession2TokensListener() throws Exception {
351         final Context context = getInstrumentation().getTargetContext();
352         Handler handler = createHandler();
353         Executor handlerExecutor = (runnable) -> {
354             if (handler != null) {
355                 handler.post(() -> {
356                     runnable.run();
357                 });
358             }
359         };
360 
361         Session2TokenListener listener1 = new Session2TokenListener();
362         mSessionManager.addOnSession2TokensChangedListener(listener1, handler);
363 
364         Session2Callback sessionCallback = new Session2Callback();
365         try (MediaSession2 session = new MediaSession2.Builder(context)
366                 .setSessionCallback(handlerExecutor, sessionCallback)
367                 .build()) {
368             assertTrue(listener1.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
369             Session2Token currentToken = session.getToken();
370             assertTrue(listContainsToken(listener1.mTokens, currentToken));
371 
372             // Test removing listener
373             listener1.resetCountDownLatch();
374             Session2TokenListener listener2 = new Session2TokenListener();
375             mSessionManager.addOnSession2TokensChangedListener(listener2, handler);
376             mSessionManager.removeOnSession2TokensChangedListener(listener1);
377 
378             session.close();
379             assertFalse(listener1.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
380             assertTrue(listener2.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
381         }
382     }
383 
listContainsToken(List<Session2Token> tokens, Session2Token token)384     private boolean listContainsToken(List<Session2Token> tokens, Session2Token token) {
385         for (int i = 0; i < tokens.size(); i++) {
386             if (tokens.get(i).equals(token)) {
387                 return true;
388             }
389         }
390         return false;
391     }
392 
createHandler()393     private Handler createHandler() {
394         HandlerThread handlerThread = new HandlerThread("MediaSessionManagerTest");
395         handlerThread.start();
396         return new Handler(handlerThread.getLooper());
397     }
398 
removeHandler(Handler handler)399     private void removeHandler(Handler handler) {
400         if (handler == null) {
401             return;
402         }
403         handler.getLooper().quitSafely();
404     }
405 
406     private class VolumeKeyLongPressListener
407             implements MediaSessionManager.OnVolumeKeyLongPressListener {
408         private final List<KeyEvent> mKeyEvents = new ArrayList<>();
409         private final CountDownLatch mCountDownLatch;
410         private final Handler mHandler;
411 
VolumeKeyLongPressListener(int count, Handler handler)412         public VolumeKeyLongPressListener(int count, Handler handler) {
413             mCountDownLatch = new CountDownLatch(count);
414             mHandler = handler;
415         }
416 
417         @Override
onVolumeKeyLongPress(KeyEvent event)418         public void onVolumeKeyLongPress(KeyEvent event) {
419             mKeyEvents.add(event);
420             // Ensure the listener is called on the thread.
421             assertEquals(mHandler.getLooper(), Looper.myLooper());
422             mCountDownLatch.countDown();
423         }
424     }
425 
426     private class MediaKeyListener implements MediaSessionManager.OnMediaKeyListener {
427         private final CountDownLatch mCountDownLatch;
428         private final boolean mConsume;
429         private final Handler mHandler;
430         private final List<KeyEvent> mKeyEvents = new ArrayList<>();
431 
MediaKeyListener(int count, boolean consume, Handler handler)432         public MediaKeyListener(int count, boolean consume, Handler handler) {
433             mCountDownLatch = new CountDownLatch(count);
434             mConsume = consume;
435             mHandler = handler;
436         }
437 
438         @Override
onMediaKey(KeyEvent event)439         public boolean onMediaKey(KeyEvent event) {
440             mKeyEvents.add(event);
441             // Ensure the listener is called on the thread.
442             assertEquals(mHandler.getLooper(), Looper.myLooper());
443             mCountDownLatch.countDown();
444             return mConsume;
445         }
446     }
447 
448     private class MediaSessionCallback extends MediaSession.Callback {
449         private final CountDownLatch mCountDownLatch;
450         private final MediaSession mSession;
451         private final List<KeyEvent> mKeyEvents = new ArrayList<>();
452         private final List<MediaSessionManager.RemoteUserInfo> mCallers = new ArrayList<>();
453 
MediaSessionCallback(int count, MediaSession session)454         private MediaSessionCallback(int count, MediaSession session) {
455             mCountDownLatch = new CountDownLatch(count);
456             mSession = session;
457         }
458 
onMediaButtonEvent(Intent mediaButtonIntent)459         public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
460             KeyEvent event = (KeyEvent) mediaButtonIntent.getParcelableExtra(
461                     Intent.EXTRA_KEY_EVENT);
462             assertNotNull(event);
463             mKeyEvents.add(event);
464             mCallers.add(mSession.getCurrentControllerInfo());
465             mCountDownLatch.countDown();
466             return true;
467         }
468     }
469 
470     private class Session2Callback extends MediaSession2.SessionCallback {
471         private CountDownLatch mCountDownLatch;
472 
Session2Callback()473         private Session2Callback() {
474             mCountDownLatch = new CountDownLatch(1);
475         }
476 
477         @Override
onConnect(MediaSession2 session, MediaSession2.ControllerInfo controller)478         public Session2CommandGroup onConnect(MediaSession2 session,
479                 MediaSession2.ControllerInfo controller) {
480             if (controller.getUid() == Process.SYSTEM_UID) {
481                 // System server will try to connect here for monitor session.
482                 mCountDownLatch.countDown();
483             }
484             return new Session2CommandGroup.Builder().build();
485         }
486     }
487 
488     private class Session2TokenListener implements
489             MediaSessionManager.OnSession2TokensChangedListener {
490         private CountDownLatch mCountDownLatch;
491         private List<Session2Token> mTokens;
492 
Session2TokenListener()493         private Session2TokenListener() {
494             mCountDownLatch = new CountDownLatch(1);
495         }
496 
497         @Override
onSession2TokensChanged(List<Session2Token> tokens)498         public void onSession2TokensChanged(List<Session2Token> tokens) {
499             mTokens = tokens;
500             mCountDownLatch.countDown();
501         }
502 
resetCountDownLatch()503         public void resetCountDownLatch() {
504             mCountDownLatch = new CountDownLatch(1);
505         }
506     }
507 }
508