Fix permission CTS tests on Fugu

The Leanback UI on TV required some tweaks in the
permission tests. Due to bugs in UiAutomator we
had to fall-back to the raw accessibility APIs to
make the tests robust.

bug:28657926

Change-Id: Icefa05fb97094de3a76eb34325ff43212dab898a
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp23/src/com/android/cts/usepermission/BasePermissionsTest.java b/hostsidetests/appsecurity/test-apps/UsePermissionApp23/src/com/android/cts/usepermission/BasePermissionsTest.java
index 5b7dfb6..fb838d4 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp23/src/com/android/cts/usepermission/BasePermissionsTest.java
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp23/src/com/android/cts/usepermission/BasePermissionsTest.java
@@ -29,21 +29,28 @@
 import android.content.res.Resources;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.SystemClock;
 import android.provider.Settings;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.runner.AndroidJUnit4;
 import android.support.test.uiautomator.By;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 import android.support.test.uiautomator.UiDevice;
 import android.support.test.uiautomator.UiObject;
 import android.support.test.uiautomator.UiObject2;
 import android.support.test.uiautomator.UiSelector;
 import android.util.ArrayMap;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.Switch;
 import junit.framework.Assert;
 import org.junit.Before;
 import org.junit.runner.RunWith;
 
+import java.util.List;
 import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeoutException;
 
 @RunWith(AndroidJUnit4.class)
 public abstract class BasePermissionsTest {
@@ -52,6 +59,8 @@
     private static final long IDLE_TIMEOUT_MILLIS = 500;
     private static final long GLOBAL_TIMEOUT_MILLIS = 5000;
 
+    private static final long RETRY_TIMEOUT = 30000;
+
     private static Map<String, String> sPermissionToLabelResNameMap = new ArrayMap<>();
     static {
         // Contacts
@@ -160,6 +169,11 @@
         } catch (PackageManager.NameNotFoundException e) {
             /* cannot happen */
         }
+
+        UiObject2 button = getUiDevice().findObject(By.text("Close"));
+        if (button != null) {
+            button.click();
+        }
     }
 
     protected BasePermissionActivity.Result requestPermissions(
@@ -203,6 +217,11 @@
                 "com.android.packageinstaller:id/do_not_ask_checkbox")).click();
     }
 
+    protected void clickDontAskAgainButton() throws Exception {
+        getUiDevice().findObject(new UiSelector().resourceId(
+                "com.android.packageinstaller:id/permission_deny_dont_ask_again_button")).click();
+    }
+
     protected void grantPermission(String permission) throws Exception {
         grantPermissions(new String[]{permission});
     }
