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