Merge "GlobalActions: Disable IME focusability" into rvc-dev am: 4869550555 am: a44bbba4d9 am: 2f59f928ca

Change-Id: I56fe1ba7dfac43423e56954e17d45c31b2a1492d
This commit is contained in:
Adrian Roos
2020-05-11 14:39:35 +00:00
committed by Automerger Merge Worker
4 changed files with 164 additions and 0 deletions

View File

@@ -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();
}

View File

@@ -69,6 +69,11 @@
android:exported="false"
android:resizeableActivity="true" />
<activity
android:name="com.android.systemui.globalactions.GlobalActionsImeTest$TestActivity"
android:excludeFromRecents="true"
android:exported="false" />
<provider
android:name="androidx.lifecycle.ProcessLifecycleOwnerInitializer"
tools:replace="android:authorities"

View File

@@ -0,0 +1,152 @@
/*
* 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);
}
}
}

View File

@@ -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);