Home | History | Annotate | Download | only in cts
      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
     56     protected void setUp() throws Exception {
     57         super.setUp();
     58         mSessionManager = (MediaSessionManager) getInstrumentation().getTargetContext()
     59                 .getSystemService(Context.MEDIA_SESSION_SERVICE);
     60     }
     61 
     62     @Override
     63     protected void tearDown() throws Exception {
     64         super.tearDown();
     65     }
     66 
     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
     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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    393     private Handler createHandler() {
    394         HandlerThread handlerThread = new HandlerThread("MediaSessionManagerTest");
    395         handlerThread.start();
    396         return new Handler(handlerThread.getLooper());
    397     }
    398 
    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 
    412         public VolumeKeyLongPressListener(int count, Handler handler) {
    413             mCountDownLatch = new CountDownLatch(count);
    414             mHandler = handler;
    415         }
    416 
    417         @Override
    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 
    432         public MediaKeyListener(int count, boolean consume, Handler handler) {
    433             mCountDownLatch = new CountDownLatch(count);
    434             mConsume = consume;
    435             mHandler = handler;
    436         }
    437 
    438         @Override
    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 
    454         private MediaSessionCallback(int count, MediaSession session) {
    455             mCountDownLatch = new CountDownLatch(count);
    456             mSession = session;
    457         }
    458 
    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 
    473         private Session2Callback() {
    474             mCountDownLatch = new CountDownLatch(1);
    475         }
    476 
    477         @Override
    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 
    493         private Session2TokenListener() {
    494             mCountDownLatch = new CountDownLatch(1);
    495         }
    496 
    497         @Override
    498         public void onSession2TokensChanged(List<Session2Token> tokens) {
    499             mTokens = tokens;
    500             mCountDownLatch.countDown();
    501         }
    502 
    503         public void resetCountDownLatch() {
    504             mCountDownLatch = new CountDownLatch(1);
    505         }
    506     }
    507 }
    508