Home | History | Annotate | Download | only in cts
      1 /*
      2  * Copyright 2019 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 
     17 package android.media.cts;
     18 
     19 import static org.junit.Assert.assertEquals;
     20 import static org.junit.Assert.assertFalse;
     21 import static org.junit.Assert.assertNotEquals;
     22 import static org.junit.Assert.assertNotNull;
     23 import static org.junit.Assert.assertSame;
     24 import static org.junit.Assert.assertTrue;
     25 import static org.testng.Assert.assertNull;
     26 
     27 import android.app.Notification;
     28 import android.content.ComponentName;
     29 import android.content.Context;
     30 import android.content.Intent;
     31 import android.media.MediaController2;
     32 import android.media.MediaSession2;
     33 import android.media.MediaSession2.ControllerInfo;
     34 import android.media.MediaSession2Service;
     35 import android.media.Session2CommandGroup;
     36 import android.media.Session2Token;
     37 import android.os.Bundle;
     38 import android.os.HandlerThread;
     39 import android.os.Process;
     40 
     41 import androidx.test.InstrumentationRegistry;
     42 import androidx.test.filters.SmallTest;
     43 import androidx.test.runner.AndroidJUnit4;
     44 
     45 import org.junit.After;
     46 import org.junit.AfterClass;
     47 import org.junit.Before;
     48 import org.junit.BeforeClass;
     49 import org.junit.Test;
     50 import org.junit.runner.RunWith;
     51 
     52 import java.util.ArrayList;
     53 import java.util.List;
     54 import java.util.concurrent.CountDownLatch;
     55 import java.util.concurrent.TimeUnit;
     56 
     57 /**
     58  * Tests {@link MediaSession2Service}.
     59  */
     60 @RunWith(AndroidJUnit4.class)
     61 @SmallTest
     62 public class MediaSession2ServiceTest {
     63     private static final long TIMEOUT_MS = 3000L;
     64     private static final long WAIT_TIME_FOR_NO_RESPONSE_MS = 500L;
     65 
     66     private static HandlerExecutor sHandlerExecutor;
     67     private final List<MediaController2> mControllers = new ArrayList<>();
     68     private Context mContext;
     69     private Session2Token mToken;
     70 
     71     @BeforeClass
     72     public static void setUpThread() {
     73         synchronized (MediaSession2ServiceTest.class) {
     74             if (sHandlerExecutor != null) {
     75                 return;
     76             }
     77             HandlerThread handlerThread = new HandlerThread("MediaSession2ServiceTest");
     78             handlerThread.start();
     79             sHandlerExecutor = new HandlerExecutor(handlerThread.getLooper());
     80             StubMediaSession2Service.setHandlerExecutor(sHandlerExecutor);
     81         }
     82     }
     83 
     84     @AfterClass
     85     public static void cleanUpThread() {
     86         synchronized (MediaSession2Test.class) {
     87             if (sHandlerExecutor == null) {
     88                 return;
     89             }
     90             StubMediaSession2Service.setHandlerExecutor(null);
     91             sHandlerExecutor.getLooper().quitSafely();
     92             sHandlerExecutor = null;
     93         }
     94     }
     95 
     96     @Before
     97     public void setUp() throws Exception {
     98         mContext = InstrumentationRegistry.getContext();
     99         mToken = new Session2Token(mContext,
    100                 new ComponentName(mContext, StubMediaSession2Service.class));
    101     }
    102 
    103     @After
    104     public void cleanUp() throws Exception {
    105         for (MediaController2 controller : mControllers) {
    106             controller.close();
    107         }
    108         mControllers.clear();
    109 
    110         StubMediaSession2Service.setTestInjector(null);
    111     }
    112 
    113     /**
    114      * Tests whether {@link MediaSession2Service#onGetSession(ControllerInfo)}
    115      * is called when controller tries to connect, with the proper arguments.
    116      */
    117     @Test
    118     public void testOnGetSessionIsCalled() throws InterruptedException {
    119         final List<ControllerInfo> controllerInfoList = new ArrayList<>();
    120         final CountDownLatch latch = new CountDownLatch(1);
    121         StubMediaSession2Service.setTestInjector(new StubMediaSession2Service.TestInjector() {
    122             @Override
    123             MediaSession2 onGetSession(ControllerInfo controllerInfo) {
    124                 controllerInfoList.add(controllerInfo);
    125                 latch.countDown();
    126                 return null;
    127             }
    128         });
    129         Bundle testHints = new Bundle();
    130         testHints.putString("test_key", "test_value");
    131         MediaController2 controller = new MediaController2.Builder(mContext, mToken)
    132                 .setConnectionHints(testHints)
    133                 .setControllerCallback(sHandlerExecutor,
    134                         new MediaController2.ControllerCallback() {})
    135                 .build();
    136         mControllers.add(controller);
    137 
    138         // onGetSession() should be called.
    139         assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
    140         assertEquals(controllerInfoList.get(0).getPackageName(), mContext.getPackageName());
    141         assertTrue(TestUtils.equals(controllerInfoList.get(0).getConnectionHints(), testHints));
    142     }
    143 
    144     /**
    145      * Tests whether the controller is connected to the session which is returned from
    146      * {@link MediaSession2Service#onGetSession(ControllerInfo)}.
    147      * Also checks whether the connection hints are properly passed to
    148      * {@link MediaSession2.SessionCallback#onConnect(MediaSession2, ControllerInfo)}.
    149      */
    150     @Test
    151     public void testOnGetSession_returnsSession() throws InterruptedException {
    152         final List<ControllerInfo> controllerInfoList = new ArrayList<>();
    153         final CountDownLatch latch = new CountDownLatch(1);
    154 
    155         try (MediaSession2 testSession = new MediaSession2.Builder(mContext)
    156                 .setId("testOnGetSession_returnsSession")
    157                 .setSessionCallback(sHandlerExecutor, new SessionCallback() {
    158                     @Override
    159                     public Session2CommandGroup onConnect(MediaSession2 session,
    160                             ControllerInfo controller) {
    161                         if (controller.getUid() == Process.myUid()) {
    162                             controllerInfoList.add(controller);
    163                             latch.countDown();
    164                             return new Session2CommandGroup.Builder().build();
    165                         }
    166                         return null;
    167                     }
    168                 }).build()) {
    169 
    170             StubMediaSession2Service.setTestInjector(new StubMediaSession2Service.TestInjector() {
    171                 @Override
    172                 MediaSession2 onGetSession(ControllerInfo controllerInfo) {
    173                     // Add dummy call for preventing this from being missed by CTS coverage.
    174                     super.onGetSession(controllerInfo);
    175                     return testSession;
    176                 }
    177             });
    178 
    179             Bundle testHints = new Bundle();
    180             testHints.putString("test_key", "test_value");
    181             MediaController2 controller = new MediaController2.Builder(mContext, mToken)
    182                     .setConnectionHints(testHints)
    183                     .setControllerCallback(sHandlerExecutor,
    184                             new MediaController2.ControllerCallback() {})
    185                     .build();
    186             mControllers.add(controller);
    187 
    188             // MediaSession2.SessionCallback#onConnect() should be called.
    189             assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
    190             assertEquals(controllerInfoList.get(0).getPackageName(), mContext.getPackageName());
    191             assertTrue(TestUtils.equals(controllerInfoList.get(0).getConnectionHints(), testHints));
    192 
    193             // The controller should be connected to the right session.
    194             assertNotEquals(mToken, controller.getConnectedToken());
    195             assertEquals(testSession.getToken(), controller.getConnectedToken());
    196         }
    197     }
    198 
    199     /**
    200      * Tests whether {@link MediaSession2Service#onGetSession(ControllerInfo)}
    201      * can return different sessions for different controllers.
    202      */
    203     @Test
    204     public void testOnGetSession_returnsDifferentSessions() throws InterruptedException {
    205         final List<Session2Token> tokens = new ArrayList<>();
    206         StubMediaSession2Service.setTestInjector(new StubMediaSession2Service.TestInjector() {
    207             @Override
    208             MediaSession2 onGetSession(ControllerInfo controllerInfo) {
    209                 MediaSession2 session = createMediaSession2(
    210                         "testOnGetSession_returnsDifferentSessions" + System.currentTimeMillis());
    211                 tokens.add(session.getToken());
    212                 return session;
    213             }
    214         });
    215 
    216         MediaController2 controller1 = createConnectedController(mToken);
    217         MediaController2 controller2 = createConnectedController(mToken);
    218 
    219         assertNotEquals(mToken, controller1.getConnectedToken());
    220         assertNotEquals(mToken, controller2.getConnectedToken());
    221 
    222         assertNotEquals(controller1.getConnectedToken(),
    223                 controller2.getConnectedToken());
    224         assertEquals(2, tokens.size());
    225         assertEquals(tokens.get(0), controller1.getConnectedToken());
    226         assertEquals(tokens.get(1), controller2.getConnectedToken());
    227     }
    228 
    229     /**
    230      * Tests whether {@link MediaSession2Service#onGetSession(ControllerInfo)}
    231      * can reject incoming connection by returning null.
    232      */
    233     @Test
    234     public void testOnGetSession_rejectsConnection() throws InterruptedException {
    235         StubMediaSession2Service.setTestInjector(new StubMediaSession2Service.TestInjector() {
    236             @Override
    237             MediaSession2 onGetSession(ControllerInfo controllerInfo) {
    238                 return null;
    239             }
    240         });
    241         final CountDownLatch latch = new CountDownLatch(1);
    242         MediaController2 controller = new MediaController2.Builder(mContext, mToken)
    243                 .setControllerCallback(sHandlerExecutor, new MediaController2.ControllerCallback() {
    244                     @Override
    245                     public void onDisconnected(MediaController2 controller) {
    246                         latch.countDown();
    247                     }
    248                 })
    249                 .build();
    250 
    251         // MediaController2.ControllerCallback#onDisconnected() should be called.
    252         assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
    253         assertNull(controller.getConnectedToken());
    254     }
    255 
    256     @Test
    257     public void testAllControllersDisconnected_oneSession() throws InterruptedException {
    258         final CountDownLatch latch = new CountDownLatch(1);
    259         final MediaSession2 testSession =
    260                 createMediaSession2("testAllControllersDisconnected_oneSession");
    261 
    262         StubMediaSession2Service.setTestInjector(new StubMediaSession2Service.TestInjector() {
    263             @Override
    264             MediaSession2 onGetSession(ControllerInfo controllerInfo) {
    265                 return testSession;
    266             }
    267 
    268             @Override
    269             void onServiceDestroyed() {
    270                 latch.countDown();
    271             }
    272         });
    273         MediaController2 controller1 = createConnectedController(mToken);
    274         MediaController2 controller2 = createConnectedController(mToken);
    275 
    276         controller1.close();
    277         assertFalse(latch.await(WAIT_TIME_FOR_NO_RESPONSE_MS, TimeUnit.MILLISECONDS));
    278 
    279         // Service should be closed only when all controllers are closed.
    280         controller2.close();
    281         assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
    282     }
    283 
    284     @Test
    285     public void testAllControllersDisconnected_multipleSessions() throws InterruptedException {
    286         final CountDownLatch latch = new CountDownLatch(1);
    287         StubMediaSession2Service.setTestInjector(new StubMediaSession2Service.TestInjector() {
    288             @Override
    289             MediaSession2 onGetSession(ControllerInfo controllerInfo) {
    290                 return createMediaSession2("testAllControllersDisconnected_multipleSession"
    291                         + System.currentTimeMillis());
    292             }
    293 
    294             @Override
    295             void onServiceDestroyed() {
    296                 latch.countDown();
    297             }
    298         });
    299 
    300         MediaController2 controller1 = createConnectedController(mToken);
    301         MediaController2 controller2 = createConnectedController(mToken);
    302 
    303         controller1.close();
    304         assertFalse(latch.await(WAIT_TIME_FOR_NO_RESPONSE_MS, TimeUnit.MILLISECONDS));
    305 
    306         // Service should be closed only when all controllers are closed.
    307         controller2.close();
    308         assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
    309     }
    310 
    311     @Test
    312     public void testGetSessions() throws InterruptedException {
    313         MediaController2 controller = createConnectedController(mToken);
    314         MediaSession2Service service = StubMediaSession2Service.getInstance();
    315         try (MediaSession2 session = new MediaSession2.Builder(mContext)
    316                 .setId("testGetSessions")
    317                 .setSessionCallback(sHandlerExecutor, new SessionCallback())
    318                 .build()) {
    319             service.addSession(session);
    320             List<MediaSession2> sessions = service.getSessions();
    321             assertTrue(sessions.contains(session));
    322             assertEquals(2, sessions.size());
    323 
    324             service.removeSession(session);
    325             sessions = service.getSessions();
    326             assertFalse(sessions.contains(session));
    327         }
    328     }
    329 
    330     @Test
    331     public void testAddSessions_removedWhenClose() throws InterruptedException {
    332         MediaController2 controller = createConnectedController(mToken);
    333         MediaSession2Service service = StubMediaSession2Service.getInstance();
    334         try (MediaSession2 session = new MediaSession2.Builder(mContext)
    335                 .setId("testAddSessions_removedWhenClose")
    336                 .setSessionCallback(sHandlerExecutor, new SessionCallback())
    337                 .build()) {
    338             service.addSession(session);
    339             List<MediaSession2> sessions = service.getSessions();
    340             assertTrue(sessions.contains(session));
    341             assertEquals(2, sessions.size());
    342 
    343             session.close();
    344             sessions = service.getSessions();
    345             assertFalse(sessions.contains(session));
    346         }
    347     }
    348 
    349     @Test
    350     public void testOnUpdateNotification() throws InterruptedException {
    351         MediaController2 controller = createConnectedController(mToken);
    352         MediaSession2Service service = StubMediaSession2Service.getInstance();
    353         MediaSession2 testSession = service.getSessions().get(0);
    354         CountDownLatch latch = new CountDownLatch(2);
    355 
    356         StubMediaSession2Service.setTestInjector(
    357                 new StubMediaSession2Service.TestInjector() {
    358                     @Override
    359                     MediaSession2Service.MediaNotification onUpdateNotification(
    360                             MediaSession2 session) {
    361                         assertEquals(testSession, session);
    362                         switch ((int) latch.getCount()) {
    363                             case 2:
    364 
    365                                 break;
    366                             case 1:
    367                         }
    368                         latch.countDown();
    369                         return super.onUpdateNotification(session);
    370                     }
    371                 });
    372 
    373         testSession.setPlaybackActive(true);
    374         testSession.setPlaybackActive(false);
    375         assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
    376 
    377         // Add dummy call for preventing this from being missed by CTS coverage.
    378         if (StubMediaSession2Service.getInstance() != null) {
    379             ((MediaSession2Service) StubMediaSession2Service.getInstance())
    380                     .onUpdateNotification(null);
    381         }
    382     }
    383 
    384     @Test
    385     public void testOnBind() throws Exception {
    386         MediaController2 controller1 = createConnectedController(mToken);
    387         MediaSession2Service service = StubMediaSession2Service.getInstance();
    388 
    389         Intent serviceIntent = new Intent(MediaSession2Service.SERVICE_INTERFACE);
    390         assertNotNull(service.onBind(serviceIntent));
    391 
    392         Intent wrongIntent = new Intent("wrongIntent");
    393         assertNull(service.onBind(wrongIntent));
    394     }
    395 
    396     @Test
    397     public void testMediaNotification() {
    398         final int testId = 1001;
    399         final String testChannelId = "channelId";
    400         final Notification testNotification =
    401                 new Notification.Builder(mContext, testChannelId).build();
    402 
    403         MediaSession2Service.MediaNotification notification =
    404                 new MediaSession2Service.MediaNotification(testId, testNotification);
    405         assertEquals(testId, notification.getNotificationId());
    406         assertSame(testNotification, notification.getNotification());
    407     }
    408 
    409     private MediaController2 createConnectedController(Session2Token token)
    410             throws InterruptedException {
    411         CountDownLatch latch = new CountDownLatch(1);
    412         MediaController2 controller = new MediaController2.Builder(mContext, token)
    413                 .setControllerCallback(sHandlerExecutor, new MediaController2.ControllerCallback() {
    414                     @Override
    415                     public void onConnected(MediaController2 controller,
    416                             Session2CommandGroup allowedCommands) {
    417                         latch.countDown();
    418                         super.onConnected(controller, allowedCommands);
    419                     }
    420                 }).build();
    421 
    422         mControllers.add(controller);
    423         assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
    424         return controller;
    425     }
    426 
    427     private MediaSession2 createMediaSession2(String id) {
    428         return new MediaSession2.Builder(mContext)
    429                 .setId(id)
    430                 .setSessionCallback(sHandlerExecutor, new SessionCallback())
    431                 .build();
    432     }
    433 
    434     private static class SessionCallback extends MediaSession2.SessionCallback {
    435         @Override
    436         public Session2CommandGroup onConnect(MediaSession2 session,
    437                 ControllerInfo controller) {
    438             if (controller.getUid() == Process.myUid()) {
    439                 return new Session2CommandGroup.Builder().build();
    440             }
    441             return null;
    442         }
    443     }
    444 }
    445