RESTRICT AUTOMERGE
CTS for verifying toasts are not clickable

To verify this, we:
* Allow reflection via hidden_api_policy setting (to make sure we can construct
  a clickable toast).
* Send broadcast to app to post a toast.
* App posts toast and waits for it to be displayed, communicating back
  to test.
* Test clicks toast and waits for the tap to be propagated back.
* If click is received, test fails, otherwise, succeeds.

Bug: 128674520
Test: atest CtsWindowManagerDeviceTestCases:ToastTest
      1) Passes with ag/9585932
      2) Fails without ag/9585932

Change-Id: I6d2903477f8ea86c75d6415890f443414751ab84
diff --git a/tests/framework/base/windowmanager/app/AndroidManifest.xml b/tests/framework/base/windowmanager/app/AndroidManifest.xml
index 0b79756..67cafd4 100755
--- a/tests/framework/base/windowmanager/app/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/app/AndroidManifest.xml
@@ -532,6 +532,10 @@
                <action android:name="android.service.vr.VrListenerService" />
            </intent-filter>
         </service>
+
+        <receiver
+            android:name=".ToastReceiver"
+            android:exported="true" />
     </application>
 </manifest>
 
diff --git a/tests/framework/base/windowmanager/app/res/layout/toast.xml b/tests/framework/base/windowmanager/app/res/layout/toast.xml
new file mode 100644
index 0000000..3663ab6
--- /dev/null
+++ b/tests/framework/base/windowmanager/app/res/layout/toast.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="#FFFFFFFF"
+    >
+
+  <TextView
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:layout_centerInParent="true"
+      android:text="I'm a fullscreen toast!" />
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java
index 94b397f..734b482 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java
@@ -168,6 +168,9 @@
     public static final ComponentName LAUNCH_BROADCAST_RECEIVER =
             component("LaunchBroadcastReceiver");
 
+    public static final ComponentName TOAST_RECEIVER =
+            component("ToastReceiver");
+
     public static class LaunchBroadcastReceiver {
         public static final String LAUNCH_BROADCAST_ACTION =
                 "android.server.wm.app.LAUNCH_BROADCAST_ACTION";
@@ -413,11 +416,16 @@
         public static final String COMMAND_RESIZE_DISPLAY = "resize_display";
     }
 
+    public static class ToastReceiver {
+        public static final String ACTION_TOAST_DISPLAYED = "toast_displayed";
+        public static final String ACTION_TOAST_TAP_DETECTED = "toast_tap_detected";
+    }
+
     private static ComponentName component(String className) {
         return component(Components.class, className);
     }
 
