blob: eb43b8172c16b75f60bcb6da44841c371d87cbca [file] [log] [blame]
Adrian Roos284b32d2020-05-08 14:26:13 +02001/*
2 * Copyright (C) 2020 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
17package com.android.systemui.globalactions;
18
19import static android.view.WindowInsets.Type.ime;
20
21import static org.junit.Assert.assertTrue;
22
23import android.app.Activity;
24import android.os.Bundle;
25import android.os.SystemClock;
26import android.view.View;
27import android.view.WindowInsets;
28import android.view.WindowInsetsController;
29import android.widget.EditText;
30
31import androidx.annotation.NonNull;
32import androidx.annotation.Nullable;
33import androidx.test.filters.LargeTest;
34import androidx.test.platform.app.InstrumentationRegistry;
35import androidx.test.rule.ActivityTestRule;
36
Adrian Roos2b50ff12020-05-12 15:47:42 +000037import com.android.systemui.SysuiTestCase;
38
Adrian Roos284b32d2020-05-08 14:26:13 +020039import org.junit.Rule;
40import org.junit.Test;
41
42import java.util.concurrent.TimeUnit;
43import java.util.function.BooleanSupplier;
44
45@LargeTest
Adrian Roos2b50ff12020-05-12 15:47:42 +000046public class GlobalActionsImeTest extends SysuiTestCase {
Adrian Roos284b32d2020-05-08 14:26:13 +020047
48 @Rule
49 public ActivityTestRule<TestActivity> mActivityTestRule = new ActivityTestRule<>(
50 TestActivity.class, false, false);
51
52 /**
53 * This test verifies that GlobalActions, which is frequently used to capture bugreports,
54 * doesn't interfere with the IME, i.e. soft-keyboard state.
55 */
56 @Test
57 public void testGlobalActions_doesntStealImeControl() {
58 final TestActivity activity = mActivityTestRule.launchActivity(null);
59
60 activity.waitFor(() -> activity.mHasFocus && activity.mControlsIme && activity.mImeVisible);
61
62 InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
63 "input keyevent --longpress POWER"
64 );
65
66 activity.waitFor(() -> !activity.mHasFocus);
67 // Give the dialog time to animate in, and steal IME focus. Unfortunately, there's currently
68 // no better way to wait for this.
69 SystemClock.sleep(TimeUnit.SECONDS.toMillis(2));
70
71 runAssertionOnMainThread(() -> {
72 assertTrue("IME should remain visible behind GlobalActions, but didn't",
73 activity.mControlsIme);
74 assertTrue("App behind GlobalActions should remain in control of IME, but didn't",
75 activity.mImeVisible);
76 });
77 }
78
79 /** Like Instrumentation.runOnMainThread(), but forwards AssertionErrors to the caller. */
80 private static void runAssertionOnMainThread(Runnable r) {
81 AssertionError[] t = new AssertionError[1];
82 InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
83 try {
84 r.run();
85 } catch (AssertionError e) {
86 t[0] = e;
87 // Ignore assertion - throwing it here would crash the main thread.
88 }
89 });
90 if (t[0] != null) {
91 throw t[0];
92 }
93 }
94
95 public static class TestActivity extends Activity implements
96 WindowInsetsController.OnControllableInsetsChangedListener,
97 View.OnApplyWindowInsetsListener {
98
99 private EditText mContent;
100 boolean mHasFocus;
101 boolean mControlsIme;
102 boolean mImeVisible;
103
104 @Override
105 protected void onCreate(@Nullable Bundle savedInstanceState) {
106 super.onCreate(savedInstanceState);
107
108 mContent = new EditText(this);
109 mContent.setCursorVisible(false); // Otherwise, main thread doesn't go idle.
110 setContentView(mContent);
111 mContent.requestFocus();
112
113 getWindow().getDecorView().setOnApplyWindowInsetsListener(this);
114 WindowInsetsController wic = mContent.getWindowInsetsController();
115 wic.addOnControllableInsetsChangedListener(this);
116 wic.show(ime());
117 }
118
119 @Override
120 public void onWindowFocusChanged(boolean hasFocus) {
121 synchronized (this) {
122 mHasFocus = hasFocus;
123 notifyAll();
124 }
125 }
126
127 @Override
128 public void onControllableInsetsChanged(@NonNull WindowInsetsController controller,
129 int typeMask) {
130 synchronized (this) {
131 mControlsIme = (typeMask & ime()) != 0;
132 notifyAll();
133 }
134 }
135
136 void waitFor(BooleanSupplier condition) {
137 synchronized (this) {
138 while (!condition.getAsBoolean()) {
139 try {
140 wait(TimeUnit.SECONDS.toMillis(5));
141 } catch (InterruptedException e) {
142 throw new RuntimeException(e);
143 }
144 }
145 }
146 }
147
148 @Override
149 public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
150 mImeVisible = insets.isVisible(ime());
151 return v.onApplyWindowInsets(insets);
152 }
153 }
154}