@@ -222,47 +241,53 @@
     private void setPermissionGrantState(String[] permissions, boolean granted,
             boolean legacyApp) throws Exception {
         getUiDevice().pressBack();
-        getUiDevice().waitForIdle();
+        waitForIdle();
         getUiDevice().pressBack();
-        getUiDevice().waitForIdle();
+        waitForIdle();
 
         // Open the app details settings
         Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
         intent.addCategory(Intent.CATEGORY_DEFAULT);
         intent.setData(Uri.parse("package:" + mContext.getPackageName()));
-        mContext.startActivity(intent);
+        startActivity(intent);
 
-        getUiDevice().waitForIdle();
+        waitForIdle();
 
         // Open the permissions UI
-        UiObject permissionItem = getUiDevice().findObject(new UiSelector().text("Permissions"));
-        permissionItem.click();
+        AccessibilityNodeInfo permLabelView = getNodeTimed(() -> findByText("Permissions"));
+        Assert.assertNotNull("Permissions label should be present", permLabelView);
 
-        getUiDevice().waitForIdle();
+        AccessibilityNodeInfo permItemView = findCollectionItem(permLabelView);
+        Assert.assertNotNull("Permissions item should be present", permLabelView);
+
+        click(permItemView);
+
+        waitForIdle();
 
         for (String permission : permissions) {
             // Find the permission toggle
             String permissionLabel = getPermissionLabel(permission);
 
-            UiObject2 toggleSwitch = null;
-            UiObject2 current = getUiDevice().findObject(By.text(permissionLabel));
-            Assert.assertNotNull("Permission should be present");
+            AccessibilityNodeInfo labelView = getNodeTimed(() -> findByText(permissionLabel));
+            Assert.assertNotNull("Permission label should be present", labelView);
 
-            while (toggleSwitch == null) {
-                UiObject2 parent = current.getParent();
-                if (parent == null) {
-                    fail("Cannot find permission list item");
-                }
-                toggleSwitch = current.findObject(By.clazz(Switch.class));
-                current = parent;
-            }
+            AccessibilityNodeInfo itemView = findCollectionItem(labelView);
+            Assert.assertNotNull("Permission item should be present", itemView);
 
-            final boolean wasGranted = toggleSwitch.isChecked();
+            final AccessibilityNodeInfo toggleView = findSwitch(itemView);
+            Assert.assertNotNull("Permission toggle should be present", toggleView);
+
+            final boolean wasGranted = toggleView.isChecked();
             if (granted != wasGranted) {
                 // Toggle the permission
-                toggleSwitch.click();
 
-                getUiDevice().waitForIdle();
+                if (!itemView.getActionList().contains(AccessibilityAction.ACTION_CLICK)) {
+                    click(toggleView);
+                } else {
+                    click(itemView);
+                }
+
+                waitForIdle();
 
                 if (wasGranted && legacyApp) {
                     String packageName = getInstrumentation().getContext().getPackageManager()
@@ -277,15 +302,15 @@
                             .text(confirmTitle.toUpperCase()));
                     denyAnyway.click();
 
-                    getUiDevice().waitForIdle();
+                    waitForIdle();
                 }
             }
         }
 
         getUiDevice().pressBack();
-        getUiDevice().waitForIdle();
+        waitForIdle();
         getUiDevice().pressBack();
-        getUiDevice().waitForIdle();
+        waitForIdle();
     }
 
     private String getPermissionLabel(String permission) throws Exception {
@@ -294,4 +319,126 @@
         final int resourceId = mPlatformResources.getIdentifier(labelResName, null, null);
         return mPlatformResources.getString(resourceId);
     }
