1 /* 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 17 package com.android.systemui.globalactions; 18 19 import static android.provider.Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD; 20 import static android.view.WindowInsets.Type.ime; 21 22 import static org.junit.Assert.assertEquals; 23 import static org.junit.Assert.assertNotNull; 24 import static org.junit.Assert.assertTrue; 25 import static org.junit.Assert.fail; 26 27 import android.app.Activity; 28 import android.content.ContentResolver; 29 import android.os.Bundle; 30 import android.os.PowerManager; 31 import android.os.SystemClock; 32 import android.provider.Settings; 33 import android.view.View; 34 import android.view.WindowInsets; 35 import android.view.WindowInsetsController; 36 import android.widget.EditText; 37 38 import androidx.annotation.NonNull; 39 import androidx.annotation.Nullable; 40 import androidx.test.filters.FlakyTest; 41 import androidx.test.filters.LargeTest; 42 import androidx.test.platform.app.InstrumentationRegistry; 43 import androidx.test.rule.ActivityTestRule; 44 45 import com.android.systemui.SysuiTestCase; 46 47 import org.junit.After; 48 import org.junit.Before; 49 import org.junit.Rule; 50 import org.junit.Test; 51 52 import java.util.concurrent.TimeUnit; 53 import java.util.function.BooleanSupplier; 54 55 @LargeTest 56 @FlakyTest(bugId = 176891566) 57 public class GlobalActionsImeTest extends SysuiTestCase { 58 59 @Rule 60 public ActivityTestRule<TestActivity> mActivityTestRule = new ActivityTestRule<>( 61 TestActivity.class, false, false); 62 63 private int mOriginalShowImeWithHardKeyboard; 64 65 @Before setUp()66 public void setUp() { 67 final ContentResolver contentResolver = mContext.getContentResolver(); 68 mOriginalShowImeWithHardKeyboard = Settings.Secure.getInt( 69 contentResolver, SHOW_IME_WITH_HARD_KEYBOARD, 0); 70 // Forcibly shows IME even when hardware keyboard is connected. 71 // To change USER_SYSTEM settings, we have to use settings shell command. 72 executeShellCommand("settings put secure " + SHOW_IME_WITH_HARD_KEYBOARD + " 1"); 73 } 74 75 @After tearDown()76 public void tearDown() { 77 // To restore USER_SYSTEM settings, we have to use settings shell command. 78 executeShellCommand("settings put secure " 79 + SHOW_IME_WITH_HARD_KEYBOARD + " " + mOriginalShowImeWithHardKeyboard); 80 // Hide power menu and return to home screen 81 executeShellCommand("input keyevent --longpress POWER"); 82 executeShellCommand("input keyevent HOME"); 83 } 84 85 /** 86 * This test verifies that GlobalActions, which is frequently used to capture bugreports, 87 * doesn't interfere with the IME, i.e. soft-keyboard state. 88 */ 89 @Test testGlobalActions_doesntStealImeControl()90 public void testGlobalActions_doesntStealImeControl() throws Exception { 91 turnScreenOn(); 92 final TestActivity activity = mActivityTestRule.launchActivity(null); 93 boolean isImeVisible = waitUntil(activity::isImeVisible); 94 if (!isImeVisible) { 95 // Sometimes the keyboard is dismissed when run with other tests. Bringing it up again 96 // should improve test reliability 97 activity.showIme(); 98 waitUntil("Ime is not visible", activity::isImeVisible); 99 } 100 101 // In some cases, IME is not controllable. e.g., floating IME or fullscreen IME. 102 final boolean activityControlledIme = activity.mControlsIme; 103 104 executeShellCommand("input keyevent --longpress POWER"); 105 106 waitUntil("activity loses focus", () -> !activity.mHasFocus); 107 // Give the dialog time to animate in, and steal IME focus. Unfortunately, there's currently 108 // no better way to wait for this. 109 SystemClock.sleep(TimeUnit.SECONDS.toMillis(2)); 110 111 runAssertionOnMainThread(() -> { 112 assertTrue("IME should remain visible behind GlobalActions, but didn't", 113 activity.mImeVisible); 114 assertEquals("App behind GlobalActions should remain in control of IME, but didn't", 115 activityControlledIme, activity.mControlsIme); 116 }); 117 } 118 turnScreenOn()119 private void turnScreenOn() throws Exception { 120 PowerManager powerManager = mContext.getSystemService(PowerManager.class); 121 assertNotNull(powerManager); 122 if (powerManager.isInteractive()) { 123 return; 124 } 125 executeShellCommand("input keyevent KEYCODE_WAKEUP"); 126 waitUntil("Device not interactive", powerManager::isInteractive); 127 executeShellCommand("am wait-for-broadcast-idle"); 128 } 129 waitUntil(String message, BooleanSupplier predicate)130 private static void waitUntil(String message, BooleanSupplier predicate) 131 throws Exception { 132 if (!waitUntil(predicate)) { 133 fail(message); 134 } 135 } 136 waitUntil(BooleanSupplier predicate)137 private static boolean waitUntil(BooleanSupplier predicate) throws Exception { 138 int sleep = 125; 139 final long timeout = SystemClock.uptimeMillis() + 10_000; // 10 second timeout 140 while (SystemClock.uptimeMillis() < timeout) { 141 if (predicate.getAsBoolean()) { 142 return true; 143 } 144 Thread.sleep(sleep); 145 sleep *= 5; 146 sleep = Math.min(2000, sleep); 147 } 148 return false; 149 } 150 executeShellCommand(String cmd)151 private static void executeShellCommand(String cmd) { 152 InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(cmd); 153 } 154 155 /** 156 * Like Instrumentation.runOnMainThread(), but forwards AssertionErrors to the caller. 157 */ runAssertionOnMainThread(Runnable r)158 private static void runAssertionOnMainThread(Runnable r) { 159 AssertionError[] t = new AssertionError[1]; 160 InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { 161 try { 162 r.run(); 163 } catch (AssertionError e) { 164 t[0] = e; 165 // Ignore assertion - throwing it here would crash the main thread. 166 } 167 }); 168 if (t[0] != null) { 169 throw t[0]; 170 } 171 } 172 173 public static class TestActivity extends Activity implements 174 WindowInsetsController.OnControllableInsetsChangedListener, 175 View.OnApplyWindowInsetsListener { 176 177 private EditText mEditText; 178 boolean mHasFocus; 179 boolean mControlsIme; 180 boolean mImeVisible; 181 182 @Override onCreate(@ullable Bundle savedInstanceState)183 protected void onCreate(@Nullable Bundle savedInstanceState) { 184 super.onCreate(savedInstanceState); 185 setShowWhenLocked(true); // Allow this test to work even if device got stuck on keyguard 186 mEditText = new EditText(this); 187 mEditText.setCursorVisible(false); // Otherwise, main thread doesn't go idle. 188 setContentView(mEditText); 189 showIme(); 190 } 191 showIme()192 private void showIme() { 193 mEditText.requestFocus(); 194 getWindow().getDecorView().setOnApplyWindowInsetsListener(this); 195 WindowInsetsController wic = mEditText.getWindowInsetsController(); 196 wic.addOnControllableInsetsChangedListener(this); 197 wic.show(ime()); 198 } 199 200 @Override onWindowFocusChanged(boolean hasFocus)201 public void onWindowFocusChanged(boolean hasFocus) { 202 synchronized (this) { 203 mHasFocus = hasFocus; 204 notifyAll(); 205 } 206 } 207 208 @Override onControllableInsetsChanged(@onNull WindowInsetsController controller, int typeMask)209 public void onControllableInsetsChanged(@NonNull WindowInsetsController controller, 210 int typeMask) { 211 synchronized (this) { 212 mControlsIme = (typeMask & ime()) != 0; 213 notifyAll(); 214 } 215 } 216 isImeVisible()217 boolean isImeVisible() { 218 return mHasFocus && mImeVisible; 219 } 220 221 @Override onApplyWindowInsets(View v, WindowInsets insets)222 public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { 223 mImeVisible = insets.isVisible(ime()); 224 return v.onApplyWindowInsets(insets); 225 } 226 } 227 } 228