• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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