Prevent apps to overlay other apps via toast windows - CTS

bug:30150688

Change-Id: I8423bc8eeab56dad345d743ac381c4f8b88f3541
diff --git a/tests/tests/toast/Android.mk b/tests/tests/toast/Android.mk
new file mode 100644
index 0000000..d9f15eb
--- /dev/null
+++ b/tests/tests/toast/Android.mk
@@ -0,0 +1,33 @@
+#
+# Copyright (C) 2016 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.
+#
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner android-support-test
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsToastTestCases
+
+include $(BUILD_CTS_PACKAGE)
+
diff --git a/tests/tests/toast/AndroidManifest.xml b/tests/tests/toast/AndroidManifest.xml
new file mode 100644
index 0000000..1fa71c4
--- /dev/null
+++ b/tests/tests/toast/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+      package="android.widget.toast.cts">
+
+    <uses-sdk android:minSdkVersion="25" android:targetSdkVersion="26" />
+
+    <application>
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.widget.toast.cts"
+        android:label="CTS tests for toast windows">
+        <meta-data android:name="listener"
+            android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+
+</manifest>
+
diff --git a/tests/tests/toast/AndroidTest.xml b/tests/tests/toast/AndroidTest.xml
new file mode 100644
index 0000000..d0a5eed
--- /dev/null
+++ b/tests/tests/toast/AndroidTest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+<configuration description="Config for Toast test cases">
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.ApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsToastTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.widget.toast.cts" />
+    </test>
+</configuration>
diff --git a/tests/tests/toast/src/android/widget/toast/cts/BaseToastTest.java b/tests/tests/toast/src/android/widget/toast/cts/BaseToastTest.java
new file mode 100644
index 0000000..fd75309
--- /dev/null
+++ b/tests/tests/toast/src/android/widget/toast/cts/BaseToastTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2016 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.widget.toast.cts;
+
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.view.WindowManager;
+import android.widget.TextView;
+import android.widget.Toast;
+import org.junit.Before;
+
+/**
+ * Base class for toast tests.
+ */
+public abstract class BaseToastTest {
+    protected static final long TOAST_TIMEOUT_MILLIS = 5000; // 5 sec
+    protected static final long EVENT_TIMEOUT_MILLIS = 5000; // 5 sec
+
+    protected Context mContext;
+    protected Instrumentation mInstrumentation;
+    protected UiAutomation mUiAutomation;
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getContext();
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mUiAutomation = mInstrumentation.getUiAutomation();
+        waitForToastTimeout();
+    }
+
+    protected void waitForToastTimeout() {
+        SystemClock.sleep(TOAST_TIMEOUT_MILLIS);
+    }
+
+    protected void showToastsViaToastApis(int count) throws Exception {
+        Exception[] exceptions = new Exception[1];
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    try {
+                        for (int i = 0; i < count; i++) {
+                            Toast.makeText(mContext, getClass().getName(),
+                                    Toast.LENGTH_LONG).show();
+                        }
+                    } catch (Exception e) {
+                        exceptions[0] = e;
+                    }
+                });
+        if (exceptions[0] != null) {
+            throw exceptions[0];
+        }
+    }
+
+    protected void showToastsViaAddingWindow(int count, boolean focusable) throws Exception {
+        Exception[] exceptions = new Exception[1];
+        mInstrumentation.runOnMainSync(() -> {
+            try {
+                for (int i = 0; i < count; i++) {
+                    WindowManager.LayoutParams params = new WindowManager.LayoutParams();
+                    params.height = WindowManager.LayoutParams.WRAP_CONTENT;
+                    params.width = WindowManager.LayoutParams.WRAP_CONTENT;
+                    params.format = PixelFormat.TRANSLUCENT;
+                    params.type = WindowManager.LayoutParams.TYPE_TOAST;
+                    params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
+                    if (!focusable) {
+                        params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+                    }
+
+                    TextView textView = new TextView(mContext);
+                    textView.setText(BaseToastTest.class.getName());
+
+                    WindowManager windowManager = mContext
+                            .getSystemService(WindowManager.class);
+                    windowManager.addView(textView, params);
+                }
+            } catch (Exception e) {
+                exceptions[0] = e;
+            }
+        });
+        if (exceptions[0] != null) {
+            throw exceptions[0];
+        }
+    }
+}
diff --git a/tests/tests/toast/src/android/widget/toast/cts/LegacyToastTest.java b/tests/tests/toast/src/android/widget/toast/cts/LegacyToastTest.java
new file mode 100644
index 0000000..4ac88b3
--- /dev/null
+++ b/tests/tests/toast/src/android/widget/toast/cts/LegacyToastTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2016 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.widget.toast.cts;
+
+import android.support.test.runner.AndroidJUnit4;
+import android.view.WindowManager;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.fail;
+
+/**
+ * Test whether toasts are properly shown. For apps targeting API 25+
+ * like this app the only way to add toast windows is via the dedicated
+ * toast APIs.
+ */
+@RunWith(AndroidJUnit4.class)
+public class LegacyToastTest extends BaseToastTest {
+    @Test
+    public void testAddSingleToastViaTestApisWhenUidFocused() throws Exception {
+        // Normal toast windows cannot be obtained vie the accessibility APIs because
+        // they are not touchable. In this case not crashing is good enough.
+        showToastsViaToastApis(1);
+    }
+
+    @Test
+    public void testAddTwoToastViaTestApisWhenUidFocused() throws Exception {
+        // Normal toast windows cannot be obtained vie the accessibility APIs because
+        // they are not touchable. In this case not crashing is good enough.
+        showToastsViaToastApis(2);
+
+        // Wait for the first one to expire
+        waitForToastTimeout();
+    }
+
+    @Test
+    public void testAddSingleToastViaAddingWindowApisWhenUidFocused() throws Exception {
+        try {
+            showToastsViaAddingWindow(1, false);
+            fail("Shouldn't be able to add toast windows directly");
+        } catch (WindowManager.BadTokenException e) {
+            /* expected */
+        }
+    }
+}
diff --git a/tests/tests/toastlegacy/Android.mk b/tests/tests/toastlegacy/Android.mk
new file mode 100644
index 0000000..ac5825d
--- /dev/null
+++ b/tests/tests/toastlegacy/Android.mk
@@ -0,0 +1,34 @@
+#
+# Copyright (C) 2016 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.
+#
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner android-support-test
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+    ../toast/src/android/widget/toast/cts/BaseToastTest.java
+
+LOCAL_PACKAGE_NAME := CtsToastLegacyTestCases
+
+include $(BUILD_CTS_PACKAGE)
+
diff --git a/tests/tests/toastlegacy/AndroidManifest.xml b/tests/tests/toastlegacy/AndroidManifest.xml
new file mode 100644
index 0000000..8529b5c
--- /dev/null
+++ b/tests/tests/toastlegacy/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+      package="android.widget.toast.legacy.cts">
+
+    <uses-sdk android:minSdkVersion="25" android:targetSdkVersion="25" />
+
+    <application>
+        <activity android:name="android.widget.toast.cts.legacy.ToastActivity">
+        </activity>
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.widget.toast.legacy.cts"
+        android:label="CTS tests for legacy toast windows">
+        <meta-data android:name="listener"
+            android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+
+</manifest>
+
diff --git a/tests/tests/toastlegacy/AndroidTest.xml b/tests/tests/toastlegacy/AndroidTest.xml
new file mode 100644
index 0000000..a208077
--- /dev/null
+++ b/tests/tests/toastlegacy/AndroidTest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+<configuration description="Config for Toast legacy test cases">
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.ApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsToastLegacyTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.widget.toast.legacy.cts" />
+    </test>
+</configuration>
diff --git a/tests/tests/toastlegacy/src/android/widget/toast/cts/legacy/ToastActivity.java b/tests/tests/toastlegacy/src/android/widget/toast/cts/legacy/ToastActivity.java
new file mode 100644
index 0000000..745385e
--- /dev/null
+++ b/tests/tests/toastlegacy/src/android/widget/toast/cts/legacy/ToastActivity.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2016 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.widget.toast.cts.legacy;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Activity that shows toasts on demand.
+ */
+public class ToastActivity extends Activity {
+
+    @Override
+    public void onCreate(Bundle bundle) {
+        super.onCreate(bundle);
+        View view = new View(this);
+        view.setBackgroundColor(Color.BLUE);
+        view.setLayoutParams(new ViewGroup.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.MATCH_PARENT));
+        setContentView(view);
+    }
+}
diff --git a/tests/tests/toastlegacy/src/android/widget/toast/cts/legacy/ToastTest.java b/tests/tests/toastlegacy/src/android/widget/toast/cts/legacy/ToastTest.java
new file mode 100644
index 0000000..207e6ea
--- /dev/null
+++ b/tests/tests/toastlegacy/src/android/widget/toast/cts/legacy/ToastTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2016 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.widget.toast.cts.legacy;
+
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.toast.cts.BaseToastTest;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test whether toasts are properly shown. For apps targeting SDK
+ * 25 and below a toast window can be added via the window APIs
+ * but it will be removed after a timeout if the UID that added
+ * the window is not focused. Also only a single toast window
+ * is allowed at a time.
+ */
+@RunWith(AndroidJUnit4.class)
+public class ToastTest extends BaseToastTest {
+    @Rule
+    public final ActivityTestRule<ToastActivity> mActivityRule =
+            new ActivityTestRule<>(ToastActivity.class);
+
+    @Test
+    public void testAddSingleNotFocusableToastViaAddingWindowApisWhenUidFocused() throws Exception {
+        // Show a toast on top of the focused activity
+        showToastsViaAddingWindow(1, false);
+
+        // Wait for the toast to timeout
+        waitForToastTimeout();
+
+        // Finish the activity so the UID loses focus
+        finishActivity(false);
+
+        // Wait for the toast to timeout
+        waitForToastTimeout();
+
+        // Show another toast
+        showToastsViaAddingWindow(1, false);
+    }
+
+    @Test
+    public void testAddSingleFocusableToastViaAddingWindowApisWhenUidFocused() throws Exception {
+        // Show a toast on top of our activity
+        showToastsViaAddingWindow(1, true);
+
+        // Wait for the toast to timeout
+        waitForToastTimeout();
+
+        // Show a toast on top of our activity
+        showToastsViaAddingWindow(1, true);
+    }
+
+    @Test
+    public void testAddSingleToastViaAddingWindowApisWhenUidNotFocused() throws Exception {
+        // Finish the activity so the UID loses focus
+        finishActivity(false);
+
+        // Show a toast
+        showToastsViaAddingWindow(1, true);
+
+        // Wait for the toast to timeout
+        waitForToastTimeout();
+
+        // Show a toast on top of our activity
+        showToastsViaAddingWindow(1, true);
+    }
+
+    @Test
+    public void testAddTwoToastsViaToastApisWhenUidFocused() throws Exception {
+        // Finish the activity so the UID loses focus
+        finishActivity(false);
+
+        // Normal toast windows cannot be obtained vie the accessibility APIs because
+        // they are not touchable. In this case not crashing is good enough.
+        showToastsViaToastApis(2);
+
+        // Wait for the first one to expire
+        waitForToastTimeout();
+    }
+
+    @Test
+    public void testAddTwoToastsViaToastApisWhenUidNotFocused() throws Exception {
+        // Normal toast windows cannot be obtained vie the accessibility APIs because
+        // they are not touchable. In this case not crashing is good enough.
+        showToastsViaToastApis(2);
+
+        // Wait for the first one to expire
+        waitForToastTimeout();
+    }
+
+    @Test
+    public void testAddTwoToastsViaAddingWindowApisWhenUidFocusedQuickly() throws Exception {
+        try {
+            showToastsViaAddingWindow(2, false);
+            Assert.fail("Only one custom toast window at a time should be allowed");
+        } catch (WindowManager.BadTokenException e) {
+            /* expected */
+        } catch (Exception ex) {
+            Assert.fail("Unexpected exception when adding second toast window" + ex);
+        }
+    }
+
+    @Test
+    public void testAddTwoToastsViaAddingWindowApisWhenUidFocusedSlowly() throws Exception {
+        // Add one window
+        showToastsViaAddingWindow(1, true);
+
+        // Wait for the toast to timeout
+        waitForToastTimeout();
+
+        // Add another window
+        showToastsViaAddingWindow(1, true);
+    }
+
+    private void finishActivity(boolean waitForEvent) throws Exception {
+        if (waitForEvent) {
+            mUiAutomation.executeAndWaitForEvent(
+                    () -> mActivityRule.getActivity().finish(),
+                    (event) -> event.getEventType() == AccessibilityEvent.TYPE_WINDOWS_CHANGED
+                    , EVENT_TIMEOUT_MILLIS);
+        } else {
+            mActivityRule.getActivity().finish();
+        }
+    }
+}