-}
+
+    private void startActivity(final Intent intent) throws Exception {
+        getInstrumentation().getUiAutomation().executeAndWaitForEvent(
+                () -> {
+            try {
+                getInstrumentation().getContext().startActivity(intent);
+            } catch (Exception e) {
+                fail("Cannot start activity: " + intent);
+            }
+        }, (AccessibilityEvent event) -> event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
+         , GLOBAL_TIMEOUT_MILLIS);
+    }
+
+    private AccessibilityNodeInfo findByText(String text) throws Exception {
+        AccessibilityNodeInfo root = getInstrumentation().getUiAutomation().getRootInActiveWindow();
+        AccessibilityNodeInfo result = findByText(root, text);
+        if (result != null) {
+            return result;
+        }
+        return findByTextInCollection(root, text);
+    }
+
+    private static AccessibilityNodeInfo findByText(AccessibilityNodeInfo root, String text) {
+        List<AccessibilityNodeInfo> nodes = root.findAccessibilityNodeInfosByText(text);
+        for (AccessibilityNodeInfo node : nodes) {
+            if (node.getText().toString().equals(text)) {
+                return node;
+            }
+        }
+        return null;
+    }
+
+    private static AccessibilityNodeInfo findByTextInCollection(AccessibilityNodeInfo root, String text)
+            throws Exception {
+        AccessibilityNodeInfo result;
+        final int childCount = root.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            AccessibilityNodeInfo child = root.getChild(i);
+            if (child == null) {
+                continue;
+            }
+            if (child.getCollectionInfo() != null) {
+                while (child.getActionList().contains(AccessibilityAction.ACTION_SCROLL_FORWARD)) {
+                    scrollForward(child);
+                    waitForIdle();
+                    result = getNodeTimed(() -> findByText(child, text));
+                    if (result != null) {
+                        return result;
+                    }
+                }
+            } else {
+                result = findByTextInCollection(child, text);
+                if (result != null) {
+                    return result;
+                }
+            }
+        }
+        return null;
+    }
+
+    private static void scrollForward(AccessibilityNodeInfo node) throws Exception {
+        getInstrumentation().getUiAutomation().executeAndWaitForEvent(
+                () -> node.performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD),
+                (AccessibilityEvent event) -> event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED,
+                GLOBAL_TIMEOUT_MILLIS);
+    }
+
+    private static void click(AccessibilityNodeInfo node) throws Exception {
+        getInstrumentation().getUiAutomation().executeAndWaitForEvent(
+                () -> node.performAction(AccessibilityNodeInfo.ACTION_CLICK),
+                (AccessibilityEvent event) -> event.getEventType() == AccessibilityEvent.TYPE_VIEW_CLICKED,
+                GLOBAL_TIMEOUT_MILLIS);
+    }
+
+    private static AccessibilityNodeInfo findCollectionItem(AccessibilityNodeInfo current) throws Exception {
+        AccessibilityNodeInfo result = current;
+        while (result != null) {
+            if (result.getCollectionItemInfo() != null) {
+                return result;
+            }
+            result = result.getParent();
+        }
+        return null;
+    }
+
+    private static AccessibilityNodeInfo findSwitch(AccessibilityNodeInfo root) throws Exception {
+        if (Switch.class.getName().equals(root.getClassName().toString())) {
+            return root;
+        }
+        final int childCount = root.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            AccessibilityNodeInfo child = root.getChild(i);
+            if (child == null) {
+                continue;
+            }
+            if (Switch.class.getName().equals(child.getClassName().toString())) {
+                return child;
+            }
+            AccessibilityNodeInfo result = findSwitch(child);
+            if (result != null) {
+                return result;
+            }
+        }
+        return null;
+    }
+
+    private static AccessibilityNodeInfo getNodeTimed(Callable<AccessibilityNodeInfo> callable) throws Exception {
+        final long startTimeMillis = SystemClock.uptimeMillis();
+        while (true) {
+            AccessibilityNodeInfo node = callable.call();
+            if (node != null) {
+                return node;
+            }
+            final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
+            final long remainingTimeMillis = Math.min(startTimeMillis + RETRY_TIMEOUT, 2 * elapsedTimeMillis);
+            SystemClock.sleep(remainingTimeMillis);
+        }
+    }
+
+    private static void waitForIdle() throws TimeoutException {
+        getInstrumentation().getUiAutomation().waitForIdle(IDLE_TIMEOUT_MILLIS, GLOBAL_TIMEOUT_MILLIS);
+    }
+ }
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp23/src/com/android/cts/usepermission/UsePermissionTest23.java b/hostsidetests/appsecurity/test-apps/UsePermissionApp23/src/com/android/cts/usepermission/UsePermissionTest23.java
index 9908a45..8572aa4 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp23/src/com/android/cts/usepermission/UsePermissionTest23.java
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp23/src/com/android/cts/usepermission/UsePermissionTest23.java
@@ -276,8 +276,7 @@
                         Manifest.permission.WRITE_CONTACTS}, REQUEST_CODE_PERMISSIONS + 1,
                 BasePermissionActivity.class, () -> {
                     try {
-                        clickDontAskAgainCheckbox();
-                        clickDenyButton();
+                        denyWithPrejudice();
                         getUiDevice().waitForIdle();
                     } catch (Exception e) {
                         throw new RuntimeException(e);
@@ -352,8 +351,7 @@
                         Manifest.permission.READ_CALENDAR}, REQUEST_CODE_PERMISSIONS + 1,
                 BasePermissionActivity.class, () -> {
                     try {
-                        clickDontAskAgainCheckbox();
-                        clickDenyButton();
+                        denyWithPrejudice();
                         getUiDevice().waitForIdle();
                     } catch (Exception e) {
                         throw new RuntimeException(e);
@@ -558,4 +556,14 @@
                     .checkSelfPermission(permission));
         }
     }
+
+    private void denyWithPrejudice() throws Exception {
+        if (!getInstrumentation().getContext().getPackageManager()
+                .hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
+            clickDontAskAgainCheckbox();
+            clickDenyButton();
+        } else {
+            clickDontAskAgainButton();
+        }
+    }
 }