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.assertNotNull;
     21 import static org.junit.Assert.assertNull;
     22 import static org.junit.Assert.assertTrue;
     23 import static org.junit.Assert.fail;
     24 
     25 import android.content.ComponentName;
     26 import android.content.Context;
     27 import android.media.MediaController2;
     28 import android.media.MediaMetadata;
     29 import android.media.MediaSession2;
     30 import android.media.Session2Command;
     31 import android.media.Session2CommandGroup;
     32 import android.media.Session2Token;
     33 import android.os.Bundle;
     34 import android.os.Handler;
     35 import android.os.HandlerThread;
     36 import android.os.Process;
     37 
     38 import androidx.test.InstrumentationRegistry;
     39 import androidx.test.filters.SmallTest;
     40 import androidx.test.runner.AndroidJUnit4;
     41 
     42 import org.junit.After;
     43 import org.junit.AfterClass;
     44 import org.junit.Before;
     45 import org.junit.BeforeClass;
     46 import org.junit.Test;
     47 import org.junit.runner.RunWith;
     48 
     49 import java.util.ArrayList;
     50 import java.util.List;
     51 import java.util.concurrent.CountDownLatch;
     52 import java.util.concurrent.Executor;
     53 import java.util.concurrent.Executors;
     54 import java.util.concurrent.TimeUnit;
     55 
     56 /**
     57  * Tests {@link android.media.MediaController2}.
     58  */
     59 @RunWith(AndroidJUnit4.class)
     60 @SmallTest
     61 public class MediaController2Test {
     62     private static final long WAIT_TIME_MS = 100L;
     63 
     64     static final Object sTestLock = new Object();
     65 
     66     static final int ALLOWED_COMMAND_CODE = 100;
     67     static final Session2CommandGroup SESSION_ALLOWED_COMMANDS = new Session2CommandGroup.Builder()
     68             .addCommand(new Session2Command(ALLOWED_COMMAND_CODE)).build();
     69     static final int SESSION_RESULT_CODE = 101;
     70     static final String SESSION_RESULT_KEY = "test_result_key";
     71     static final String SESSION_RESULT_VALUE = "test_result_value";
     72     static final Session2Command.Result SESSION_COMMAND_RESULT;
     73 
     74     private static final String TEST_KEY = "test_key";
     75     private static final String TEST_VALUE = "test_value";
     76 
     77     static {
     78         Bundle resultData = new Bundle();
     79         resultData.putString(SESSION_RESULT_KEY, SESSION_RESULT_VALUE);
     80         SESSION_COMMAND_RESULT = new Session2Command.Result(SESSION_RESULT_CODE, resultData);
     81     }
     82 
     83     static Handler sHandler;
     84     static Executor sHandlerExecutor;
     85 
     86     private Context mContext;
     87     private Bundle mExtras;
     88     private MediaSession2 mSession;
     89     private Session2Callback mSessionCallback;
     90 
     91     @BeforeClass
     92     public static void setUpThread() {
     93         synchronized (MediaSession2Test.class) {
     94             if (sHandler != null) {
     95                 return;
     96             }
     97             HandlerThread handlerThread = new HandlerThread("MediaSessionTestBase");
     98             handlerThread.start();
     99             sHandler = new Handler(handlerThread.getLooper());
    100             sHandlerExecutor = (runnable) -> {
    101                 Handler handler;
    102                 synchronized (MediaSession2Test.class) {
    103                     handler = sHandler;
    104                 }
    105                 if (handler != null) {
    106                     handler.post(() -> {
    107                         synchronized (sTestLock) {
    108                             runnable.run();
    109                         }
    110                     });
    111                 }
    112             };
    113         }
    114     }
    115 
    116     @AfterClass
    117     public static void cleanUpThread() {
    118         synchronized (MediaSession2Test.class) {
    119             if (sHandler == null) {
    120                 return;
    121             }
    122             sHandler.getLooper().quitSafely();
    123             sHandler = null;
    124             sHandlerExecutor = null;
    125         }
    126     }
    127 
    128     @Before
    129     public void setUp() throws Exception {
    130         mContext = InstrumentationRegistry.getContext();
    131         mSessionCallback = new Session2Callback();
    132         mExtras = new Bundle();
    133         mExtras.putString(TEST_KEY, TEST_VALUE);
    134         mSession = new MediaSession2.Builder(mContext)
    135                 .setSessionCallback(sHandlerExecutor, mSessionCallback)
    136                 .setExtras(mExtras)
    137                 .build();
    138     }
    139 
    140     @After
    141     public void cleanUp() {
    142         if (mSession != null) {
    143             mSession.close();
    144             mSession = null;
    145         }
    146     }
    147 
    148     @Test
    149     public void testBuilder_withIllegalArguments() {
    150         final Session2Token token = new Session2Token(
    151                 mContext, new ComponentName(mContext, this.getClass()));
    152 
    153         try {
    154             MediaController2.Builder builder = new MediaController2.Builder(null, token);
    155             fail("null context shouldn't be accepted!");
    156         } catch (IllegalArgumentException e) {
    157             // Expected
    158         }
    159 
    160         try {
    161             MediaController2.Builder builder = new MediaController2.Builder(mContext, null);
    162             fail("null token shouldn't be accepted!");
    163         } catch (IllegalArgumentException e) {
    164             // Expected
    165         }
    166 
    167         try {
    168             MediaController2.Builder builder = new MediaController2.Builder(mContext, token);
    169             builder.setConnectionHints(null);
    170             fail("null connectionHints shouldn't be accepted!");
    171         } catch (IllegalArgumentException e) {
    172             // Expected
    173         }
    174 
    175         try {
    176             MediaController2.Builder builder = new MediaController2.Builder(mContext, token);
    177             builder.setControllerCallback(null, new MediaController2.ControllerCallback() {});
    178             fail("null Executor shouldn't be accepted!");
    179         } catch (IllegalArgumentException e) {
    180             // Expected
    181         }
    182 
    183         try {
    184             MediaController2.Builder builder = new MediaController2.Builder(mContext, token);
    185             builder.setControllerCallback(Executors.newSingleThreadExecutor(), null);
    186             fail("null ControllerCallback shouldn't be accepted!");
    187         } catch (IllegalArgumentException e) {
    188             // Expected
    189         }
    190     }
    191 
    192     @Test
    193     public void testBuilder_setConnectionHints_withFrameworkParcelable() throws Exception {
    194         final List<MediaSession2.ControllerInfo> controllerInfoList = new ArrayList<>();
    195         final CountDownLatch latch = new CountDownLatch(1);
    196 
    197         try (MediaSession2 session = new MediaSession2.Builder(mContext)
    198                 .setId("testBuilder_setConnectionHints_withFrameworkParcelable")
    199                 .setSessionCallback(sHandlerExecutor, new MediaSession2.SessionCallback() {
    200                     @Override
    201                     public Session2CommandGroup onConnect(MediaSession2 session,
    202                             MediaSession2.ControllerInfo controller) {
    203                         if (controller.getUid() == Process.myUid()) {
    204                             controllerInfoList.add(controller);
    205                             latch.countDown();
    206                             return new Session2CommandGroup.Builder().build();
    207                         }
    208                         return null;
    209                     }
    210                 })
    211                 .build()) {
    212 
    213             final Session2Token frameworkParcelable = new Session2Token(
    214                     mContext, new ComponentName(mContext, this.getClass()));
    215             final String testKey = "test_key";
    216 
    217             Bundle connectionHints = new Bundle();
    218             connectionHints.putParcelable(testKey, frameworkParcelable);
    219 
    220             MediaController2 controller = new MediaController2.Builder(mContext, session.getToken())
    221                     .setConnectionHints(connectionHints)
    222                     .build();
    223             assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
    224 
    225             Bundle connectionHintsOut = controllerInfoList.get(0).getConnectionHints();
    226             assertTrue(connectionHintsOut.containsKey(testKey));
    227             assertEquals(frameworkParcelable, connectionHintsOut.getParcelable(testKey));
    228         }
    229     }
    230 
    231     @Test
    232     public void testBuilder_setConnectionHints_withCustomParcelable() {
    233         final Session2Token token = new Session2Token(
    234                 mContext, new ComponentName(mContext, this.getClass()));
    235         final String testKey = "test_key";
    236         final MediaSession2Test.CustomParcelable customParcelable =
    237                 new MediaSession2Test.CustomParcelable(1);
    238 
    239         Bundle connectionHints = new Bundle();
    240         connectionHints.putParcelable(testKey, customParcelable);
    241 
    242         try (MediaController2 controller = new MediaController2.Builder(mContext, token)
    243                 .setConnectionHints(connectionHints)
    244                 .build()) {
    245             fail("Custom Parcelables shouldn't be accepted!");
    246         } catch (IllegalArgumentException e) {
    247             // Expected
    248         }
    249     }
    250 
    251     @Test
    252     public void testCreatingControllerWithoutCallback() throws Exception {
    253         try (MediaController2 controller =
    254                      new MediaController2.Builder(mContext, mSession.getToken()).build()) {
    255             assertTrue(mSessionCallback.mOnConnectedLatch.await(
    256                     WAIT_TIME_MS, TimeUnit.MILLISECONDS));
    257             assertEquals(mContext.getPackageName(),
    258                     mSessionCallback.mControllerInfo.getPackageName());
    259         }
    260     }
    261 
    262     @Test
    263     public void testGetConnectedToken() {
    264         Controller2Callback controllerCallback = new Controller2Callback();
    265         try (MediaController2 controller =
    266                      new MediaController2.Builder(mContext, mSession.getToken())
    267                              .setControllerCallback(sHandlerExecutor, controllerCallback)
    268                              .build()) {
    269             assertTrue(controllerCallback.awaitOnConnected(WAIT_TIME_MS));
    270             assertEquals(controller, controllerCallback.mController);
    271             assertEquals(mSession.getToken(), controller.getConnectedToken());
    272 
    273             Bundle extrasFromConnectedSessionToken =
    274                     controller.getConnectedToken().getExtras();
    275             assertNotNull(extrasFromConnectedSessionToken);
    276             assertEquals(TEST_VALUE, extrasFromConnectedSessionToken.getString(TEST_KEY));
    277         } finally {
    278             assertTrue(controllerCallback.awaitOnDisconnected(WAIT_TIME_MS));
    279             assertNull(controllerCallback.mController.getConnectedToken());
    280         }
    281     }
    282 
    283     @Test
    284     public void testCallback_onConnected_onDisconnected() {
    285         Controller2Callback controllerCallback = new Controller2Callback();
    286         try (MediaController2 controller =
    287                      new MediaController2.Builder(mContext, mSession.getToken())
    288                              .setControllerCallback(sHandlerExecutor, controllerCallback)
    289                              .build()) {
    290             assertTrue(controllerCallback.awaitOnConnected(WAIT_TIME_MS));
    291             assertEquals(controller, controllerCallback.mController);
    292             assertTrue(controllerCallback.mAllowedCommands.hasCommand(ALLOWED_COMMAND_CODE));
    293         } finally {
    294             assertTrue(controllerCallback.awaitOnDisconnected(WAIT_TIME_MS));
    295         }
    296     }
    297 
    298     @Test
    299     public void testCallback_onSessionCommand() {
    300         Controller2Callback controllerCallback = new Controller2Callback();
    301         try (MediaController2 controller =
    302                      new MediaController2.Builder(mContext, mSession.getToken())
    303                              .setControllerCallback(sHandlerExecutor, controllerCallback)
    304                              .build()) {
    305             assertTrue(controllerCallback.awaitOnConnected(WAIT_TIME_MS));
    306 
    307             String commandStr = "test_command";
    308             String commandExtraKey = "test_extra_key";
    309             String commandExtraValue = "test_extra_value";
    310             Bundle commandExtra = new Bundle();
    311             commandExtra.putString(commandExtraKey, commandExtraValue);
    312             Session2Command command = new Session2Command(commandStr, commandExtra);
    313 
    314             String commandArgKey = "test_arg_key";
    315             String commandArgValue = "test_arg_value";
    316             Bundle commandArg = new Bundle();
    317             commandArg.putString(commandArgKey, commandArgValue);
    318             mSession.sendSessionCommand(mSessionCallback.mControllerInfo, command, commandArg);
    319 
    320             assertTrue(controllerCallback.awaitOnSessionCommand(WAIT_TIME_MS));
    321             assertEquals(controller, controllerCallback.mController);
    322             assertEquals(commandStr, controllerCallback.mCommand.getCustomAction());
    323             assertEquals(commandExtraValue,
    324                     controllerCallback.mCommand.getCustomExtras().getString(commandExtraKey));
    325             assertEquals(commandArgValue, controllerCallback.mCommandArgs.getString(commandArgKey));
    326         } finally {
    327             assertTrue(controllerCallback.awaitOnDisconnected(WAIT_TIME_MS));
    328         }
    329     }
    330 
    331     @Test
    332     public void testCallback_onCommandResult() {
    333         Controller2Callback controllerCallback = new Controller2Callback();
    334         try (MediaController2 controller =
    335                      new MediaController2.Builder(mContext, mSession.getToken())
    336                              .setControllerCallback(sHandlerExecutor, controllerCallback)
    337                              .build()) {
    338             assertTrue(controllerCallback.awaitOnConnected(WAIT_TIME_MS));
    339 
    340             String commandStr = "test_command";
    341             String commandExtraKey = "test_extra_key";
    342             String commandExtraValue = "test_extra_value";
    343             Bundle commandExtra = new Bundle();
    344             commandExtra.putString(commandExtraKey, commandExtraValue);
    345             Session2Command command = new Session2Command(commandStr, commandExtra);
    346 
    347             String commandArgKey = "test_arg_key";
    348             String commandArgValue = "test_arg_value";
    349             Bundle commandArg = new Bundle();
    350             commandArg.putString(commandArgKey, commandArgValue);
    351             controller.sendSessionCommand(command, commandArg);
    352 
    353             assertTrue(controllerCallback.awaitOnCommandResult(WAIT_TIME_MS));
    354             assertEquals(controller, controllerCallback.mController);
    355             assertEquals(SESSION_RESULT_CODE, controllerCallback.mCommandResult.getResultCode());
    356             assertEquals(SESSION_RESULT_VALUE,
    357                     controllerCallback.mCommandResult.getResultData()
    358                             .getString(SESSION_RESULT_KEY));
    359         } finally {
    360             assertTrue(controllerCallback.awaitOnDisconnected(WAIT_TIME_MS));
    361         }
    362     }
    363 
    364     @Test
    365     public void testCancelSessionCommand() {
    366         Controller2Callback controllerCallback = new Controller2Callback();
    367         try (MediaController2 controller =
    368                      new MediaController2.Builder(mContext, mSession.getToken())
    369                              .setControllerCallback(sHandlerExecutor, controllerCallback)
    370                              .build()) {
    371             assertTrue(controllerCallback.awaitOnConnected(WAIT_TIME_MS));
    372 
    373             String commandStr = "test_command_";
    374             String commandExtraKey = "test_extra_key_";
    375             String commandExtraValue = "test_extra_value_";
    376             Bundle commandExtra = new Bundle();
    377             commandExtra.putString(commandExtraKey, commandExtraValue);
    378             Session2Command command = new Session2Command(commandStr, commandExtra);
    379 
    380             String commandArgKey = "test_arg_key_";
    381             String commandArgValue = "test_arg_value_";
    382             Bundle commandArg = new Bundle();
    383             commandArg.putString(commandArgKey, commandArgValue);
    384             synchronized (sTestLock) {
    385                 Object token = controller.sendSessionCommand(command, commandArg);
    386                 controller.cancelSessionCommand(token);
    387             }
    388             assertTrue(controllerCallback.awaitOnCommandResult(WAIT_TIME_MS));
    389             assertEquals(Session2Command.Result.RESULT_INFO_SKIPPED,
    390                     controllerCallback.mCommandResult.getResultCode());
    391         } finally {
    392             assertTrue(controllerCallback.awaitOnDisconnected(WAIT_TIME_MS));
    393         }
    394     }
    395 
    396     class Session2Callback extends MediaSession2.SessionCallback {
    397         MediaSession2.ControllerInfo mControllerInfo;
    398         CountDownLatch mOnConnectedLatch = new CountDownLatch(1);
    399 
    400         @Override
    401         public Session2CommandGroup onConnect(MediaSession2 session,
    402                 MediaSession2.ControllerInfo controller) {
    403             if (controller.getUid() != Process.myUid()) {
    404                 return null;
    405             }
    406             mControllerInfo = controller;
    407             mOnConnectedLatch.countDown();
    408             return SESSION_ALLOWED_COMMANDS;
    409         }
    410 
    411         @Override
    412         public Session2Command.Result onSessionCommand(MediaSession2 session,
    413                 MediaSession2.ControllerInfo controller, Session2Command command, Bundle args) {
    414             return SESSION_COMMAND_RESULT;
    415         }
    416     }
    417 
    418     class Controller2Callback extends MediaController2.ControllerCallback {
    419         CountDownLatch mOnConnectedLatch = new CountDownLatch(1);
    420         CountDownLatch mOnDisconnectedLatch = new CountDownLatch(1);
    421         private CountDownLatch mOnSessionCommandLatch = new CountDownLatch(1);
    422         private CountDownLatch mOnCommandResultLatch = new CountDownLatch(1);
    423 
    424         MediaController2 mController;
    425         Session2Command mCommand;
    426         Session2CommandGroup mAllowedCommands;
    427         Bundle mCommandArgs;
    428         Session2Command.Result mCommandResult;
    429 
    430         public boolean await(long waitMs) {
    431             try {
    432                 return mOnSessionCommandLatch.await(waitMs, TimeUnit.MILLISECONDS);
    433             } catch (InterruptedException e) {
    434                 return false;
    435             }
    436         }
    437 
    438         @Override
    439         public void onConnected(MediaController2 controller, Session2CommandGroup allowedCommands) {
    440             super.onConnected(controller, allowedCommands);
    441             mController = controller;
    442             mAllowedCommands = allowedCommands;
    443             mOnConnectedLatch.countDown();
    444         }
    445 
    446         @Override
    447         public void onDisconnected(MediaController2 controller) {
    448             super.onDisconnected(controller);
    449             mController = controller;
    450             mOnDisconnectedLatch.countDown();
    451         }
    452 
    453         @Override
    454         public Session2Command.Result onSessionCommand(MediaController2 controller,
    455                 Session2Command command, Bundle args) {
    456             super.onSessionCommand(controller, command, args);
    457             mController = controller;
    458             mCommand = command;
    459             mCommandArgs = args;
    460             mOnSessionCommandLatch.countDown();
    461             return SESSION_COMMAND_RESULT;
    462         }
    463 
    464         @Override
    465         public void onCommandResult(MediaController2 controller, Object token,
    466                 Session2Command command, Session2Command.Result result) {
    467             super.onCommandResult(controller, token, command, result);
    468             mController = controller;
    469             mCommand = command;
    470             mCommandResult = result;
    471             mOnCommandResultLatch.countDown();
    472         }
    473 
    474         @Override
    475         public void onPlaybackActiveChanged(MediaController2 controller, boolean playbackActive) {
    476             super.onPlaybackActiveChanged(controller, playbackActive);
    477         }
    478 
    479         public boolean awaitOnConnected(long waitTimeMs) {
    480             try {
    481                 return mOnConnectedLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
    482             } catch (InterruptedException e) {
    483                 return false;
    484             }
    485         }
    486 
    487         public boolean awaitOnDisconnected(long waitTimeMs) {
    488             try {
    489                 return mOnDisconnectedLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
    490             } catch (InterruptedException e) {
    491                 return false;
    492             }
    493         }
    494 
    495         public boolean awaitOnSessionCommand(long waitTimeMs) {
    496             try {
    497                 return mOnSessionCommandLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
    498             } catch (InterruptedException e) {
    499                 return false;
    500             }
    501         }
    502 
    503         public boolean awaitOnCommandResult(long waitTimeMs) {
    504             try {
    505                 return mOnCommandResultLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
    506             } catch (InterruptedException e) {
    507                 return false;
    508             }
    509         }
    510     }
    511 }
    512