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