-    private static String getPackageName() {
+    public static String getPackageName() {
         return getPackageName(Components.class);
     }
 }
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/ToastReceiver.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/ToastReceiver.java
new file mode 100644
index 0000000..9ef6c3a
--- /dev/null
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/ToastReceiver.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm.app;
+
+import static android.server.wm.app.Components.ToastReceiver.ACTION_TOAST_DISPLAYED;
+import static android.server.wm.app.Components.ToastReceiver.ACTION_TOAST_TAP_DETECTED;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager.LayoutParams;
+import android.widget.Toast;
+
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Field;
+
+public class ToastReceiver extends BroadcastReceiver {
+    private static final int DETECT_TOAST_TIMEOUT_MS = 15000;
+    private static final int DETECT_TOAST_POOLING_INTERVAL_MS = 200;
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Handler handler = new Handler();
+        Toast toast = getToast(context);
+        long deadline = SystemClock.uptimeMillis() + DETECT_TOAST_TIMEOUT_MS;
+        handler.post(
+                new DetectToastRunnable(
+                        context.getApplicationContext(), toast.getView(), deadline, handler));
+        toast.show();
+    }
+
+    private Toast getToast(Context context) {
+        Context applicationContext = context.getApplicationContext();
+        View view = LayoutInflater.from(context).inflate(R.layout.toast, null);
+        view.setOnTouchListener((v, event) -> {
+            applicationContext.sendBroadcast(new Intent(ACTION_TOAST_TAP_DETECTED));
+            return false;
+        });
+        Toast toast = getClickableToast(context);
+        toast.setView(view);
+        toast.setGravity(Gravity.FILL_HORIZONTAL | Gravity.FILL_VERTICAL, 0, 0);
+        toast.setDuration(Toast.LENGTH_LONG);
+        return toast;
+    }
+
+    /**
+     * Purposely creating a toast without FLAG_NOT_TOUCHABLE in the client-side (via reflection) to
+     * test enforcement on the server-side.
+     */
+    private Toast getClickableToast(Context context) {
+        try {
+            Toast toast = new Toast(context);
+            Field tnField = Toast.class.getDeclaredField("mTN");
+            tnField.setAccessible(true);
+            Object tnObject = tnField.get(toast);
+            Field paramsField = Class.forName(
+                    Toast.class.getCanonicalName() + "$TN").getDeclaredField("mParams");
+            paramsField.setAccessible(true);
+            LayoutParams params = (LayoutParams) paramsField.get(tnObject);
+            params.flags = LayoutParams.FLAG_KEEP_SCREEN_ON | LayoutParams.FLAG_NOT_FOCUSABLE;
+            return toast;
+        } catch (NoSuchFieldException | IllegalAccessException | ClassNotFoundException e) {
+            throw new IllegalStateException("Toast reflection failed", e);
+        }
+    }
+
+    private static class DetectToastRunnable implements Runnable {
+        private final Context mContext;
+        private final WeakReference<View> mToastViewRef;
+        private final long mDeadline;
+        private final Handler mHandler;
+
+        private DetectToastRunnable(
+                Context applicationContext, View toastView, long deadline, Handler handler) {
+            mContext = applicationContext;
+            mToastViewRef = new WeakReference<>(toastView);
+            mDeadline = deadline;
+            mHandler = handler;
+        }
+
+        @Override
+        public void run() {
+            View toastView = mToastViewRef.get();
+            if (SystemClock.uptimeMillis() > mDeadline || toastView == null) {
+                return;
+            }
+            if (toastView.getParent() != null) {
+                mContext.sendBroadcast(new Intent(ACTION_TOAST_DISPLAYED));
+                return;
+            }
+            mHandler.postDelayed(this, DETECT_TOAST_POOLING_INTERVAL_MS);
+        }
+    }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ToastTest.java b/tests/framework/base/windowmanager/src/android/server/wm/ToastTest.java
new file mode 100644
index 0000000..253ade4
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ToastTest.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm;
+
+import static android.server.wm.app.Components.ToastReceiver.ACTION_TOAST_DISPLAYED;
+import static android.server.wm.app.Components.ToastReceiver.ACTION_TOAST_TAP_DETECTED;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.Looper;
+import android.platform.test.annotations.Presubmit;
+import android.provider.Settings;
+import android.server.wm.WindowManagerState.WindowState;
+import android.server.wm.app.Components;
+import android.view.WindowManager.LayoutParams;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.annotation.Nullable;
+
+@Presubmit
+public class ToastTest extends ActivityManagerTestBase {
+    private static final String SETTING_HIDDEN_API_POLICY = "hidden_api_policy";
+    private static final long TOAST_DISPLAY_TIMEOUT_MS = 8000;
+    private static final long TOAST_TAP_TIMEOUT_MS = 3500;
+
+    /**
+     * Tests can be executed as soon as the device has booted. When that happens the broadcast queue
+     * is long and it takes some time to process the broadcast we just sent.
+     */
+    private static final long BROADCAST_DELIVERY_TIMEOUT_MS = 60000;
+
+    @Nullable
+    private String mPreviousHiddenApiPolicy;
+    private Map<String, ConditionVariable> mBroadcastsReceived;
+
+    private BroadcastReceiver mAppCommunicator = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+             getBroadcastReceivedVariable(intent.getAction()).open();
+        }
+    };
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            ContentResolver resolver = mContext.getContentResolver();
+            mPreviousHiddenApiPolicy = Settings.Global.getString(resolver,
+                    SETTING_HIDDEN_API_POLICY);
+            Settings.Global.putString(resolver, SETTING_HIDDEN_API_POLICY, "1");
+        });
+        // Stopping just in case, to make sure reflection is allowed
+        stopTestPackage(Components.getPackageName());
+
+        // These are parallel broadcasts, not affected by a busy queue
+        mBroadcastsReceived = Collections.synchronizedMap(new HashMap<>());
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(ACTION_TOAST_DISPLAYED);
+        filter.addAction(ACTION_TOAST_TAP_DETECTED);
+        mContext.registerReceiver(mAppCommunicator, filter);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        mContext.unregisterReceiver(mAppCommunicator);
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            Settings.Global.putString(mContext.getContentResolver(), SETTING_HIDDEN_API_POLICY,
+                    mPreviousHiddenApiPolicy);
+        });
+        super.tearDown();
+    }
+
+    @Test
+    public void testToastIsNotClickable() {
+        Intent intent = new Intent();
+        intent.setComponent(Components.TOAST_RECEIVER);
+        sendAndWaitForBroadcast(intent);
+        boolean toastDisplayed = getBroadcastReceivedVariable(ACTION_TOAST_DISPLAYED).block(
+                TOAST_DISPLAY_TIMEOUT_MS);
+        assertTrue("Toast not displayed on time", toastDisplayed);
+        WindowManagerState wmState = getAmWmState().getWmState();
+        wmState.computeState();
+        WindowState toastWindow = wmState.findFirstWindowWithType(LayoutParams.TYPE_TOAST);
+        assertNotNull("Couldn't retrieve toast window", toastWindow);
+
+        tapOnCenter(toastWindow.getContainingFrame(), toastWindow.getDisplayId());
+
+        boolean toastClicked = getBroadcastReceivedVariable(ACTION_TOAST_TAP_DETECTED).block(
+                TOAST_TAP_TIMEOUT_MS);
+        assertFalse("Toast tap detected", toastClicked);
+    }
+
+    private void sendAndWaitForBroadcast(Intent intent) {
+        assertNotEquals("Can't wait on main thread", Thread.currentThread(),
+                Looper.getMainLooper().getThread());
+
+        ConditionVariable broadcastDelivered = new ConditionVariable(false);
+        mContext.sendOrderedBroadcast(
+                intent,
+                null,
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        broadcastDelivered.open();
+                    }
+                },
+                new Handler(Looper.getMainLooper()),
+                Activity.RESULT_OK,
+                null,
+                null);
+        broadcastDelivered.block(BROADCAST_DELIVERY_TIMEOUT_MS);
+    }
+
+    private ConditionVariable getBroadcastReceivedVariable(String action) {
+        return mBroadcastsReceived.computeIfAbsent(action, key -> new ConditionVariable());
+    }
+}
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java
index f7337e9..c99764e 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java
@@ -500,11 +500,14 @@
         injectMotion(downTime, upTime, MotionEvent.ACTION_UP, x, y, displayId);
     }
 
+    protected void tapOnCenter(Rect bounds, int displayId) {
+        final int tapX = bounds.left + bounds.width() / 2;
+        final int tapY = bounds.top + bounds.height() / 2;
+        tapOnDisplay(tapX, tapY, displayId);
+    }
+
     protected void tapOnStackCenter(ActivityManagerState.ActivityStack stack) {
-        final Rect sideStackBounds = stack.getBounds();
-        final int tapX = sideStackBounds.left + sideStackBounds.width() / 2;
-        final int tapY = sideStackBounds.top + sideStackBounds.height() / 2;
-        tapOnDisplay(tapX, tapY, stack.mDisplayId);
+        tapOnCenter(stack.getBounds(), stack.mDisplayId);
     }
 
     private static void injectMotion(long downTime, long eventTime, int action,