Loading packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +3 −0 Original line number Original line Diff line number Diff line Loading @@ -15,6 +15,7 @@ package com.android.systemui.globalactions; package com.android.systemui.globalactions; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; 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.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS; import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS; import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN; import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN; Loading Loading @@ -485,6 +486,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, attrs.setTitle("ActionsDialog"); attrs.setTitle("ActionsDialog"); attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; mDialog.getWindow().setAttributes(attrs); 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(); mDialog.show(); mWindowManagerFuncs.onGlobalActionsShown(); mWindowManagerFuncs.onGlobalActionsShown(); } } Loading packages/SystemUI/tests/AndroidManifest.xml +5 −0 Original line number Original line Diff line number Diff line Loading @@ -69,6 +69,11 @@ android:exported="false" android:exported="false" android:resizeableActivity="true" /> android:resizeableActivity="true" /> <activity android:name="com.android.systemui.globalactions.GlobalActionsImeTest$TestActivity" android:excludeFromRecents="true" android:exported="false" /> <provider <provider android:name="androidx.lifecycle.ProcessLifecycleOwnerInitializer" android:name="androidx.lifecycle.ProcessLifecycleOwnerInitializer" tools:replace="android:authorities" tools:replace="android:authorities" Loading packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java 0 → 100644 +152 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.globalactions; import static android.view.WindowInsets.Type.ime; import static org.junit.Assert.assertTrue; import android.app.Activity; import android.os.Bundle; import android.os.SystemClock; import android.view.View; import android.view.WindowInsets; import android.view.WindowInsetsController; import android.widget.EditText; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.test.filters.LargeTest; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.rule.ActivityTestRule; import org.junit.Rule; import org.junit.Test; import java.util.concurrent.TimeUnit; import java.util.function.BooleanSupplier; @LargeTest public class GlobalActionsImeTest { @Rule public ActivityTestRule<TestActivity> 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); } } } services/core/java/com/android/server/policy/LegacyGlobalActions.java +4 −0 Original line number Original line Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.server.policy; package com.android.server.policy; import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; import android.app.ActivityManager; import android.app.ActivityManager; import android.content.BroadcastReceiver; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Context; Loading Loading @@ -336,6 +338,8 @@ class LegacyGlobalActions implements DialogInterface.OnDismissListener, DialogIn } } }); }); dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); 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); dialog.setOnDismissListener(this); Loading Loading
packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +3 −0 Original line number Original line Diff line number Diff line Loading @@ -15,6 +15,7 @@ package com.android.systemui.globalactions; package com.android.systemui.globalactions; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; 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.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS; import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS; import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN; import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN; Loading Loading @@ -485,6 +486,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, attrs.setTitle("ActionsDialog"); attrs.setTitle("ActionsDialog"); attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; mDialog.getWindow().setAttributes(attrs); 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(); mDialog.show(); mWindowManagerFuncs.onGlobalActionsShown(); mWindowManagerFuncs.onGlobalActionsShown(); } } Loading
packages/SystemUI/tests/AndroidManifest.xml +5 −0 Original line number Original line Diff line number Diff line Loading @@ -69,6 +69,11 @@ android:exported="false" android:exported="false" android:resizeableActivity="true" /> android:resizeableActivity="true" /> <activity android:name="com.android.systemui.globalactions.GlobalActionsImeTest$TestActivity" android:excludeFromRecents="true" android:exported="false" /> <provider <provider android:name="androidx.lifecycle.ProcessLifecycleOwnerInitializer" android:name="androidx.lifecycle.ProcessLifecycleOwnerInitializer" tools:replace="android:authorities" tools:replace="android:authorities" Loading
packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsImeTest.java 0 → 100644 +152 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.globalactions; import static android.view.WindowInsets.Type.ime; import static org.junit.Assert.assertTrue; import android.app.Activity; import android.os.Bundle; import android.os.SystemClock; import android.view.View; import android.view.WindowInsets; import android.view.WindowInsetsController; import android.widget.EditText; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.test.filters.LargeTest; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.rule.ActivityTestRule; import org.junit.Rule; import org.junit.Test; import java.util.concurrent.TimeUnit; import java.util.function.BooleanSupplier; @LargeTest public class GlobalActionsImeTest { @Rule public ActivityTestRule<TestActivity> 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); } } }
services/core/java/com/android/server/policy/LegacyGlobalActions.java +4 −0 Original line number Original line Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.server.policy; package com.android.server.policy; import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; import android.app.ActivityManager; import android.app.ActivityManager; import android.content.BroadcastReceiver; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Context; Loading Loading @@ -336,6 +338,8 @@ class LegacyGlobalActions implements DialogInterface.OnDismissListener, DialogIn } } }); }); dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); 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); dialog.setOnDismissListener(this); Loading