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