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 setUpThread()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 cleanUpThread()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 setUp()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 cleanUp()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 testOnGetSessionIsCalled()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 testOnGetSession_returnsSession()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 fake 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 testOnGetSession_returnsDifferentSessions()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 testOnGetSession_rejectsConnection()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 testAllControllersDisconnected_oneSession()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 testAllControllersDisconnected_multipleSessions()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 testGetSessions()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 testAddSessions_removedWhenClose()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 testOnUpdateNotification()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 fake 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 testOnBind()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 testMediaNotification()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 createConnectedController(Session2Token token)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 createMediaSession2(String id)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 onConnect(MediaSession2 session, ControllerInfo controller)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