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 17 package android.media.tv.cts; 18 19 import android.app.Activity; 20 import android.app.ActivityManager; 21 import android.app.ActivityManager.RunningAppProcessInfo; 22 import android.app.Instrumentation; 23 import android.content.ComponentName; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.pm.PackageManager; 27 import android.content.pm.ResolveInfo; 28 import android.database.Cursor; 29 import android.graphics.SurfaceTexture; 30 import android.media.AudioDeviceInfo; 31 import android.media.AudioFormat; 32 import android.media.AudioManager; 33 import android.media.tv.TunedInfo; 34 import android.media.tv.TvContentRating; 35 import android.media.tv.TvContract; 36 import android.media.tv.TvInputHardwareInfo; 37 import android.media.tv.TvInputInfo; 38 import android.media.tv.TvInputManager; 39 import android.media.tv.TvInputManager.Hardware; 40 import android.media.tv.TvInputManager.HardwareCallback; 41 import android.media.tv.TvInputManager.Session; 42 import android.media.tv.TvInputService; 43 import android.media.tv.TvStreamConfig; 44 import android.media.tv.TvView; 45 import android.media.tv.cts.TvViewTest.MockCallback; 46 import android.media.tv.tunerresourcemanager.TunerResourceManager; 47 import android.net.Uri; 48 import android.os.Binder; 49 import android.os.Handler; 50 import android.os.IBinder; 51 import android.os.Looper; 52 import android.test.ActivityInstrumentationTestCase2; 53 import android.tv.cts.R; 54 import android.util.Log; 55 import android.view.Surface; 56 57 import androidx.test.InstrumentationRegistry; 58 59 import com.android.compatibility.common.util.PollingCheck; 60 61 import org.xmlpull.v1.XmlPullParserException; 62 63 import java.io.IOException; 64 import java.util.ArrayList; 65 import java.util.Arrays; 66 import java.util.HashMap; 67 import java.util.List; 68 import java.util.Map; 69 import java.util.concurrent.Executor; 70 71 /** 72 * Test for {@link android.media.tv.TvInputManager}. 73 */ 74 public class TvInputManagerTest extends ActivityInstrumentationTestCase2<TvViewStubActivity> { 75 private static final String TAG = "TvInputManagerTest"; 76 77 /** The maximum time to wait for an operation. */ 78 private static final long TIME_OUT_MS = 15000L; 79 private static final long LONG_TIME_OUT_MS = 30000L; 80 private static final int PRIORITY_HINT_USE_CASE_TYPE_INVALID = 1000; 81 82 private static final int DUMMY_DEVICE_ID = Integer.MAX_VALUE; 83 private static final String[] VALID_TV_INPUT_SERVICES = { 84 StubTunerTvInputService.class.getName() 85 }; 86 private static final String[] INVALID_TV_INPUT_SERVICES = { 87 NoMetadataTvInputService.class.getName(), NoPermissionTvInputService.class.getName() 88 }; 89 private static final String EXTENSION_INTERFACE_NAME_WITHOUT_PERMISSION = 90 "android.media.tv.cts.TvInputManagerTest.EXTENSION_INTERFACE_NAME_WITHOUT_PERMISSION"; 91 private static final String EXTENSION_INTERFACE_NAME_WITH_PERMISSION_GRANTED = 92 "android.media.tv.cts.TvInputManagerTest" 93 + ".EXTENSION_INTERFACE_NAME_WITH_PERMISSION_GRANTED"; 94 private static final String EXTENSION_INTERFACE_NAME_WITH_PERMISSION_UNGRANTED = 95 "android.media.tv.cts.TvInputManagerTest" 96 + ".EXTENSION_INTERFACE_NAME_WITH_PERMISSION_UNGRANTED"; 97 private static final String PERMISSION_GRANTED = 98 "android.media.tv.cts.TvInputManagerTest.PERMISSION_GRANTED"; 99 private static final String PERMISSION_UNGRANTED = 100 "android.media.tv.cts.TvInputManagerTest.PERMISSION_UNGRANTED"; 101 102 private static final TvContentRating DUMMY_RATING = TvContentRating.createRating( 103 "com.android.tv", "US_TV", "US_TV_PG", "US_TV_D", "US_TV_L"); 104 105 private static final String PERMISSION_ACCESS_WATCHED_PROGRAMS = 106 "com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS"; 107 private static final String PERMISSION_WRITE_EPG_DATA = 108 "com.android.providers.tv.permission.WRITE_EPG_DATA"; 109 private static final String PERMISSION_ACCESS_TUNED_INFO = 110 "android.permission.ACCESS_TUNED_INFO"; 111 private static final String PERMISSION_TV_INPUT_HARDWARE = 112 "android.permission.TV_INPUT_HARDWARE"; 113 private static final String PERMISSION_TUNER_RESOURCE_ACCESS = 114 "android.permission.TUNER_RESOURCE_ACCESS"; 115 private static final String PERMISSION_TIS_EXTENSION_INTERFACE = 116 "android.permission.TIS_EXTENSION_INTERFACE"; 117 private static final String[] BASE_SHELL_PERMISSIONS = { 118 PERMISSION_ACCESS_WATCHED_PROGRAMS, 119 PERMISSION_WRITE_EPG_DATA, 120 PERMISSION_ACCESS_TUNED_INFO, 121 PERMISSION_TUNER_RESOURCE_ACCESS, 122 PERMISSION_TIS_EXTENSION_INTERFACE 123 }; 124 125 private String mStubId; 126 private Context mContext; 127 private TvInputManager mManager; 128 private LoggingCallback mCallback = new LoggingCallback(); 129 private TvInputInfo mStubTvInputInfo; 130 private TvView mTvView; 131 private Activity mActivity; 132 private Instrumentation mInstrumentation; 133 private TvInputInfo mStubTunerTvInputInfo; 134 private final MockCallback mMockCallback = new MockCallback(); 135 getInfoForClassName(List<TvInputInfo> list, String name)136 private static TvInputInfo getInfoForClassName(List<TvInputInfo> list, String name) { 137 for (TvInputInfo info : list) { 138 if (info.getServiceInfo().name.equals(name)) { 139 return info; 140 } 141 } 142 return null; 143 } 144 isHardwareDeviceAdded(List<TvInputHardwareInfo> list, int deviceId)145 private static boolean isHardwareDeviceAdded(List<TvInputHardwareInfo> list, int deviceId) { 146 if (list != null) { 147 for (TvInputHardwareInfo info : list) { 148 if (info.getDeviceId() == deviceId) { 149 return true; 150 } 151 } 152 } 153 return false; 154 } 155 prepareStubHardwareTvInputService()156 private String prepareStubHardwareTvInputService() { 157 String[] newPermissions = Arrays.copyOf( 158 BASE_SHELL_PERMISSIONS, BASE_SHELL_PERMISSIONS.length + 1); 159 newPermissions[BASE_SHELL_PERMISSIONS.length] = PERMISSION_TV_INPUT_HARDWARE; 160 InstrumentationRegistry.getInstrumentation().getUiAutomation() 161 .adoptShellPermissionIdentity(newPermissions); 162 163 // Use the test api to add an HDMI hardware device 164 mManager.addHardwareDevice(DUMMY_DEVICE_ID); 165 assertTrue(isHardwareDeviceAdded(mManager.getHardwareList(), DUMMY_DEVICE_ID)); 166 167 PackageManager pm = getActivity().getPackageManager(); 168 ComponentName component = 169 new ComponentName(getActivity(), StubHardwareTvInputService.class); 170 pm.setComponentEnabledSetting(component, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 171 PackageManager.DONT_KILL_APP); 172 new PollingCheck(LONG_TIME_OUT_MS) { 173 @Override 174 protected boolean check() { 175 return null != getInfoForClassName( 176 mManager.getTvInputList(), StubHardwareTvInputService.class.getName()); 177 } 178 }.run(); 179 180 TvInputInfo info = getInfoForClassName( 181 mManager.getTvInputList(), StubHardwareTvInputService.class.getName()); 182 assertNotNull(info); 183 return info.getId(); 184 } 185 cleanupStubHardwareTvInputService()186 private void cleanupStubHardwareTvInputService() { 187 // Restore the base shell permissions 188 InstrumentationRegistry.getInstrumentation().getUiAutomation() 189 .adoptShellPermissionIdentity(BASE_SHELL_PERMISSIONS); 190 191 PackageManager pm = getActivity().getPackageManager(); 192 ComponentName component = 193 new ComponentName(getActivity(), StubHardwareTvInputService.class); 194 pm.setComponentEnabledSetting(component, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 195 PackageManager.DONT_KILL_APP); 196 new PollingCheck(TIME_OUT_MS) { 197 @Override 198 protected boolean check() { 199 return null == getInfoForClassName( 200 mManager.getTvInputList(), StubHardwareTvInputService.class.getName()); 201 } 202 }.run(); 203 204 mManager.removeHardwareDevice(DUMMY_DEVICE_ID); 205 } 206 TvInputManagerTest()207 public TvInputManagerTest() { 208 super(TvViewStubActivity.class); 209 } 210 211 @Override setUp()212 public void setUp() throws Exception { 213 super.setUp(); 214 mActivity = getActivity(); 215 if (!Utils.hasTvInputFramework(mActivity)) { 216 return; 217 } 218 219 InstrumentationRegistry.getInstrumentation().getUiAutomation() 220 .adoptShellPermissionIdentity(BASE_SHELL_PERMISSIONS); 221 222 mInstrumentation = getInstrumentation(); 223 mContext = mInstrumentation.getTargetContext(); 224 mTvView = findTvViewById(R.id.tvview); 225 mManager = (TvInputManager) mActivity.getSystemService(Context.TV_INPUT_SERVICE); 226 mStubId = getInfoForClassName( 227 mManager.getTvInputList(), StubTvInputService2.class.getName()).getId(); 228 mStubTvInputInfo = getInfoForClassName( 229 mManager.getTvInputList(), StubTvInputService2.class.getName()); 230 for (TvInputInfo info : mManager.getTvInputList()) { 231 if (info.getServiceInfo().name.equals(StubTunerTvInputService.class.getName())) { 232 mStubTunerTvInputInfo = info; 233 break; 234 } 235 } 236 assertNotNull(mStubTunerTvInputInfo); 237 mTvView.setCallback(mMockCallback); 238 } 239 240 @Override tearDown()241 protected void tearDown() throws Exception { 242 if (!Utils.hasTvInputFramework(getActivity())) { 243 super.tearDown(); 244 return; 245 } 246 StubTunerTvInputService.deleteChannels( 247 mActivity.getContentResolver(), mStubTunerTvInputInfo); 248 StubTunerTvInputService.clearTracks(); 249 try { 250 runTestOnUiThread(new Runnable() { 251 @Override 252 public void run() { 253 mTvView.reset(); 254 } 255 }); 256 } catch (Throwable t) { 257 throw new RuntimeException(t); 258 } 259 mInstrumentation.waitForIdleSync(); 260 261 InstrumentationRegistry.getInstrumentation().getUiAutomation() 262 .dropShellPermissionIdentity(); 263 super.tearDown(); 264 } 265 findTvViewById(int id)266 private TvView findTvViewById(int id) { 267 return (TvView) mActivity.findViewById(id); 268 } 269 tryTuneAllChannels()270 private void tryTuneAllChannels() throws Throwable { 271 StubTunerTvInputService.insertChannels( 272 mActivity.getContentResolver(), mStubTunerTvInputInfo); 273 274 Uri uri = TvContract.buildChannelsUriForInput(mStubTunerTvInputInfo.getId()); 275 String[] projection = { TvContract.Channels._ID }; 276 try (Cursor cursor = mActivity.getContentResolver().query( 277 uri, projection, null, null, null)) { 278 while (cursor != null && cursor.moveToNext()) { 279 long channelId = cursor.getLong(0); 280 Uri channelUri = TvContract.buildChannelUri(channelId); 281 mCallback.mTunedInfos = null; 282 mTvView.tune(mStubTunerTvInputInfo.getId(), channelUri); 283 mInstrumentation.waitForIdleSync(); 284 new PollingCheck(TIME_OUT_MS) { 285 @Override 286 protected boolean check() { 287 return mMockCallback.isVideoAvailable(mStubTunerTvInputInfo.getId()); 288 } 289 }.run(); 290 new PollingCheck(TIME_OUT_MS) { 291 @Override 292 protected boolean check() { 293 return mCallback.mTunedInfos != null; 294 } 295 }.run(); 296 297 List<TunedInfo> returnedInfos = mManager.getCurrentTunedInfos(); 298 assertEquals(1, returnedInfos.size()); 299 TunedInfo returnedInfo = returnedInfos.get(0); 300 TunedInfo expectedInfo = new TunedInfo( 301 "android.tv.cts/android.media.tv.cts.StubTunerTvInputService", 302 channelUri, 303 false, 304 false, 305 false, 306 TunedInfo.APP_TYPE_SELF, 307 TunedInfo.APP_TAG_SELF); 308 assertEquals(expectedInfo, returnedInfo); 309 310 assertEquals(expectedInfo.getAppTag(), returnedInfo.getAppTag()); 311 assertEquals(expectedInfo.getAppType(), returnedInfo.getAppType()); 312 assertEquals(expectedInfo.getChannelUri(), returnedInfo.getChannelUri()); 313 assertEquals(expectedInfo.getInputId(), returnedInfo.getInputId()); 314 assertEquals(expectedInfo.isMainSession(), returnedInfo.isMainSession()); 315 assertEquals(expectedInfo.isRecordingSession(), returnedInfo.isRecordingSession()); 316 assertEquals(expectedInfo.isVisible(), returnedInfo.isVisible()); 317 318 assertEquals(1, mCallback.mTunedInfos.size()); 319 TunedInfo callbackInfo = mCallback.mTunedInfos.get(0); 320 assertEquals(expectedInfo, callbackInfo); 321 } 322 } 323 } 324 testGetCurrentTunedInfos()325 public void testGetCurrentTunedInfos() throws Throwable { 326 if (!Utils.hasTvInputFramework(getActivity())) { 327 return; 328 } 329 mActivity.runOnUiThread(new Runnable() { 330 @Override 331 public void run() { 332 mManager.registerCallback(mCallback, new Handler()); 333 } 334 }); 335 tryTuneAllChannels(); 336 mActivity.runOnUiThread(new Runnable() { 337 @Override 338 public void run() { 339 mManager.unregisterCallback(mCallback); 340 } 341 }); 342 } 343 testGetInputState()344 public void testGetInputState() throws Exception { 345 if (!Utils.hasTvInputFramework(getActivity())) { 346 return; 347 } 348 assertEquals(mManager.getInputState(mStubId), TvInputManager.INPUT_STATE_CONNECTED); 349 } 350 testGetTvInputInfo()351 public void testGetTvInputInfo() throws Exception { 352 if (!Utils.hasTvInputFramework(getActivity())) { 353 return; 354 } 355 TvInputInfo expected = mManager.getTvInputInfo(mStubId); 356 TvInputInfo actual = getInfoForClassName(mManager.getTvInputList(), 357 StubTvInputService2.class.getName()); 358 assertTrue("expected=" + expected + " actual=" + actual, 359 TvInputInfoTest.compareTvInputInfos(getActivity(), expected, actual)); 360 } 361 testGetTvInputList()362 public void testGetTvInputList() throws Exception { 363 if (!Utils.hasTvInputFramework(getActivity())) { 364 return; 365 } 366 List<TvInputInfo> list = mManager.getTvInputList(); 367 for (String name : VALID_TV_INPUT_SERVICES) { 368 assertNotNull("getTvInputList() doesn't contain valid input: " + name, 369 getInfoForClassName(list, name)); 370 } 371 for (String name : INVALID_TV_INPUT_SERVICES) { 372 assertNull("getTvInputList() contains invalind input: " + name, 373 getInfoForClassName(list, name)); 374 } 375 } 376 testIsParentalControlsEnabled()377 public void testIsParentalControlsEnabled() { 378 if (!Utils.hasTvInputFramework(getActivity())) { 379 return; 380 } 381 try { 382 mManager.isParentalControlsEnabled(); 383 } catch (Exception e) { 384 fail(); 385 } 386 } 387 testIsRatingBlocked()388 public void testIsRatingBlocked() { 389 if (!Utils.hasTvInputFramework(getActivity())) { 390 return; 391 } 392 try { 393 mManager.isRatingBlocked(DUMMY_RATING); 394 } catch (Exception e) { 395 fail(); 396 } 397 } 398 testRegisterUnregisterCallback()399 public void testRegisterUnregisterCallback() { 400 if (!Utils.hasTvInputFramework(getActivity())) { 401 return; 402 } 403 getActivity().runOnUiThread(new Runnable() { 404 @Override 405 public void run() { 406 try { 407 mManager.registerCallback(mCallback, new Handler()); 408 mManager.unregisterCallback(mCallback); 409 } catch (Exception e) { 410 fail(); 411 } 412 } 413 }); 414 getInstrumentation().waitForIdleSync(); 415 } 416 testInputAddedAndRemoved()417 public void testInputAddedAndRemoved() { 418 if (!Utils.hasTvInputFramework(getActivity())) { 419 return; 420 } 421 getActivity().runOnUiThread(new Runnable() { 422 @Override 423 public void run() { 424 mManager.registerCallback(mCallback, new Handler()); 425 } 426 }); 427 getInstrumentation().waitForIdleSync(); 428 429 // Test if onInputRemoved() is called. 430 mCallback.resetLogs(); 431 PackageManager pm = getActivity().getPackageManager(); 432 ComponentName component = new ComponentName(getActivity(), StubTvInputService2.class); 433 assertTrue(PackageManager.COMPONENT_ENABLED_STATE_DISABLED != pm.getComponentEnabledSetting( 434 component)); 435 pm.setComponentEnabledSetting(component, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 436 PackageManager.DONT_KILL_APP); 437 new PollingCheck(TIME_OUT_MS) { 438 @Override 439 protected boolean check() { 440 return mCallback.isInputRemoved(mStubId); 441 } 442 }.run(); 443 444 // Test if onInputAdded() is called. 445 mCallback.resetLogs(); 446 assertEquals(PackageManager.COMPONENT_ENABLED_STATE_DISABLED, pm.getComponentEnabledSetting( 447 component)); 448 pm.setComponentEnabledSetting(component, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 449 PackageManager.DONT_KILL_APP); 450 new PollingCheck(TIME_OUT_MS) { 451 @Override 452 protected boolean check() { 453 return mCallback.isInputAdded(mStubId); 454 } 455 }.run(); 456 457 getActivity().runOnUiThread(new Runnable() { 458 @Override 459 public void run() { 460 mManager.unregisterCallback(mCallback); 461 } 462 }); 463 getInstrumentation().waitForIdleSync(); 464 } 465 testTvInputInfoUpdated()466 public void testTvInputInfoUpdated() throws IOException, XmlPullParserException { 467 if (!Utils.hasTvInputFramework(getActivity())) { 468 return; 469 } 470 getActivity().runOnUiThread(new Runnable() { 471 @Override 472 public void run() { 473 mManager.registerCallback(mCallback, new Handler()); 474 } 475 }); 476 getInstrumentation().waitForIdleSync(); 477 478 mCallback.resetLogs(); 479 TvInputInfo defaultInfo = new TvInputInfo.Builder(getActivity(), 480 new ComponentName(getActivity(), StubTunerTvInputService.class)).build(); 481 TvInputInfo updatedInfo = new TvInputInfo.Builder(getActivity(), 482 new ComponentName(getActivity(), StubTunerTvInputService.class)) 483 .setTunerCount(10).setCanRecord(true).setCanPauseRecording(false).build(); 484 485 mManager.updateTvInputInfo(updatedInfo); 486 new PollingCheck(TIME_OUT_MS) { 487 @Override 488 protected boolean check() { 489 TvInputInfo info = mCallback.getLastUpdatedTvInputInfo(); 490 return info != null && info.getTunerCount() == 10 && info.canRecord() 491 && !info.canPauseRecording(); 492 } 493 }.run(); 494 495 mManager.updateTvInputInfo(defaultInfo); 496 new PollingCheck(TIME_OUT_MS) { 497 @Override 498 protected boolean check() { 499 TvInputInfo info = mCallback.getLastUpdatedTvInputInfo(); 500 return info != null && info.getTunerCount() == 1 && !info.canRecord() 501 && info.canPauseRecording(); 502 } 503 }.run(); 504 505 getActivity().runOnUiThread(new Runnable() { 506 @Override 507 public void run() { 508 mManager.unregisterCallback(mCallback); 509 } 510 }); 511 getInstrumentation().waitForIdleSync(); 512 } 513 testAcquireTvInputHardware()514 public void testAcquireTvInputHardware() { 515 if (!Utils.hasTvInputFramework(getActivity()) || mManager == null) { 516 return; 517 } 518 519 String[] newPermissions = Arrays.copyOf( 520 BASE_SHELL_PERMISSIONS, BASE_SHELL_PERMISSIONS.length + 1); 521 newPermissions[BASE_SHELL_PERMISSIONS.length] = PERMISSION_TV_INPUT_HARDWARE; 522 InstrumentationRegistry.getInstrumentation().getUiAutomation() 523 .adoptShellPermissionIdentity(newPermissions); 524 525 int deviceId = 0; 526 Hardware hardware = null; 527 boolean hardwareDeviceAdded = false; 528 529 try { 530 // Update hardware device list 531 List<TvInputHardwareInfo> hardwareList = mManager.getHardwareList(); 532 if (hardwareList == null || hardwareList.isEmpty()) { 533 // Use the test api to add an HDMI hardware device 534 mManager.addHardwareDevice(deviceId); 535 hardwareDeviceAdded = true; 536 } else { 537 deviceId = hardwareList.get(0).getDeviceId(); 538 } 539 540 // Acquire Hardware with a record client 541 HardwareCallback callback = new HardwareCallback() { 542 @Override 543 public void onReleased() {} 544 545 @Override 546 public void onStreamConfigChanged(TvStreamConfig[] configs) {} 547 }; 548 CallbackExecutor executor = new CallbackExecutor(); 549 hardware = mManager.acquireTvInputHardware(deviceId, mStubTvInputInfo, 550 null /*tvInputSessionId*/, TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 551 executor, callback); 552 assertNotNull(hardware); 553 554 // Acquire the same device with a LIVE client 555 mManager.releaseTvInputHardware(deviceId, hardware); 556 hardware = mManager.acquireTvInputHardware(deviceId, mStubTvInputInfo, 557 null /*tvInputSessionId*/, TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE, 558 executor, callback); 559 560 // The request for Live may fail because it have lower priority than Playback 561 // assertNotNull(hardware); 562 } finally { 563 // Clean up 564 if (hardware != null) { 565 mManager.releaseTvInputHardware(deviceId, hardware); 566 } 567 if (hardwareDeviceAdded) { 568 mManager.removeHardwareDevice(deviceId); 569 } 570 // Restore the base shell permissions 571 InstrumentationRegistry.getInstrumentation() 572 .getUiAutomation() 573 .adoptShellPermissionIdentity(BASE_SHELL_PERMISSIONS); 574 } 575 } 576 testTvInputHardwareOverrideAudioSink()577 public void testTvInputHardwareOverrideAudioSink() { 578 if (!Utils.hasTvInputFramework(getActivity()) || mManager == null) { 579 return; 580 } 581 582 String[] newPermissions = 583 Arrays.copyOf(BASE_SHELL_PERMISSIONS, BASE_SHELL_PERMISSIONS.length + 1); 584 newPermissions[BASE_SHELL_PERMISSIONS.length] = PERMISSION_TV_INPUT_HARDWARE; 585 InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity( 586 newPermissions); 587 588 int deviceId = 0; 589 Hardware hardware = null; 590 boolean hardwareDeviceAdded = false; 591 592 try { 593 // Update hardware device list 594 List<TvInputHardwareInfo> hardwareList = mManager.getHardwareList(); 595 if (hardwareList == null || hardwareList.isEmpty()) { 596 // Use the test api to add an HDMI hardware device 597 mManager.addHardwareDevice(deviceId); 598 hardwareDeviceAdded = true; 599 } else { 600 deviceId = hardwareList.get(0).getDeviceId(); 601 } 602 603 // Acquire Hardware with a record client 604 HardwareCallback callback = new HardwareCallback() { 605 @Override 606 public void onReleased() {} 607 608 @Override 609 public void onStreamConfigChanged(TvStreamConfig[] configs) {} 610 }; 611 CallbackExecutor executor = new CallbackExecutor(); 612 hardware = mManager.acquireTvInputHardware(deviceId, mStubTvInputInfo, 613 null /*tvInputSessionId*/, TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 614 executor, callback); 615 assertNotNull(hardware); 616 617 // Override audio sink 618 AudioManager am = mActivity.getSystemService(AudioManager.class); 619 AudioDeviceInfo[] deviceInfos = am.getDevices(AudioManager.GET_DEVICES_OUTPUTS); 620 if (deviceInfos.length > 0) { 621 // test available overrideAudioSink APIs 622 hardware.overrideAudioSink(deviceInfos[0], 0, 623 AudioFormat.CHANNEL_OUT_DEFAULT, AudioFormat.ENCODING_DEFAULT); 624 hardware.overrideAudioSink(deviceInfos[0].getType(), deviceInfos[0].getAddress(), 0, 625 AudioFormat.CHANNEL_OUT_DEFAULT, AudioFormat.ENCODING_DEFAULT); 626 } 627 } catch (Exception e) { 628 fail(); 629 } finally { 630 if (hardware != null) { 631 mManager.releaseTvInputHardware(deviceId, hardware); 632 } 633 if (hardwareDeviceAdded) { 634 mManager.removeHardwareDevice(deviceId); 635 } 636 InstrumentationRegistry.getInstrumentation() 637 .getUiAutomation() 638 .adoptShellPermissionIdentity(BASE_SHELL_PERMISSIONS); 639 } 640 } 641 testTvInputHardwareSetSurface()642 public void testTvInputHardwareSetSurface() { 643 if (!Utils.hasTvInputFramework(getActivity()) || mManager == null) { 644 return; 645 } 646 647 String[] newPermissions = 648 Arrays.copyOf(BASE_SHELL_PERMISSIONS, BASE_SHELL_PERMISSIONS.length + 1); 649 newPermissions[BASE_SHELL_PERMISSIONS.length] = PERMISSION_TV_INPUT_HARDWARE; 650 InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity( 651 newPermissions); 652 653 int deviceId = 0; 654 Hardware hardware = null; 655 SurfaceTexture dummySurfaceTexture = null; 656 Surface dummySurface = null; 657 658 try { 659 dummySurfaceTexture = new SurfaceTexture(1); 660 dummySurface = new Surface(dummySurfaceTexture); 661 List<TvInputHardwareInfo> hardwareList = mManager.getHardwareList(); 662 if (hardwareList == null || hardwareList.isEmpty()) { 663 Log.w(TAG, "No hardware found, skip testTvInputHardwareSetSurface."); 664 return; 665 } 666 667 deviceId = hardwareList.get(0).getDeviceId(); 668 final List<TvStreamConfig> configList = new ArrayList<TvStreamConfig>(); 669 670 // Acquire Hardware with a record client 671 HardwareCallback callback = new HardwareCallback() { 672 @Override 673 public void onReleased() {} 674 675 @Override 676 public void onStreamConfigChanged(TvStreamConfig[] configs) { 677 configList.addAll(Arrays.asList(configs)); 678 } 679 }; 680 CallbackExecutor executor = new CallbackExecutor(); 681 hardware = mManager.acquireTvInputHardware(deviceId, mStubTvInputInfo, 682 null /*tvInputSessionId*/, TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 683 executor, callback); 684 assertNotNull(hardware); 685 PollingCheck.waitFor(TIME_OUT_MS, () -> !configList.isEmpty()); 686 687 assertTrue(hardware.setSurface(dummySurface, configList.get(0))); 688 assertTrue(hardware.setSurface(null, null)); 689 } finally { 690 if (dummySurface != null) { 691 dummySurface.release(); 692 } 693 if (dummySurfaceTexture != null) { 694 dummySurfaceTexture.release(); 695 } 696 if (hardware != null) { 697 mManager.releaseTvInputHardware(deviceId, hardware); 698 } 699 // Restore the base shell permissions 700 InstrumentationRegistry.getInstrumentation() 701 .getUiAutomation() 702 .adoptShellPermissionIdentity(BASE_SHELL_PERMISSIONS); 703 } 704 } 705 testGetAvailableExtensionInterfaceNames()706 public void testGetAvailableExtensionInterfaceNames() { 707 if (!Utils.hasTvInputFramework(getActivity())) { 708 return; 709 } 710 711 try { 712 String inputId = prepareStubHardwareTvInputService(); 713 714 StubHardwareTvInputService.injectAvailableExtensionInterface( 715 EXTENSION_INTERFACE_NAME_WITHOUT_PERMISSION, null); 716 StubHardwareTvInputService.injectAvailableExtensionInterface( 717 EXTENSION_INTERFACE_NAME_WITH_PERMISSION_GRANTED, PERMISSION_GRANTED); 718 StubHardwareTvInputService.injectAvailableExtensionInterface( 719 EXTENSION_INTERFACE_NAME_WITH_PERMISSION_UNGRANTED, PERMISSION_UNGRANTED); 720 721 List<String> names = mManager.getAvailableExtensionInterfaceNames(inputId); 722 assertTrue(names != null && !names.isEmpty()); 723 assertTrue(names.contains(EXTENSION_INTERFACE_NAME_WITHOUT_PERMISSION)); 724 assertTrue(names.contains(EXTENSION_INTERFACE_NAME_WITH_PERMISSION_GRANTED)); 725 assertFalse(names.contains(EXTENSION_INTERFACE_NAME_WITH_PERMISSION_UNGRANTED)); 726 727 StubHardwareTvInputService.clearAvailableExtensionInterfaces(); 728 729 names = mManager.getAvailableExtensionInterfaceNames(inputId); 730 assertTrue(names != null && names.isEmpty()); 731 } finally { 732 StubHardwareTvInputService.clearAvailableExtensionInterfaces(); 733 cleanupStubHardwareTvInputService(); 734 } 735 } 736 testGetExtensionInterface()737 public void testGetExtensionInterface() { 738 if (!Utils.hasTvInputFramework(getActivity())) { 739 return; 740 } 741 742 try { 743 String inputId = prepareStubHardwareTvInputService(); 744 745 StubHardwareTvInputService.injectAvailableExtensionInterface( 746 EXTENSION_INTERFACE_NAME_WITHOUT_PERMISSION, null); 747 StubHardwareTvInputService.injectAvailableExtensionInterface( 748 EXTENSION_INTERFACE_NAME_WITH_PERMISSION_GRANTED, PERMISSION_GRANTED); 749 StubHardwareTvInputService.injectAvailableExtensionInterface( 750 EXTENSION_INTERFACE_NAME_WITH_PERMISSION_UNGRANTED, PERMISSION_UNGRANTED); 751 752 assertNotNull(mManager.getExtensionInterface(inputId, 753 EXTENSION_INTERFACE_NAME_WITHOUT_PERMISSION)); 754 assertNotNull(mManager.getExtensionInterface(inputId, 755 EXTENSION_INTERFACE_NAME_WITH_PERMISSION_GRANTED)); 756 assertNull(mManager.getExtensionInterface(inputId, 757 EXTENSION_INTERFACE_NAME_WITH_PERMISSION_UNGRANTED)); 758 } finally { 759 StubHardwareTvInputService.clearAvailableExtensionInterfaces(); 760 cleanupStubHardwareTvInputService(); 761 } 762 } 763 testGetClientPriority()764 public void testGetClientPriority() { 765 if (!Utils.hasTvInputFramework(getActivity()) || !Utils.hasTunerFeature(getActivity())) { 766 return; 767 } 768 769 // Use the test api to get priorities in tunerResourceManagerUseCaseConfig.xml 770 TunerResourceManager trm = (TunerResourceManager) getActivity() 771 .getSystemService(Context.TV_TUNER_RESOURCE_MGR_SERVICE); 772 int fgLivePriority = trm.getConfigPriority(TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE, 773 true); 774 int bgLivePriority = trm.getConfigPriority(TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE, 775 false); 776 int fgDefaultPriority = trm.getConfigPriority(PRIORITY_HINT_USE_CASE_TYPE_INVALID, true); 777 int bgDefaultPriority = trm.getConfigPriority(PRIORITY_HINT_USE_CASE_TYPE_INVALID, false); 778 boolean isForeground = checkIsForeground(android.os.Process.myPid()); 779 780 int priority = mManager.getClientPriority(TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE); 781 assertTrue(priority == (isForeground ? fgLivePriority : bgLivePriority)); 782 783 try { 784 priority = mManager.getClientPriority( 785 PRIORITY_HINT_USE_CASE_TYPE_INVALID /* invalid use case type */); 786 } catch (IllegalArgumentException e) { 787 // pass 788 } 789 790 Handler handler = new Handler(Looper.getMainLooper()); 791 final SessionCallback sessionCallback = new SessionCallback(); 792 mManager.createSession(mStubId, mContext.getAttributionSource(), sessionCallback, handler); 793 PollingCheck.waitFor(TIME_OUT_MS, () -> sessionCallback.getSession() != null); 794 Session session = sessionCallback.getSession(); 795 String sessionId = StubTvInputService2.getSessionId(); 796 assertNotNull(sessionId); 797 798 priority = mManager.getClientPriority( 799 TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE, sessionId /* valid sessionId */); 800 assertTrue(priority == (isForeground ? fgLivePriority : bgLivePriority)); 801 802 try { 803 priority = mManager.getClientPriority( 804 PRIORITY_HINT_USE_CASE_TYPE_INVALID /* invalid use case type */, 805 sessionId /* valid sessionId */); 806 } catch (IllegalArgumentException e) { 807 // pass 808 } 809 810 session.release(); 811 PollingCheck.waitFor(TIME_OUT_MS, () -> StubTvInputService2.getSessionId() == null); 812 813 priority = mManager.getClientPriority( 814 TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE, sessionId /* invalid sessionId */); 815 assertTrue(priority == bgLivePriority); 816 817 try { 818 priority = mManager.getClientPriority( 819 PRIORITY_HINT_USE_CASE_TYPE_INVALID /* invalid use case type */, 820 sessionId /* invalid sessionId */); 821 } catch (IllegalArgumentException e) { 822 // pass 823 } 824 } 825 testGetClientPid()826 public void testGetClientPid() { 827 if (!Utils.hasTvInputFramework(getActivity())) { 828 return; 829 } 830 831 Handler handler = new Handler(Looper.getMainLooper()); 832 final SessionCallback sessionCallback = new SessionCallback(); 833 mManager.createSession(mStubId, mContext.getAttributionSource(), sessionCallback, handler); 834 PollingCheck.waitFor(TIME_OUT_MS, () -> sessionCallback.getSession() != null); 835 Session session = sessionCallback.getSession(); 836 String sessionId = StubTvInputService2.getSessionId(); 837 assertNotNull(sessionId); 838 839 int pid = mManager.getClientPid(sessionId); 840 assertTrue(pid == android.os.Process.myPid()); 841 842 session.release(); 843 PollingCheck.waitFor(TIME_OUT_MS, () -> StubTvInputService2.getSessionId() == null); 844 } 845 846 private static class LoggingCallback extends TvInputManager.TvInputCallback { 847 private final List<String> mAddedInputs = new ArrayList<>(); 848 private final List<String> mRemovedInputs = new ArrayList<>(); 849 private TvInputInfo mLastUpdatedTvInputInfo; 850 private List<TunedInfo> mTunedInfos; 851 852 @Override onInputAdded(String inputId)853 public synchronized void onInputAdded(String inputId) { 854 mAddedInputs.add(inputId); 855 } 856 857 @Override onInputRemoved(String inputId)858 public synchronized void onInputRemoved(String inputId) { 859 mRemovedInputs.add(inputId); 860 } 861 862 @Override onTvInputInfoUpdated(TvInputInfo info)863 public synchronized void onTvInputInfoUpdated(TvInputInfo info) { 864 mLastUpdatedTvInputInfo = info; 865 } 866 867 @Override onCurrentTunedInfosUpdated( List<TunedInfo> tunedInfos)868 public synchronized void onCurrentTunedInfosUpdated( 869 List<TunedInfo> tunedInfos) { 870 super.onCurrentTunedInfosUpdated(tunedInfos); 871 mTunedInfos = tunedInfos; 872 } 873 resetLogs()874 public synchronized void resetLogs() { 875 mAddedInputs.clear(); 876 mRemovedInputs.clear(); 877 mLastUpdatedTvInputInfo = null; 878 } 879 isInputAdded(String inputId)880 public synchronized boolean isInputAdded(String inputId) { 881 return mRemovedInputs.isEmpty() && mAddedInputs.size() == 1 && mAddedInputs.contains( 882 inputId); 883 } 884 isInputRemoved(String inputId)885 public synchronized boolean isInputRemoved(String inputId) { 886 return mAddedInputs.isEmpty() && mRemovedInputs.size() == 1 && mRemovedInputs.contains( 887 inputId); 888 } 889 getLastUpdatedTvInputInfo()890 public synchronized TvInputInfo getLastUpdatedTvInputInfo() { 891 return mLastUpdatedTvInputInfo; 892 } 893 } 894 895 public static class StubTvInputService2 extends StubTvInputService { 896 static String sTvInputSessionId; 897 getSessionId()898 public static String getSessionId() { 899 return sTvInputSessionId; 900 } 901 902 @Override onCreateSession(String inputId, String tvInputSessionId)903 public Session onCreateSession(String inputId, String tvInputSessionId) { 904 sTvInputSessionId = tvInputSessionId; 905 return new StubSessionImpl2(this); 906 } 907 908 public static class StubSessionImpl2 extends StubTvInputService.StubSessionImpl { StubSessionImpl2(Context context)909 StubSessionImpl2(Context context) { 910 super(context); 911 } 912 913 @Override onRelease()914 public void onRelease() { 915 sTvInputSessionId = null; 916 } 917 } 918 } 919 920 public static class StubHardwareTvInputService extends TvInputService { 921 private static final Map<String, String> sAvailableExtensionInterfaceMap = new HashMap<>(); 922 923 private ResolveInfo mResolveInfo = null; 924 private TvInputInfo mTvInputInfo = null; 925 clearAvailableExtensionInterfaces()926 public static void clearAvailableExtensionInterfaces() { 927 sAvailableExtensionInterfaceMap.clear(); 928 } 929 injectAvailableExtensionInterface(String name, String permission)930 public static void injectAvailableExtensionInterface(String name, String permission) { 931 sAvailableExtensionInterfaceMap.put(name, permission); 932 } 933 934 @Override onCreate()935 public void onCreate() { 936 mResolveInfo = getPackageManager().resolveService( 937 new Intent(SERVICE_INTERFACE).setClass(this, getClass()), 938 PackageManager.GET_SERVICES | PackageManager.GET_META_DATA); 939 } 940 941 @Override onHardwareAdded(TvInputHardwareInfo hardwareInfo)942 public TvInputInfo onHardwareAdded(TvInputHardwareInfo hardwareInfo) { 943 TvInputInfo info = null; 944 if (hardwareInfo.getDeviceId() == DUMMY_DEVICE_ID) { 945 info = new TvInputInfo.Builder(this, mResolveInfo) 946 .setTvInputHardwareInfo(hardwareInfo) 947 .build(); 948 mTvInputInfo = info; 949 } 950 return info; 951 } 952 953 @Override onHardwareRemoved(TvInputHardwareInfo hardwareInfo)954 public String onHardwareRemoved(TvInputHardwareInfo hardwareInfo) { 955 String inputId = null; 956 if (hardwareInfo.getDeviceId() == DUMMY_DEVICE_ID && mTvInputInfo != null) { 957 inputId = mTvInputInfo.getId(); 958 mTvInputInfo = null; 959 } 960 return inputId; 961 } 962 963 @Override onCreateSession(String inputId)964 public Session onCreateSession(String inputId) { 965 return null; 966 } 967 968 @Override getAvailableExtensionInterfaceNames()969 public List<String> getAvailableExtensionInterfaceNames() { 970 super.getAvailableExtensionInterfaceNames(); 971 return new ArrayList<>(sAvailableExtensionInterfaceMap.keySet()); 972 } 973 974 @Override getExtensionInterfacePermission(String name)975 public String getExtensionInterfacePermission(String name) { 976 super.getExtensionInterfacePermission(name); 977 return sAvailableExtensionInterfaceMap.get(name); 978 } 979 980 @Override getExtensionInterface(String name)981 public IBinder getExtensionInterface(String name) { 982 super.getExtensionInterface(name); 983 if (sAvailableExtensionInterfaceMap.containsKey(name)) { 984 return new Binder(); 985 } else { 986 return null; 987 } 988 } 989 } 990 991 public class CallbackExecutor implements Executor { 992 @Override execute(Runnable r)993 public void execute(Runnable r) { 994 r.run(); 995 } 996 } 997 998 private class SessionCallback extends TvInputManager.SessionCallback { 999 private TvInputManager.Session mSession; 1000 getSession()1001 public TvInputManager.Session getSession() { 1002 return mSession; 1003 } 1004 1005 @Override onSessionCreated(TvInputManager.Session session)1006 public void onSessionCreated(TvInputManager.Session session) { 1007 mSession = session; 1008 } 1009 } 1010 checkIsForeground(int pid)1011 private boolean checkIsForeground(int pid) { 1012 ActivityManager am = (ActivityManager) getActivity() 1013 .getSystemService(Context.ACTIVITY_SERVICE); 1014 List<RunningAppProcessInfo> appProcesses = am.getRunningAppProcesses(); 1015 if (appProcesses == null) { 1016 return false; 1017 } 1018 for (RunningAppProcessInfo appProcess : appProcesses) { 1019 if (appProcess.pid == pid 1020 && appProcess.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND) { 1021 return true; 1022 } 1023 } 1024 return false; 1025 } 1026 } 1027