diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index cf3538c592d9b..e02b1ecaef328 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -15,6 +15,7 @@
package com.android.systemui.globalactions;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS;
import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
@@ -485,6 +486,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
attrs.setTitle("ActionsDialog");
attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
mDialog.getWindow().setAttributes(attrs);
+ // Don't acquire soft keyboard focus, to avoid destroying state when capturing bugreports
+ mDialog.getWindow().setFlags(FLAG_ALT_FOCUSABLE_IM, FLAG_ALT_FOCUSABLE_IM);
mDialog.show();
mWindowManagerFuncs.onGlobalActionsShown();
}
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index 12c704881909e..9c94b8693b9ff 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -69,6 +69,11 @@
android:exported="false"
android:resizeableActivity="true" />
+
+
mActivityTestRule = new ActivityTestRule<>(
+ TestActivity.class, false, false);
+
+ /**
+ * This test verifies that GlobalActions, which is frequently used to capture bugreports,
+ * doesn't interfere with the IME, i.e. soft-keyboard state.
+ */
+ @Test
+ public void testGlobalActions_doesntStealImeControl() {
+ final TestActivity activity = mActivityTestRule.launchActivity(null);
+
+ activity.waitFor(() -> activity.mHasFocus && activity.mControlsIme && activity.mImeVisible);
+
+ InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+ "input keyevent --longpress POWER"
+ );
+
+ activity.waitFor(() -> !activity.mHasFocus);
+ // Give the dialog time to animate in, and steal IME focus. Unfortunately, there's currently
+ // no better way to wait for this.
+ SystemClock.sleep(TimeUnit.SECONDS.toMillis(2));
+
+ runAssertionOnMainThread(() -> {
+ assertTrue("IME should remain visible behind GlobalActions, but didn't",
+ activity.mControlsIme);
+ assertTrue("App behind GlobalActions should remain in control of IME, but didn't",
+ activity.mImeVisible);
+ });
+ }
+
+ /** Like Instrumentation.runOnMainThread(), but forwards AssertionErrors to the caller. */
+ private static void runAssertionOnMainThread(Runnable r) {
+ AssertionError[] t = new AssertionError[1];
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ try {
+ r.run();
+ } catch (AssertionError e) {
+ t[0] = e;
+ // Ignore assertion - throwing it here would crash the main thread.
+ }
+ });
+ if (t[0] != null) {
+ throw t[0];
+ }
+ }
+
+ public static class TestActivity extends Activity implements
+ WindowInsetsController.OnControllableInsetsChangedListener,
+ View.OnApplyWindowInsetsListener {
+
+ private EditText mContent;
+ boolean mHasFocus;
+ boolean mControlsIme;
+ boolean mImeVisible;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mContent = new EditText(this);
+ mContent.setCursorVisible(false); // Otherwise, main thread doesn't go idle.
+ setContentView(mContent);
+ mContent.requestFocus();
+
+ getWindow().getDecorView().setOnApplyWindowInsetsListener(this);
+ WindowInsetsController wic = mContent.getWindowInsetsController();
+ wic.addOnControllableInsetsChangedListener(this);
+ wic.show(ime());
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+ synchronized (this) {
+ mHasFocus = hasFocus;
+ notifyAll();
+ }
+ }
+
+ @Override
+ public void onControllableInsetsChanged(@NonNull WindowInsetsController controller,
+ int typeMask) {
+ synchronized (this) {
+ mControlsIme = (typeMask & ime()) != 0;
+ notifyAll();
+ }
+ }
+
+ void waitFor(BooleanSupplier condition) {
+ synchronized (this) {
+ while (!condition.getAsBoolean()) {
+ try {
+ wait(TimeUnit.SECONDS.toMillis(5));
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ }
+
+ @Override
+ public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
+ mImeVisible = insets.isVisible(ime());
+ return v.onApplyWindowInsets(insets);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/policy/LegacyGlobalActions.java b/services/core/java/com/android/server/policy/LegacyGlobalActions.java
index 6eba59acbc94f..39f7ac0b5b54b 100644
--- a/services/core/java/com/android/server/policy/LegacyGlobalActions.java
+++ b/services/core/java/com/android/server/policy/LegacyGlobalActions.java
@@ -16,6 +16,8 @@
package com.android.server.policy;
+import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+
import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -336,6 +338,8 @@ class LegacyGlobalActions implements DialogInterface.OnDismissListener, DialogIn
}
});
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+ // Don't acquire soft keyboard focus, to avoid destroying state when capturing bugreports
+ mDialog.getWindow().setFlags(FLAG_ALT_FOCUSABLE_IM, FLAG_ALT_FOCUSABLE_IM);
dialog.setOnDismissListener(this);