1 /*
2  * Copyright (C) 2016 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 com.android.cts.usepermission;
18 
19 import static junit.framework.Assert.assertEquals;
20 import static org.junit.Assert.assertNotNull;
21 import static org.junit.Assert.fail;
22 
23 import android.Manifest;
24 import android.app.Activity;
25 import android.app.Instrumentation;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.pm.PackageManager;
29 import android.content.res.Resources;
30 import android.net.Uri;
31 import android.os.Bundle;
32 import android.os.SystemClock;
33 import android.provider.Settings;
34 import android.support.test.InstrumentationRegistry;
35 import android.support.test.runner.AndroidJUnit4;
36 import android.support.test.uiautomator.By;
37 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
38 import android.support.test.uiautomator.UiDevice;
39 import android.support.test.uiautomator.UiObject;
40 import android.support.test.uiautomator.UiObject2;
41 import android.support.test.uiautomator.UiScrollable;
42 import android.support.test.uiautomator.UiSelector;
43 import android.util.ArrayMap;
44 import android.util.Log;
45 import android.view.accessibility.AccessibilityEvent;
46 import android.view.accessibility.AccessibilityNodeInfo;
47 import android.widget.Switch;
48 import android.widget.ScrollView;
49 import junit.framework.Assert;
50 import org.junit.Before;
51 import org.junit.runner.RunWith;
52 
53 import java.util.List;
54 import java.util.Map;
55 import java.util.concurrent.Callable;
56 import java.util.concurrent.TimeoutException;
57 
58 @RunWith(AndroidJUnit4.class)
59 public abstract class BasePermissionsTest {
60     private static final String PLATFORM_PACKAGE_NAME = "android";
61 
62     private static final long IDLE_TIMEOUT_MILLIS = 500;
63     private static final long GLOBAL_TIMEOUT_MILLIS = 5000;
64 
65     private static final long RETRY_TIMEOUT = 3 * GLOBAL_TIMEOUT_MILLIS;
66     private static final String LOG_TAG = "BasePermissionsTest";
67 
68     private static Map<String, String> sPermissionToLabelResNameMap = new ArrayMap<>();
69     static {
70         // Contacts
sPermissionToLabelResNameMap.put(Manifest.permission.READ_CONTACTS, "@android:string/permgrouplab_contacts")71         sPermissionToLabelResNameMap.put(Manifest.permission.READ_CONTACTS,
72                 "@android:string/permgrouplab_contacts");
sPermissionToLabelResNameMap.put(Manifest.permission.WRITE_CONTACTS, "@android:string/permgrouplab_contacts")73         sPermissionToLabelResNameMap.put(Manifest.permission.WRITE_CONTACTS,
74                 "@android:string/permgrouplab_contacts");
75         // Calendar
sPermissionToLabelResNameMap.put(Manifest.permission.READ_CALENDAR, "@android:string/permgrouplab_calendar")76         sPermissionToLabelResNameMap.put(Manifest.permission.READ_CALENDAR,
77                 "@android:string/permgrouplab_calendar");
sPermissionToLabelResNameMap.put(Manifest.permission.WRITE_CALENDAR, "@android:string/permgrouplab_calendar")78         sPermissionToLabelResNameMap.put(Manifest.permission.WRITE_CALENDAR,
79                 "@android:string/permgrouplab_calendar");
80         // SMS
sPermissionToLabelResNameMap.put(Manifest.permission.SEND_SMS, "@android:string/permgrouplab_sms")81         sPermissionToLabelResNameMap.put(Manifest.permission.SEND_SMS,
82                 "@android:string/permgrouplab_sms");
sPermissionToLabelResNameMap.put(Manifest.permission.RECEIVE_SMS, "@android:string/permgrouplab_sms")83         sPermissionToLabelResNameMap.put(Manifest.permission.RECEIVE_SMS,
84                 "@android:string/permgrouplab_sms");
sPermissionToLabelResNameMap.put(Manifest.permission.READ_SMS, "@android:string/permgrouplab_sms")85         sPermissionToLabelResNameMap.put(Manifest.permission.READ_SMS,
86                 "@android:string/permgrouplab_sms");
sPermissionToLabelResNameMap.put(Manifest.permission.RECEIVE_WAP_PUSH, "@android:string/permgrouplab_sms")87         sPermissionToLabelResNameMap.put(Manifest.permission.RECEIVE_WAP_PUSH,
88                 "@android:string/permgrouplab_sms");
sPermissionToLabelResNameMap.put(Manifest.permission.RECEIVE_MMS, "@android:string/permgrouplab_sms")89         sPermissionToLabelResNameMap.put(Manifest.permission.RECEIVE_MMS,
90                 "@android:string/permgrouplab_sms");
91         sPermissionToLabelResNameMap.put("android.permission.READ_CELL_BROADCASTS",
92                 "@android:string/permgrouplab_sms");
93         // Storage
sPermissionToLabelResNameMap.put(Manifest.permission.READ_EXTERNAL_STORAGE, "@android:string/permgrouplab_storage")94         sPermissionToLabelResNameMap.put(Manifest.permission.READ_EXTERNAL_STORAGE,
95                 "@android:string/permgrouplab_storage");
sPermissionToLabelResNameMap.put(Manifest.permission.WRITE_EXTERNAL_STORAGE, "@android:string/permgrouplab_storage")96         sPermissionToLabelResNameMap.put(Manifest.permission.WRITE_EXTERNAL_STORAGE,
97                 "@android:string/permgrouplab_storage");
98         // Location
sPermissionToLabelResNameMap.put(Manifest.permission.ACCESS_FINE_LOCATION, "@android:string/permgrouplab_location")99         sPermissionToLabelResNameMap.put(Manifest.permission.ACCESS_FINE_LOCATION,
100                 "@android:string/permgrouplab_location");
sPermissionToLabelResNameMap.put(Manifest.permission.ACCESS_COARSE_LOCATION, "@android:string/permgrouplab_location")101         sPermissionToLabelResNameMap.put(Manifest.permission.ACCESS_COARSE_LOCATION,
102                 "@android:string/permgrouplab_location");
103         // Phone
sPermissionToLabelResNameMap.put(Manifest.permission.READ_PHONE_STATE, "@android:string/permgrouplab_phone")104         sPermissionToLabelResNameMap.put(Manifest.permission.READ_PHONE_STATE,
105                 "@android:string/permgrouplab_phone");
sPermissionToLabelResNameMap.put(Manifest.permission.CALL_PHONE, "@android:string/permgrouplab_phone")106         sPermissionToLabelResNameMap.put(Manifest.permission.CALL_PHONE,
107                 "@android:string/permgrouplab_phone");
108         sPermissionToLabelResNameMap.put("android.permission.ACCESS_IMS_CALL_SERVICE",
109                 "@android:string/permgrouplab_phone");
sPermissionToLabelResNameMap.put(Manifest.permission.READ_CALL_LOG, "@android:string/permgrouplab_phone")110         sPermissionToLabelResNameMap.put(Manifest.permission.READ_CALL_LOG,
111                 "@android:string/permgrouplab_phone");
sPermissionToLabelResNameMap.put(Manifest.permission.WRITE_CALL_LOG, "@android:string/permgrouplab_phone")112         sPermissionToLabelResNameMap.put(Manifest.permission.WRITE_CALL_LOG,
113                 "@android:string/permgrouplab_phone");
sPermissionToLabelResNameMap.put(Manifest.permission.ADD_VOICEMAIL, "@android:string/permgrouplab_phone")114         sPermissionToLabelResNameMap.put(Manifest.permission.ADD_VOICEMAIL,
115                 "@android:string/permgrouplab_phone");
sPermissionToLabelResNameMap.put(Manifest.permission.USE_SIP, "@android:string/permgrouplab_phone")116         sPermissionToLabelResNameMap.put(Manifest.permission.USE_SIP,
117                 "@android:string/permgrouplab_phone");
sPermissionToLabelResNameMap.put(Manifest.permission.PROCESS_OUTGOING_CALLS, "@android:string/permgrouplab_phone")118         sPermissionToLabelResNameMap.put(Manifest.permission.PROCESS_OUTGOING_CALLS,
119                 "@android:string/permgrouplab_phone");
120         // Microphone
sPermissionToLabelResNameMap.put(Manifest.permission.RECORD_AUDIO, "@android:string/permgrouplab_microphone")121         sPermissionToLabelResNameMap.put(Manifest.permission.RECORD_AUDIO,
122                 "@android:string/permgrouplab_microphone");
123         // Camera
sPermissionToLabelResNameMap.put(Manifest.permission.CAMERA, "@android:string/permgrouplab_camera")124         sPermissionToLabelResNameMap.put(Manifest.permission.CAMERA,
125                 "@android:string/permgrouplab_camera");
126         // Body sensors
sPermissionToLabelResNameMap.put(Manifest.permission.BODY_SENSORS, "@android:string/permgrouplab_sensors")127         sPermissionToLabelResNameMap.put(Manifest.permission.BODY_SENSORS,
128                 "@android:string/permgrouplab_sensors");
129     }
130 
131     private Context mContext;
132     private Resources mPlatformResources;
133     private boolean mWatch;
134 
getInstrumentation()135     protected static Instrumentation getInstrumentation() {
136         return InstrumentationRegistry.getInstrumentation();
137     }
138 
assertPermissionRequestResult(BasePermissionActivity.Result result, int requestCode, String[] permissions, boolean[] granted)139     protected static void assertPermissionRequestResult(BasePermissionActivity.Result result,
140             int requestCode, String[] permissions, boolean[] granted) {
141         assertEquals(requestCode, result.requestCode);
142         for (int i = 0; i < permissions.length; i++) {
143             assertEquals(permissions[i], result.permissions[i]);
144             assertEquals(granted[i] ? PackageManager.PERMISSION_GRANTED
145                     : PackageManager.PERMISSION_DENIED, result.grantResults[i]);
146 
147         }
148     }
149 
getUiDevice()150     protected static UiDevice getUiDevice() {
151         return UiDevice.getInstance(getInstrumentation());
152     }
153 
launchActivity(String packageName, Class<?> clazz, Bundle extras)154     protected static Activity launchActivity(String packageName,
155             Class<?> clazz, Bundle extras) {
156         Intent intent = new Intent(Intent.ACTION_MAIN);
157         intent.setClassName(packageName, clazz.getName());
158         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
159         if (extras != null) {
160             intent.putExtras(extras);
161         }
162         Activity activity = getInstrumentation().startActivitySync(intent);
163         getInstrumentation().waitForIdleSync();
164 
165         return activity;
166     }
167 
168     @Before
beforeTest()169     public void beforeTest() {
170         mContext = InstrumentationRegistry.getTargetContext();
171         try {
172             Context platformContext = mContext.createPackageContext(PLATFORM_PACKAGE_NAME, 0);
173             mPlatformResources = platformContext.getResources();
174         } catch (PackageManager.NameNotFoundException e) {
175             /* cannot happen */
176         }
177 
178         mWatch = mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
179 
180         UiObject2 button = getUiDevice().findObject(By.text("Close"));
181         if (button != null) {
182             button.click();
183         }
184     }
185 
requestPermissions( String[] permissions, int requestCode, Class<?> clazz, Runnable postRequestAction)186     protected BasePermissionActivity.Result requestPermissions(
187             String[] permissions, int requestCode, Class<?> clazz, Runnable postRequestAction)
188             throws Exception {
189         // Start an activity
190         BasePermissionActivity activity = (BasePermissionActivity) launchActivity(
191                 getInstrumentation().getTargetContext().getPackageName(), clazz, null);
192 
193         activity.waitForOnCreate();
194 
195         // Request the permissions
196         activity.requestPermissions(permissions, requestCode);
197 
198         // Define a more conservative idle criteria
199         getInstrumentation().getUiAutomation().waitForIdle(
200                 IDLE_TIMEOUT_MILLIS, GLOBAL_TIMEOUT_MILLIS);
201 
202         // Perform the post-request action
203         if (postRequestAction != null) {
204             postRequestAction.run();
205         }
206 
207         BasePermissionActivity.Result result = activity.getResult();
208         activity.finish();
209         return result;
210     }
211 
clickAllowButton()212     protected void clickAllowButton() throws Exception {
213         scrollToBottomIfWatch();
214         getUiDevice().findObject(new UiSelector().resourceId(
215                 "com.android.packageinstaller:id/permission_allow_button")).click();
216     }
217 
clickDenyButton()218     protected void clickDenyButton() throws Exception {
219         scrollToBottomIfWatch();
220         getUiDevice().findObject(new UiSelector().resourceId(
221                 "com.android.packageinstaller:id/permission_deny_button")).click();
222     }
223 
clickDontAskAgainCheckbox()224     protected void clickDontAskAgainCheckbox() throws Exception {
225         getUiDevice().findObject(new UiSelector().resourceId(
226                 "com.android.packageinstaller:id/do_not_ask_checkbox")).click();
227     }
228 
clickDontAskAgainButton()229     protected void clickDontAskAgainButton() throws Exception {
230         scrollToBottomIfWatch();
231         getUiDevice().findObject(new UiSelector().resourceId(
232                 "com.android.packageinstaller:id/permission_deny_dont_ask_again_button")).click();
233     }
234 
grantPermission(String permission)235     protected void grantPermission(String permission) throws Exception {
236         grantPermissions(new String[]{permission});
237     }
238 
grantPermissions(String[] permissions)239     protected void grantPermissions(String[] permissions) throws Exception {
240         setPermissionGrantState(permissions, true, false);
241     }
242 
revokePermission(String permission)243     protected void revokePermission(String permission) throws Exception {
244         revokePermissions(new String[] {permission}, false);
245     }
246 
revokePermissions(String[] permissions, boolean legacyApp)247     protected void revokePermissions(String[] permissions, boolean legacyApp) throws Exception {
248         setPermissionGrantState(permissions, false, legacyApp);
249     }
250 
scrollToBottomIfWatch()251     private void scrollToBottomIfWatch() throws Exception {
252         if (mWatch) {
253             UiScrollable scrollable =
254                     new UiScrollable(new UiSelector().className(ScrollView.class));
255             if (scrollable.exists()) {
256                 scrollable.flingToEnd(10);
257             }
258         }
259     }
260 
setPermissionGrantState(String[] permissions, boolean granted, boolean legacyApp)261     private void setPermissionGrantState(String[] permissions, boolean granted,
262             boolean legacyApp) throws Exception {
263         getUiDevice().pressBack();
264         waitForIdle();
265         getUiDevice().pressBack();
266         waitForIdle();
267         getUiDevice().pressBack();
268         waitForIdle();
269 
270         if (isTv()) {
271             getUiDevice().pressHome();
272             waitForIdle();
273         }
274 
275         // Open the app details settings
276         Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
277         intent.addCategory(Intent.CATEGORY_DEFAULT);
278         intent.setData(Uri.parse("package:" + mContext.getPackageName()));
279         startActivity(intent);
280 
281         waitForIdle();
282 
283         // Open the permissions UI
284         String label = mContext.getResources().getString(R.string.Permissions);
285         AccessibilityNodeInfo permLabelView = getNodeTimed(() -> findByText(label), true);
286         Assert.assertNotNull("Permissions label should be present", permLabelView);
287 
288         AccessibilityNodeInfo permItemView = findCollectionItem(permLabelView);
289         Assert.assertNotNull("Permissions item should be present", permItemView);
290 
291         click(permItemView);
292 
293         waitForIdle();
294 
295         for (String permission : permissions) {
296             // Find the permission toggle
297             String permissionLabel = getPermissionLabel(permission);
298 
299             AccessibilityNodeInfo labelView = getNodeTimed(() -> findByText(permissionLabel), true);
300             Assert.assertNotNull("Permission label should be present", labelView);
301 
302             AccessibilityNodeInfo itemView = findCollectionItem(labelView);
303             Assert.assertNotNull("Permission item should be present", itemView);
304 
305             final AccessibilityNodeInfo toggleView = findSwitch(itemView);
306             Assert.assertNotNull("Permission toggle should be present", toggleView);
307 
308             final boolean wasGranted = toggleView.isChecked();
309             if (granted != wasGranted) {
310                 // Toggle the permission
311 
312                 if (!itemView.getActionList().contains(AccessibilityAction.ACTION_CLICK)) {
313                     click(toggleView);
314                 } else {
315                     click(itemView);
316                 }
317 
318                 waitForIdle();
319 
320                 if (wasGranted && legacyApp) {
321                     scrollToBottomIfWatch();
322                     String packageName = getInstrumentation().getContext().getPackageManager()
323                             .getPermissionControllerPackageName();
324                     String resIdName = "com.android.packageinstaller"
325                             + ":string/grant_dialog_button_deny_anyway";
326                     Resources resources = getInstrumentation().getContext()
327                             .createPackageContext(packageName, 0).getResources();
328                     final int confirmResId = resources.getIdentifier(resIdName, null, null);
329                     String confirmTitle = resources.getString(confirmResId);
330                     UiObject denyAnyway = getUiDevice().findObject(new UiSelector()
331                             .textStartsWith(confirmTitle));
332                     denyAnyway.click();
333 
334                     waitForIdle();
335                 }
336             }
337         }
338 
339         getUiDevice().pressBack();
340         waitForIdle();
341         getUiDevice().pressBack();
342         waitForIdle();
343     }
344 
getPermissionLabel(String permission)345     private String getPermissionLabel(String permission) throws Exception {
346         String labelResName = sPermissionToLabelResNameMap.get(permission);
347         assertNotNull("Unknown permisison " + permission, labelResName);
348         final int resourceId = mPlatformResources.getIdentifier(labelResName, null, null);
349         return mPlatformResources.getString(resourceId);
350     }
351 
startActivity(final Intent intent)352     private void startActivity(final Intent intent) throws Exception {
353         getInstrumentation().getUiAutomation().executeAndWaitForEvent(
354                 () -> {
355             try {
356                 getInstrumentation().getContext().startActivity(intent);
357             } catch (Exception e) {
358                 fail("Cannot start activity: " + intent);
359             }
360         }, (AccessibilityEvent event) -> event.getEventType()
361                         == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
362          , GLOBAL_TIMEOUT_MILLIS);
363     }
364 
findByText(String text)365     private AccessibilityNodeInfo findByText(String text) throws Exception {
366         AccessibilityNodeInfo root = getInstrumentation().getUiAutomation().getRootInActiveWindow();
367         AccessibilityNodeInfo result = findByText(root, text);
368         if (result != null) {
369             return result;
370         }
371         return findByTextInCollection(root, text);
372     }
373 
findByText(AccessibilityNodeInfo root, String text)374     private static AccessibilityNodeInfo findByText(AccessibilityNodeInfo root, String text) {
375         List<AccessibilityNodeInfo> nodes = root.findAccessibilityNodeInfosByText(text);
376         for (AccessibilityNodeInfo node : nodes) {
377             if (node.getText().toString().equals(text)) {
378                 return node;
379             }
380         }
381         return null;
382     }
383 
findByTextInCollection(AccessibilityNodeInfo root, String text)384     private static AccessibilityNodeInfo findByTextInCollection(AccessibilityNodeInfo root,
385             String text)  throws Exception {
386         AccessibilityNodeInfo result;
387         final int childCount = root.getChildCount();
388         for (int i = 0; i < childCount; i++) {
389             AccessibilityNodeInfo child = root.getChild(i);
390             if (child == null) {
391                 continue;
392             }
393             if (child.getCollectionInfo() != null) {
394                 scrollTop(child);
395                 result = getNodeTimed(() -> findByText(child, text), false);
396                 if (result != null) {
397                     return result;
398                 }
399                 try {
400                     while (child.getActionList().contains(AccessibilityAction.ACTION_SCROLL_FORWARD)) {
401                         scrollForward(child);
402                         result = getNodeTimed(() -> findByText(child, text), false);
403                         if (result != null) {
404                             return result;
405                         }
406                     }
407                 } catch (TimeoutException e) {
408                      /* ignore */
409                 }
410             } else {
411                 result = findByTextInCollection(child, text);
412                 if (result != null) {
413                     return result;
414                 }
415             }
416         }
417         return null;
418     }
419 
scrollTop(AccessibilityNodeInfo node)420     private static void scrollTop(AccessibilityNodeInfo node) throws Exception {
421         try {
422             while (node.getActionList().contains(AccessibilityAction.ACTION_SCROLL_BACKWARD)) {
423                 scroll(node, false);
424             }
425         } catch (TimeoutException e) {
426             /* ignore */
427         }
428     }
429 
scrollForward(AccessibilityNodeInfo node)430     private static void scrollForward(AccessibilityNodeInfo node) throws Exception {
431             scroll(node, true);
432     }
433 
scroll(AccessibilityNodeInfo node, boolean forward)434     private static void scroll(AccessibilityNodeInfo node, boolean forward) throws Exception {
435         getInstrumentation().getUiAutomation().executeAndWaitForEvent(
436                 () -> node.performAction(forward
437                         ? AccessibilityNodeInfo.ACTION_SCROLL_FORWARD
438                         : AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD),
439                 (AccessibilityEvent event) -> event.getEventType()
440                         == AccessibilityEvent.TYPE_VIEW_SCROLLED,
441                 GLOBAL_TIMEOUT_MILLIS);
442         node.refresh();
443         waitForIdle();
444     }
445 
446 
click(AccessibilityNodeInfo node)447     private static void click(AccessibilityNodeInfo node) throws Exception {
448         getInstrumentation().getUiAutomation().executeAndWaitForEvent(
449                 () -> node.performAction(AccessibilityNodeInfo.ACTION_CLICK),
450                 (AccessibilityEvent event) -> event.getEventType()
451                         == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED,
452                 GLOBAL_TIMEOUT_MILLIS);
453     }
454 
findCollectionItem(AccessibilityNodeInfo current)455     private static AccessibilityNodeInfo findCollectionItem(AccessibilityNodeInfo current)
456             throws Exception {
457         AccessibilityNodeInfo result = current;
458         while (result != null) {
459             if (result.getCollectionItemInfo() != null) {
460                 return result;
461             }
462             result = result.getParent();
463         }
464         return null;
465     }
466 
findSwitch(AccessibilityNodeInfo root)467     private static AccessibilityNodeInfo findSwitch(AccessibilityNodeInfo root) throws Exception {
468         if (Switch.class.getName().equals(root.getClassName().toString())) {
469             return root;
470         }
471         final int childCount = root.getChildCount();
472         for (int i = 0; i < childCount; i++) {
473             AccessibilityNodeInfo child = root.getChild(i);
474             if (child == null) {
475                 continue;
476             }
477             if (Switch.class.getName().equals(child.getClassName().toString())) {
478                 return child;
479             }
480             AccessibilityNodeInfo result = findSwitch(child);
481             if (result != null) {
482                 return result;
483             }
484         }
485         return null;
486     }
487 
getNodeTimed( Callable<AccessibilityNodeInfo> callable, boolean retry)488     private static AccessibilityNodeInfo getNodeTimed(
489             Callable<AccessibilityNodeInfo> callable, boolean retry) throws Exception {
490         final long startTimeMillis = SystemClock.uptimeMillis();
491         while (true) {
492             try {
493                 AccessibilityNodeInfo node = callable.call();
494 
495                 if (node != null) {
496                     return node;
497                 }
498             } catch (NullPointerException e) {
499                 Log.e(LOG_TAG, "NPE while finding AccessibilityNodeInfo", e);
500             }
501 
502             final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
503             if (!retry || elapsedTimeMillis > RETRY_TIMEOUT) {
504                 return null;
505             }
506             SystemClock.sleep(2 * elapsedTimeMillis);
507         }
508     }
509 
waitForIdle()510     private static void waitForIdle() throws TimeoutException {
511         getInstrumentation().getUiAutomation().waitForIdle(IDLE_TIMEOUT_MILLIS,
512                 GLOBAL_TIMEOUT_MILLIS);
513     }
514 
isTv()515     private static boolean isTv() {
516         return getInstrumentation().getContext().getPackageManager()
517                 .hasSystemFeature(PackageManager.FEATURE_LEANBACK);
518     }
519  }
520