• 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.misc.cts;
17 
18 import android.Manifest;
19 import android.content.ComponentName;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.pm.PackageManager;
23 import android.content.res.Resources;
24 import android.media.AudioManager;
25 import android.media.MediaSession2;
26 import android.media.Session2CommandGroup;
27 import android.media.Session2Token;
28 import android.media.cts.NonMediaMainlineTest;
29 import android.media.cts.Utils;
30 import android.media.session.MediaController;
31 import android.media.session.MediaSession;
32 import android.media.session.MediaSessionManager;
33 import android.media.session.PlaybackState;
34 import android.os.Build;
35 import android.os.Handler;
36 import android.os.HandlerThread;
37 import android.os.Looper;
38 import android.os.Process;
39 import android.platform.test.annotations.AppModeFull;
40 import android.provider.Settings;
41 import android.test.InstrumentationTestCase;
42 import android.test.UiThreadTest;
43 import android.text.TextUtils;
44 import android.view.KeyEvent;
45 
46 import androidx.test.filters.SdkSuppress;
47 
48 import com.android.compatibility.common.util.ApiLevelUtil;
49 import com.android.compatibility.common.util.MediaUtils;
50 import com.android.compatibility.common.util.SystemUtil;
51 
52 import java.io.IOException;
53 import java.util.ArrayList;
54 import java.util.List;
55 import java.util.concurrent.CountDownLatch;
56 import java.util.concurrent.Executor;
57 import java.util.concurrent.Executors;
58 import java.util.concurrent.TimeUnit;
59 
60 @AppModeFull(reason = "TODO: evaluate and port to instant")
61 public class MediaSessionManagerTest extends InstrumentationTestCase {
62     private static final String TAG = "MediaSessionManagerTest";
63     private static final int TIMEOUT_MS = 3000;
64     private static final int WAIT_MS = 500;
65     private static final String ENABLED_NOTIFICATION_LISTENERS = "enabled_notification_listeners";
66 
67     private Context mContext;
68     private AudioManager mAudioManager;
69     private MediaSessionManager mSessionManager;
70 
71     private static boolean sIsAtLeastS = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S);
72     private static boolean sIsAtLeastT = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU);
73 
74     @Override
setUp()75     protected void setUp() throws Exception {
76         super.setUp();
77         mContext = getInstrumentation().getTargetContext();
78         mAudioManager = (AudioManager) getInstrumentation().getTargetContext()
79                 .getSystemService(Context.AUDIO_SERVICE);
80         mSessionManager = (MediaSessionManager) getInstrumentation().getTargetContext()
81                 .getSystemService(Context.MEDIA_SESSION_SERVICE);
82     }
83 
84     @Override
tearDown()85     protected void tearDown() throws Exception {
86         getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
87         super.tearDown();
88     }
89 
testGetActiveSessions()90     public void testGetActiveSessions() throws Exception {
91         try {
92             List<MediaController> controllers = mSessionManager.getActiveSessions(null);
93             fail("Expected security exception for unauthorized call to getActiveSessions");
94         } catch (SecurityException e) {
95             // Expected
96         }
97         // TODO enable a notification listener, test again, disable, test again
98     }
99 
testGetMediaKeyEventSession_throwsSecurityException()100     public void testGetMediaKeyEventSession_throwsSecurityException() {
101         if (!MediaUtils.check(sIsAtLeastS, "test invalid before Android 12")) return;
102         try {
103             mSessionManager.getMediaKeyEventSession();
104             fail("Expected security exception for call to getMediaKeyEventSession");
105         } catch (SecurityException ex) {
106             // Expected
107         }
108     }
109 
testGetMediaKeyEventSessionPackageName_throwsSecurityException()110     public void testGetMediaKeyEventSessionPackageName_throwsSecurityException() {
111         if (!MediaUtils.check(sIsAtLeastS, "test invalid before Android 12")) return;
112         try {
113             mSessionManager.getMediaKeyEventSessionPackageName();
114             fail("Expected security exception for call to getMediaKeyEventSessionPackageName");
115         } catch (SecurityException ex) {
116             // Expected
117         }
118     }
119 
testOnMediaKeyEventSessionChangedListener()120     public void testOnMediaKeyEventSessionChangedListener() throws Exception {
121         // The permission can be held only on S+
122         if (!MediaUtils.check(sIsAtLeastS, "test invalid before Android 12")) return;
123 
124         getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
125                 Manifest.permission.MEDIA_CONTENT_CONTROL,
126                 Manifest.permission.MANAGE_EXTERNAL_STORAGE);
127 
128         MediaKeyEventSessionListener keyEventSessionListener = new MediaKeyEventSessionListener();
129         mSessionManager.addOnMediaKeyEventSessionChangedListener(
130                 Executors.newSingleThreadExecutor(), keyEventSessionListener);
131 
132         MediaSession session = createMediaKeySession();
133         assertTrue(keyEventSessionListener.mCountDownLatch
134                 .await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
135 
136         assertEquals(session.getSessionToken(), keyEventSessionListener.mSessionToken);
137         assertEquals(session.getSessionToken(), mSessionManager.getMediaKeyEventSession());
138         assertEquals(getInstrumentation().getTargetContext().getPackageName(),
139                 mSessionManager.getMediaKeyEventSessionPackageName());
140 
141         mSessionManager.removeOnMediaKeyEventSessionChangedListener(keyEventSessionListener);
142         keyEventSessionListener.resetCountDownLatch();
143 
144         session.release();
145         // This shouldn't be called because the callback is removed
146         assertFalse(keyEventSessionListener.mCountDownLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
147     }
148 
testOnMediaKeyEventSessionChangedListener_whenSessionIsReleased()149     public void testOnMediaKeyEventSessionChangedListener_whenSessionIsReleased() throws Exception {
150         // The permission can be held only on S+
151         if (!MediaUtils.check(sIsAtLeastS, "test invalid before Android 12")) return;
152 
153         getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
154                 Manifest.permission.MEDIA_CONTENT_CONTROL,
155                 Manifest.permission.MANAGE_EXTERNAL_STORAGE);
156 
157         MediaKeyEventSessionListener keyEventSessionListener = new MediaKeyEventSessionListener();
158         mSessionManager.addOnMediaKeyEventSessionChangedListener(
159                 Executors.newSingleThreadExecutor(), keyEventSessionListener);
160 
161         MediaSession session = createMediaKeySession();
162         assertTrue(keyEventSessionListener.mCountDownLatch
163                 .await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
164 
165         // Check that this is called when the session is released.
166         keyEventSessionListener.resetCountDownLatch();
167         session.release();
168         assertTrue(keyEventSessionListener.mCountDownLatch
169                 .await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
170         assertNull(keyEventSessionListener.mSessionToken);
171         if (ApiLevelUtil.isAfter(Build.VERSION_CODES.TIRAMISU)) {
172             assertEquals("", keyEventSessionListener.mPackageName);
173         }
174 
175         assertNull(mSessionManager.getMediaKeyEventSession());
176         assertEquals("", mSessionManager.getMediaKeyEventSessionPackageName());
177     }
178 
179     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
180     @NonMediaMainlineTest
testOnMediaKeyEventSessionChangedListener_noSession_passesEmptyPackageAndNullToken()181     public void testOnMediaKeyEventSessionChangedListener_noSession_passesEmptyPackageAndNullToken()
182             throws InterruptedException {
183         // The permission can be held only on S+
184         getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
185                 Manifest.permission.MEDIA_CONTENT_CONTROL);
186 
187         MediaKeyEventSessionListener keyEventSessionListener = new MediaKeyEventSessionListener();
188         mSessionManager.addOnMediaKeyEventSessionChangedListener(
189                 Executors.newSingleThreadExecutor(), keyEventSessionListener);
190 
191         assertTrue(keyEventSessionListener.mCountDownLatch
192                 .await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
193         assertNull(keyEventSessionListener.mSessionToken);
194         assertEquals("", keyEventSessionListener.mPackageName);
195 
196         // Clean up listener.
197         mSessionManager.removeOnMediaKeyEventSessionChangedListener(keyEventSessionListener);
198     }
199 
createMediaKeySession()200     private MediaSession createMediaKeySession() {
201         MediaSession session = new MediaSession(getInstrumentation().getTargetContext(), TAG);
202         session.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS
203                 | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
204         PlaybackState state = new PlaybackState.Builder()
205                 .setState(PlaybackState.STATE_PLAYING, 0, 1.0f).build();
206         // Fake the media session service so this session can take the media key events.
207         session.setPlaybackState(state);
208         session.setActive(true);
209         Utils.assertMediaPlaybackStarted(getInstrumentation().getTargetContext());
210 
211         return session;
212     }
213 
testOnMediaKeyEventSessionChangedListener_noPermission_throwsSecurityException()214     public void testOnMediaKeyEventSessionChangedListener_noPermission_throwsSecurityException() {
215         if (!MediaUtils.check(sIsAtLeastS, "test invalid before Android 12")) return;
216         MediaKeyEventSessionListener keyEventSessionListener = new MediaKeyEventSessionListener();
217         try {
218             mSessionManager.addOnMediaKeyEventSessionChangedListener(
219                     Executors.newSingleThreadExecutor(), keyEventSessionListener);
220             fail("Expected security exception for call to"
221                     + " addOnMediaKeyEventSessionChangedListener");
222         } catch (SecurityException ex) {
223             // Expected
224         }
225     }
226 
testOnMediaKeyEventDispatchedListener()227     public void testOnMediaKeyEventDispatchedListener() throws Exception {
228         // The permission can be held only on S+
229         if (!MediaUtils.check(sIsAtLeastS, "test invalid before Android 12")) return;
230 
231         getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
232                 Manifest.permission.MEDIA_CONTENT_CONTROL,
233                 Manifest.permission.MANAGE_EXTERNAL_STORAGE);
234 
235         MediaKeyEventDispatchedListener keyEventDispatchedListener =
236                 new MediaKeyEventDispatchedListener();
237         mSessionManager.addOnMediaKeyEventDispatchedListener(Executors.newSingleThreadExecutor(),
238                 keyEventDispatchedListener);
239 
240         MediaSession session = new MediaSession(getInstrumentation().getTargetContext(), TAG);
241         session.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS
242                 | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
243         PlaybackState state = new PlaybackState.Builder()
244                 .setState(PlaybackState.STATE_PLAYING, 0, 1.0f).build();
245         // Fake the media session service so this session can take the media key events.
246         session.setPlaybackState(state);
247         session.setActive(true);
248         Utils.assertMediaPlaybackStarted(getInstrumentation().getTargetContext());
249 
250         final int keyCode = KeyEvent.KEYCODE_MEDIA_PLAY;
251         simulateMediaKeyInput(keyCode);
252         assertTrue(keyEventDispatchedListener.mCountDownLatch
253                 .await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
254 
255         assertEquals(keyCode, keyEventDispatchedListener.mKeyEvent.getKeyCode());
256         assertEquals(getInstrumentation().getTargetContext().getPackageName(),
257                 keyEventDispatchedListener.mPackageName);
258         assertEquals(session.getSessionToken(), keyEventDispatchedListener.mSessionToken);
259 
260         mSessionManager.removeOnMediaKeyEventDispatchedListener(keyEventDispatchedListener);
261         keyEventDispatchedListener.resetCountDownLatch();
262 
263         simulateMediaKeyInput(keyCode);
264         // This shouldn't be called because the callback is removed
265         assertFalse(keyEventDispatchedListener.mCountDownLatch
266                 .await(WAIT_MS, TimeUnit.MILLISECONDS));
267 
268         session.release();
269     }
270 
271     @UiThreadTest
testAddOnActiveSessionsListener()272     public void testAddOnActiveSessionsListener() throws Exception {
273         if (!MediaUtils.check(sIsAtLeastS, "test invalid before Android 12")) return;
274         try {
275             mSessionManager.addOnActiveSessionsChangedListener(null, null);
276             fail("Expected NPE for call to addOnActiveSessionsChangedListener");
277         } catch (NullPointerException e) {
278             // Expected
279         }
280 
281         MediaSessionManager.OnActiveSessionsChangedListener listener
282                 = new MediaSessionManager.OnActiveSessionsChangedListener() {
283             @Override
284             public void onActiveSessionsChanged(List<MediaController> controllers) {
285 
286             }
287         };
288         try {
289             mSessionManager.addOnActiveSessionsChangedListener(listener, null);
290             fail("Expected security exception for call to addOnActiveSessionsChangedListener");
291         } catch (SecurityException e) {
292             // Expected
293         }
294     }
295 
assertKeyEventEquals(KeyEvent lhs, int keyCode, int action, int repeatCount)296     private void assertKeyEventEquals(KeyEvent lhs, int keyCode, int action, int repeatCount) {
297         assertTrue(lhs.getKeyCode() == keyCode
298                 && lhs.getAction() == action
299                 && lhs.getRepeatCount() == repeatCount);
300     }
301 
injectInputEvent(int keyCode, boolean longPress)302     private void injectInputEvent(int keyCode, boolean longPress) throws IOException {
303         // Injecting key with instrumentation requires a window/view, but we don't have it.
304         // Inject key event through the adb commend to workaround.
305         final String command = "input keyevent " + (longPress ? "--longpress " : "") + keyCode;
306         SystemUtil.runShellCommand(getInstrumentation(), command);
307     }
308 
testSetOnVolumeKeyLongPressListener()309     public void testSetOnVolumeKeyLongPressListener() throws Exception {
310         Context context = getInstrumentation().getTargetContext();
311         if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)
312                 || context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)
313                 || context.getResources().getBoolean(Resources.getSystem().getIdentifier(
314                         "config_handleVolumeKeysInWindowManager", "bool", "android"))) {
315             // Skip this test, because the PhoneWindowManager dispatches volume key
316             // events directly to the audio service to change the system volume.
317             return;
318         }
319         Handler handler = createHandler();
320 
321         // Ensure that the listener is called for long-press.
322         VolumeKeyLongPressListener listener = new VolumeKeyLongPressListener(3, handler);
323         mSessionManager.setOnVolumeKeyLongPressListener(listener, handler);
324         injectInputEvent(KeyEvent.KEYCODE_VOLUME_DOWN, true);
325         assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
326         assertEquals(listener.mKeyEvents.size(), 3);
327         assertKeyEventEquals(listener.mKeyEvents.get(0),
328                 KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.ACTION_DOWN, 0);
329         assertKeyEventEquals(listener.mKeyEvents.get(1),
330                 KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.ACTION_DOWN, 1);
331         assertKeyEventEquals(listener.mKeyEvents.get(2),
332                 KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.ACTION_UP, 0);
333 
334         // Ensure the the listener isn't called for short-press.
335         listener = new VolumeKeyLongPressListener(1, handler);
336         mSessionManager.setOnVolumeKeyLongPressListener(listener, handler);
337         injectInputEvent(KeyEvent.KEYCODE_VOLUME_DOWN, false);
338         assertFalse(listener.mCountDownLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
339         assertEquals(listener.mKeyEvents.size(), 0);
340 
341         // Ensure that the listener isn't called anymore.
342         mSessionManager.setOnVolumeKeyLongPressListener(null, handler);
343         injectInputEvent(KeyEvent.KEYCODE_VOLUME_DOWN, true);
344         assertFalse(listener.mCountDownLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
345         assertEquals(listener.mKeyEvents.size(), 0);
346 
347         removeHandler(handler);
348     }
349 
testSetOnMediaKeyListener()350     public void testSetOnMediaKeyListener() throws Exception {
351         Handler handler = createHandler();
352         MediaSession session = null;
353         try {
354             session = new MediaSession(getInstrumentation().getTargetContext(), TAG);
355             MediaSessionCallback callback = new MediaSessionCallback(2, session);
356             session.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS
357                     | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
358             session.setCallback(callback, handler);
359             PlaybackState state = new PlaybackState.Builder()
360                     .setState(PlaybackState.STATE_PLAYING, 0, 1.0f).build();
361             // Fake the media session service so this session can take the media key events.
362             session.setPlaybackState(state);
363             session.setActive(true);
364 
365             // A media playback is also needed to receive media key events.
366             Utils.assertMediaPlaybackStarted(getInstrumentation().getTargetContext());
367 
368             // Ensure that the listener is called for media key event,
369             // and any other media sessions don't get the key.
370             MediaKeyListener listener = new MediaKeyListener(2, true, handler);
371             mSessionManager.setOnMediaKeyListener(listener, handler);
372             injectInputEvent(KeyEvent.KEYCODE_HEADSETHOOK, false);
373             assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
374             assertEquals(listener.mKeyEvents.size(), 2);
375             assertKeyEventEquals(listener.mKeyEvents.get(0),
376                     KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_DOWN, 0);
377             assertKeyEventEquals(listener.mKeyEvents.get(1),
378                     KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_UP, 0);
379             assertFalse(callback.mCountDownLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
380             assertEquals(callback.mKeyEvents.size(), 0);
381 
382             // Ensure that the listener is called for media key event,
383             // and another media session gets the key.
384             listener = new MediaKeyListener(2, false, handler);
385             mSessionManager.setOnMediaKeyListener(listener, handler);
386             injectInputEvent(KeyEvent.KEYCODE_HEADSETHOOK, false);
387             assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
388             assertEquals(listener.mKeyEvents.size(), 2);
389             assertKeyEventEquals(listener.mKeyEvents.get(0),
390                     KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_DOWN, 0);
391             assertKeyEventEquals(listener.mKeyEvents.get(1),
392                     KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_UP, 0);
393             assertTrue(callback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
394             assertEquals(callback.mKeyEvents.size(), 2);
395             assertKeyEventEquals(callback.mKeyEvents.get(0),
396                     KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_DOWN, 0);
397             assertKeyEventEquals(callback.mKeyEvents.get(1),
398                     KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_UP, 0);
399 
400             // Ensure that the listener isn't called anymore.
401             listener = new MediaKeyListener(1, true, handler);
402             mSessionManager.setOnMediaKeyListener(listener, handler);
403             mSessionManager.setOnMediaKeyListener(null, handler);
404             injectInputEvent(KeyEvent.KEYCODE_HEADSETHOOK, false);
405             assertFalse(listener.mCountDownLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
406             assertEquals(listener.mKeyEvents.size(), 0);
407         } finally {
408             if (session != null) {
409                 session.release();
410             }
411             removeHandler(handler);
412         }
413     }
414 
testRemoteUserInfo()415     public void testRemoteUserInfo() throws Exception {
416         final Context context = getInstrumentation().getTargetContext();
417         Handler handler = createHandler();
418 
419         MediaSession session = null;
420         try {
421             session = new MediaSession(context , TAG);
422             MediaSessionCallback callback = new MediaSessionCallback(5, session);
423             session.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS
424                     | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
425             session.setCallback(callback, handler);
426             PlaybackState state = new PlaybackState.Builder()
427                     .setState(PlaybackState.STATE_PLAYING, 0, 1.0f).build();
428             // Fake the media session service so this session can take the media key events.
429             session.setPlaybackState(state);
430             session.setActive(true);
431 
432             // A media playback is also needed to receive media key events.
433             Utils.assertMediaPlaybackStarted(context);
434 
435             // Dispatch key events 5 times.
436             KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY);
437             // (1), (2): dispatch through key -- this will trigger event twice for up & down.
438             injectInputEvent(KeyEvent.KEYCODE_HEADSETHOOK, false);
439             // (3): dispatch through controller
440             session.getController().dispatchMediaButtonEvent(event);
441 
442             // Creating another controller.
443             MediaController controller = new MediaController(context, session.getSessionToken());
444             // (4): dispatch through different controller.
445             controller.dispatchMediaButtonEvent(event);
446             // (5): dispatch through the same controller
447             controller.dispatchMediaButtonEvent(event);
448 
449             // Wait.
450             assertTrue(callback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
451 
452             // Caller of (1) ~ (4) shouldn't be the same as any others.
453             for (int i = 0; i < 4; i ++) {
454                 for (int j = 0; j < i; j++) {
455                     assertNotSame(callback.mCallers.get(i), callback.mCallers.get(j));
456                 }
457             }
458             // Caller of (5) should be the same as (4), since they're called from the same
459             assertEquals(callback.mCallers.get(3), callback.mCallers.get(4));
460         } finally {
461             if (session != null) {
462                 session.release();
463             }
464             removeHandler(handler);
465         }
466     }
467 
testGetSession2Tokens()468     public void testGetSession2Tokens() throws Exception {
469         final Context context = getInstrumentation().getTargetContext();
470         Handler handler = createHandler();
471         Executor handlerExecutor = new HandlerExecutor(handler);
472 
473         Session2TokenListener listener = new Session2TokenListener();
474         mSessionManager.addOnSession2TokensChangedListener(listener, handler);
475 
476         Session2Callback sessionCallback = new Session2Callback();
477         try (MediaSession2 session = new MediaSession2.Builder(context)
478                 .setSessionCallback(handlerExecutor, sessionCallback)
479                 .build()) {
480             assertTrue(sessionCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
481             assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
482 
483             Session2Token currentToken = session.getToken();
484             assertTrue(listContainsToken(listener.mTokens, currentToken));
485             assertTrue(listContainsToken(mSessionManager.getSession2Tokens(), currentToken));
486         }
487     }
488 
testGetSession2TokensWithTwoSessions()489     public void testGetSession2TokensWithTwoSessions() throws Exception {
490         final Context context = getInstrumentation().getTargetContext();
491         Handler handler = createHandler();
492         Executor handlerExecutor = new HandlerExecutor(handler);
493 
494         Session2TokenListener listener = new Session2TokenListener();
495         mSessionManager.addOnSession2TokensChangedListener(listener, handler);
496 
497         try (MediaSession2 session1 = new MediaSession2.Builder(context)
498                 .setSessionCallback(handlerExecutor, new Session2Callback())
499                 .setId("testGetSession2TokensWithTwoSessions_session1")
500                 .build()) {
501 
502             assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
503             Session2Token session1Token = session1.getToken();
504             assertTrue(listContainsToken(mSessionManager.getSession2Tokens(), session1Token));
505 
506             // Create another session and check the result of getSession2Token().
507             listener.resetCountDownLatch();
508             Session2Token session2Token = null;
509             try (MediaSession2 session2 = new MediaSession2.Builder(context)
510                     .setSessionCallback(handlerExecutor, new Session2Callback())
511                     .setId("testGetSession2TokensWithTwoSessions_session2")
512                     .build()) {
513 
514                 assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
515                 session2Token = session2.getToken();
516                 assertNotNull(session2Token);
517                 assertTrue(listContainsToken(mSessionManager.getSession2Tokens(), session1Token));
518                 assertTrue(listContainsToken(mSessionManager.getSession2Tokens(), session2Token));
519 
520                 listener.resetCountDownLatch();
521             }
522 
523             // Since the session2 is closed, getSession2Tokens() shouldn't include session2's token.
524             assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
525             assertTrue(listContainsToken(mSessionManager.getSession2Tokens(), session1Token));
526             assertFalse(listContainsToken(mSessionManager.getSession2Tokens(), session2Token));
527         }
528     }
529 
testAddAndRemoveSession2TokensListener()530     public void testAddAndRemoveSession2TokensListener() throws Exception {
531         final Context context = getInstrumentation().getTargetContext();
532         Handler handler = createHandler();
533         Executor handlerExecutor = new HandlerExecutor(handler);
534 
535         Session2TokenListener listener1 = new Session2TokenListener();
536         mSessionManager.addOnSession2TokensChangedListener(listener1, handler);
537 
538         Session2Callback sessionCallback = new Session2Callback();
539         try (MediaSession2 session = new MediaSession2.Builder(context)
540                 .setSessionCallback(handlerExecutor, sessionCallback)
541                 .build()) {
542             assertTrue(listener1.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
543             Session2Token currentToken = session.getToken();
544             assertTrue(listContainsToken(listener1.mTokens, currentToken));
545 
546             // Test removing listener
547             listener1.resetCountDownLatch();
548             Session2TokenListener listener2 = new Session2TokenListener();
549             mSessionManager.addOnSession2TokensChangedListener(listener2, handler);
550             mSessionManager.removeOnSession2TokensChangedListener(listener1);
551 
552             session.close();
553             assertFalse(listener1.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
554             assertTrue(listener2.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
555         }
556     }
557 
testSession2TokensNotChangedBySession1()558     public void testSession2TokensNotChangedBySession1() throws Exception {
559         final Context context = getInstrumentation().getTargetContext();
560         Handler handler = createHandler();
561 
562         Session2TokenListener listener = new Session2TokenListener();
563         List<Session2Token> initialSession2Tokens = mSessionManager.getSession2Tokens();
564         mSessionManager.addOnSession2TokensChangedListener(listener, handler);
565         MediaSession session = null;
566         try {
567             session = new MediaSession(context, TAG);
568             session.setActive(true);
569             session.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS
570                     | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
571             assertFalse(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
572             List<Session2Token> laterSession2Tokens = mSessionManager.getSession2Tokens();
573 
574             assertEquals(initialSession2Tokens.size(), laterSession2Tokens.size());
575         } finally {
576             if (session != null) {
577                 session.release();
578             }
579         }
580     }
581 
testCustomClassConfigValuesAreValid()582     public void testCustomClassConfigValuesAreValid() throws Exception {
583         if (!MediaUtils.check(sIsAtLeastS, "test invalid before Android 12")) return;
584         final Context context = getInstrumentation().getTargetContext();
585         String customMediaKeyDispatcher = context.getString(
586                 android.R.string.config_customMediaKeyDispatcher);
587         String customMediaSessionPolicyProvider = context.getString(
588                 android.R.string.config_customMediaSessionPolicyProvider);
589         // MediaSessionService will call Class.forName(String) with the existing config value.
590         // If the config value is not valid (i.e. given class doesn't exist), the following
591         // methods will return false.
592         if (!customMediaKeyDispatcher.isEmpty()) {
593             assertTrue(mSessionManager.hasCustomMediaKeyDispatcher(customMediaKeyDispatcher));
594         }
595         if (!customMediaSessionPolicyProvider.isEmpty()) {
596             assertTrue(mSessionManager.hasCustomMediaSessionPolicyProvider(
597                     customMediaSessionPolicyProvider));
598         }
599     }
600 
testIsTrustedForMediaControl_withEnabledNotificationListener()601     public void testIsTrustedForMediaControl_withEnabledNotificationListener() throws Exception {
602         List<String> packageNames = getEnabledNotificationListenerPackages();
603         for (String packageName : packageNames) {
604             int packageUid =
605                     mContext.getPackageManager().getPackageUid(packageName, /* flags= */ 0);
606             MediaSessionManager.RemoteUserInfo info =
607                     new MediaSessionManager.RemoteUserInfo(packageName, /* pid= */ 0, packageUid);
608             assertTrue(mSessionManager.isTrustedForMediaControl(info));
609         }
610     }
611 
612     @NonMediaMainlineTest
testIsTrustedForMediaControl_withInvalidUid()613     public void testIsTrustedForMediaControl_withInvalidUid() throws Exception {
614         List<String> packageNames = getEnabledNotificationListenerPackages();
615         for (String packageName : packageNames) {
616             MediaSessionManager.RemoteUserInfo info =
617                     new MediaSessionManager.RemoteUserInfo(
618                             packageName, /* pid= */ 0, Process.myUid());
619             assertFalse(mSessionManager.isTrustedForMediaControl(info));
620         }
621     }
622 
listContainsToken(List<Session2Token> tokens, Session2Token token)623     private boolean listContainsToken(List<Session2Token> tokens, Session2Token token) {
624         for (int i = 0; i < tokens.size(); i++) {
625             if (tokens.get(i).equals(token)) {
626                 return true;
627             }
628         }
629         return false;
630     }
631 
createHandler()632     private Handler createHandler() {
633         HandlerThread handlerThread = new HandlerThread("MediaSessionManagerTest");
634         handlerThread.start();
635         return new Handler(handlerThread.getLooper());
636     }
637 
removeHandler(Handler handler)638     private void removeHandler(Handler handler) {
639         if (handler == null) {
640             return;
641         }
642         handler.getLooper().quitSafely();
643     }
644 
645     // This uses public APIs to dispatch key events, so sessions would consider this as
646     // 'media key event from this application'.
simulateMediaKeyInput(int keyCode)647     private void simulateMediaKeyInput(int keyCode) {
648         long downTime = System.currentTimeMillis();
649         mAudioManager.dispatchMediaKeyEvent(
650                 new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN, keyCode, 0));
651         mAudioManager.dispatchMediaKeyEvent(
652                 new KeyEvent(downTime, System.currentTimeMillis(), KeyEvent.ACTION_UP, keyCode, 0));
653     }
654 
getEnabledNotificationListenerPackages()655     private List<String> getEnabledNotificationListenerPackages() {
656         List<String> listeners = new ArrayList<>();
657         String enabledNotificationListeners =
658                 Settings.Secure.getString(
659                         mContext.getContentResolver(),
660                         ENABLED_NOTIFICATION_LISTENERS);
661         if (!TextUtils.isEmpty(enabledNotificationListeners)) {
662             String[] components = enabledNotificationListeners.split(":");
663             for (String componentString : components) {
664                 ComponentName component = ComponentName.unflattenFromString(componentString);
665                 if (component != null) {
666                     listeners.add(component.getPackageName());
667                 }
668             }
669         }
670         return listeners;
671     }
672 
673     private class VolumeKeyLongPressListener
674             implements MediaSessionManager.OnVolumeKeyLongPressListener {
675         private final List<KeyEvent> mKeyEvents = new ArrayList<>();
676         private final CountDownLatch mCountDownLatch;
677         private final Handler mHandler;
678 
VolumeKeyLongPressListener(int count, Handler handler)679         public VolumeKeyLongPressListener(int count, Handler handler) {
680             mCountDownLatch = new CountDownLatch(count);
681             mHandler = handler;
682         }
683 
684         @Override
onVolumeKeyLongPress(KeyEvent event)685         public void onVolumeKeyLongPress(KeyEvent event) {
686             mKeyEvents.add(event);
687             // Ensure the listener is called on the thread.
688             assertEquals(mHandler.getLooper(), Looper.myLooper());
689             mCountDownLatch.countDown();
690         }
691     }
692 
693     private class MediaKeyListener implements MediaSessionManager.OnMediaKeyListener {
694         private final CountDownLatch mCountDownLatch;
695         private final boolean mConsume;
696         private final Handler mHandler;
697         private final List<KeyEvent> mKeyEvents = new ArrayList<>();
698 
MediaKeyListener(int count, boolean consume, Handler handler)699         public MediaKeyListener(int count, boolean consume, Handler handler) {
700             mCountDownLatch = new CountDownLatch(count);
701             mConsume = consume;
702             mHandler = handler;
703         }
704 
705         @Override
onMediaKey(KeyEvent event)706         public boolean onMediaKey(KeyEvent event) {
707             mKeyEvents.add(event);
708             // Ensure the listener is called on the thread.
709             assertEquals(mHandler.getLooper(), Looper.myLooper());
710             mCountDownLatch.countDown();
711             return mConsume;
712         }
713     }
714 
715     private class MediaSessionCallback extends MediaSession.Callback {
716         private final CountDownLatch mCountDownLatch;
717         private final MediaSession mSession;
718         private final List<KeyEvent> mKeyEvents = new ArrayList<>();
719         private final List<MediaSessionManager.RemoteUserInfo> mCallers = new ArrayList<>();
720 
MediaSessionCallback(int count, MediaSession session)721         private MediaSessionCallback(int count, MediaSession session) {
722             mCountDownLatch = new CountDownLatch(count);
723             mSession = session;
724         }
725 
onMediaButtonEvent(Intent mediaButtonIntent)726         public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
727             KeyEvent event = (KeyEvent) mediaButtonIntent.getParcelableExtra(
728                     Intent.EXTRA_KEY_EVENT);
729             assertNotNull(event);
730             mKeyEvents.add(event);
731             mCallers.add(mSession.getCurrentControllerInfo());
732             mCountDownLatch.countDown();
733             return true;
734         }
735     }
736 
737     private class Session2Callback extends MediaSession2.SessionCallback {
738         private CountDownLatch mCountDownLatch;
739 
Session2Callback()740         private Session2Callback() {
741             mCountDownLatch = new CountDownLatch(1);
742         }
743 
744         @Override
onConnect(MediaSession2 session, MediaSession2.ControllerInfo controller)745         public Session2CommandGroup onConnect(MediaSession2 session,
746                 MediaSession2.ControllerInfo controller) {
747             if (controller.getUid() == Process.SYSTEM_UID) {
748                 // System server will try to connect here for monitor session.
749                 mCountDownLatch.countDown();
750             }
751             return new Session2CommandGroup.Builder().build();
752         }
753     }
754 
755     private class Session2TokenListener implements
756             MediaSessionManager.OnSession2TokensChangedListener {
757         private CountDownLatch mCountDownLatch;
758         private List<Session2Token> mTokens;
759 
Session2TokenListener()760         private Session2TokenListener() {
761             mCountDownLatch = new CountDownLatch(1);
762         }
763 
764         @Override
onSession2TokensChanged(List<Session2Token> tokens)765         public void onSession2TokensChanged(List<Session2Token> tokens) {
766             mTokens = tokens;
767             mCountDownLatch.countDown();
768         }
769 
resetCountDownLatch()770         public void resetCountDownLatch() {
771             mCountDownLatch = new CountDownLatch(1);
772         }
773     }
774 
775     private class MediaKeyEventSessionListener
776             implements MediaSessionManager.OnMediaKeyEventSessionChangedListener {
777         CountDownLatch mCountDownLatch;
778         MediaSession.Token mSessionToken;
779         String mPackageName;
780 
MediaKeyEventSessionListener()781         MediaKeyEventSessionListener() {
782             mCountDownLatch = new CountDownLatch(1);
783         }
784 
resetCountDownLatch()785         void resetCountDownLatch() {
786             mCountDownLatch = new CountDownLatch(1);
787             mSessionToken = null;
788             mPackageName = null;
789         }
790 
791         @Override
onMediaKeyEventSessionChanged(String packageName, MediaSession.Token sessionToken)792         public void onMediaKeyEventSessionChanged(String packageName,
793                 MediaSession.Token sessionToken) {
794             if (ApiLevelUtil.isAfter(Build.VERSION_CODES.TIRAMISU)) {
795                 assertNotNull("The package name cannot be null.", packageName);
796             }
797             mSessionToken = sessionToken;
798             mPackageName = packageName;
799             mCountDownLatch.countDown();
800         }
801     }
802 
803     private class MediaKeyEventDispatchedListener
804             implements MediaSessionManager.OnMediaKeyEventDispatchedListener {
805         CountDownLatch mCountDownLatch;
806         KeyEvent mKeyEvent;
807         String mPackageName;
808         MediaSession.Token mSessionToken;
809 
MediaKeyEventDispatchedListener()810         MediaKeyEventDispatchedListener() {
811             mCountDownLatch = new CountDownLatch(1);
812         }
813 
resetCountDownLatch()814         void resetCountDownLatch() {
815             mCountDownLatch = new CountDownLatch(1);
816         }
817 
818         @Override
onMediaKeyEventDispatched(KeyEvent event, String packageName, MediaSession.Token sessionToken)819         public void onMediaKeyEventDispatched(KeyEvent event, String packageName,
820                 MediaSession.Token sessionToken) {
821             mKeyEvent = event;
822             mPackageName = packageName;
823             mSessionToken = sessionToken;
824 
825             mCountDownLatch.countDown();
826         }
827     }
828 
829     private static class HandlerExecutor implements Executor {
830         private final Handler mHandler;
831 
HandlerExecutor(Handler handler)832         HandlerExecutor(Handler handler) {
833             mHandler = handler;
834         }
835 
836         @Override
execute(Runnable command)837         public void execute(Runnable command) {
838             mHandler.post(command);
839         }
840     }
841 }
842