From 284b32dc32ba6bfdf37ecee8d2e4d4d781fb528e Mon Sep 17 00:00:00 2001 From: Adrian Roos Date: Fri, 8 May 2020 14:26:13 +0200 Subject: [PATCH] GlobalActions: Disable IME focusability GlobalActions is a frequent path to take bugreports; this prevents it from acquiring the IME focus, which destroys the IME state and makes IME-related bugreports frequently useless. Fixes: 156071729 Test: atest GlobalActionsImeTest Test: Click any text field, long-press power, verify keyboard is still open (behind the power menu) Change-Id: I396006f3e240c9e410dfa0dab60508d9e2a5ef5d --- .../globalactions/GlobalActionsDialog.java | 3 + packages/SystemUI/tests/AndroidManifest.xml | 5 + .../globalactions/GlobalActionsImeTest.java | 152 ++++++++++++++++++ .../server/policy/LegacyGlobalActions.java | 4 + 4 files changed, 164 insertions(+) create mode 100644 packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java 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);