Merge "Migrate IME visible fix when detaching served view to AndroidX" into androidx-main am: 3e49d15581
Original change: https://android-review.googlesource.com/c/platform/frameworks/support/+/2471869
Change-Id: Idac31b0f8e95e589621df7599ccde565829d149a
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/appcompat/appcompat/src/androidTest/AndroidManifest.xml b/appcompat/appcompat/src/androidTest/AndroidManifest.xml
index 2abfc0d..cf53763 100644
--- a/appcompat/appcompat/src/androidTest/AndroidManifest.xml
+++ b/appcompat/appcompat/src/androidTest/AndroidManifest.xml
@@ -156,6 +156,11 @@
android:theme="@style/Theme.AppCompat.Light"/>
<activity
+ android:name="androidx.appcompat.widget.AppCompatEditTextImeFocusActivity"
+ android:label="@string/app_compat_edit_text_ime_focus_activity"
+ android:theme="@style/Theme.TextColors"/>
+
+ <activity
android:name="androidx.appcompat.widget.AppCompatButtonAutoSizeActivity"
android:label="@string/app_compat_button_auto_size_activity"
android:theme="@style/Theme.AppCompat.Light"/>
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatEditTextImeFocusActivity.java b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatEditTextImeFocusActivity.java
new file mode 100644
index 0000000..6783008
--- /dev/null
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatEditTextImeFocusActivity.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2023 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 androidx.appcompat.widget;
+
+import android.view.ViewGroup;
+import android.view.inputmethod.InputMethodManager;
+
+import androidx.annotation.RequiresApi;
+import androidx.appcompat.test.R;
+import androidx.appcompat.testutils.BaseTestActivity;
+
+public class AppCompatEditTextImeFocusActivity extends BaseTestActivity {
+
+ ViewGroup mLayout;
+ AppCompatEditText mEditText1;
+ AppCompatEditText mEditText2;
+ InputMethodManager mInputMethodManager;
+
+ @Override
+ protected int getContentViewLayoutResId() {
+ return R.layout.appcompat_edittext_ime_focus_activity;
+ }
+
+ @RequiresApi(23)
+ public void initActivity() {
+ mLayout = findViewById(R.id.ime_layout);
+ mEditText1 = new AppCompatEditText(this);
+ mEditText2 = new AppCompatEditText(this);
+ mInputMethodManager = getSystemService(InputMethodManager.class);
+ }
+
+ public void addFirstEditorAndRequestFocus() {
+ mEditText1.requestFocus();
+ mLayout.addView(mEditText1);
+ }
+
+ public void addSecondEditor() {
+ mLayout.addView(mEditText2);
+ }
+
+ @RequiresApi(19)
+ public boolean isSecondEditorLayout() {
+ return mEditText2.isLaidOut();
+ }
+
+ public void switchToSecondEditor() {
+ mEditText2.requestFocus();
+ mLayout.removeView(mEditText1);
+ }
+
+ public boolean isFirstEditorActive() {
+ return mInputMethodManager.isActive(mEditText1);
+ }
+
+ public boolean isSecondEditorActive() {
+ return mInputMethodManager.isActive(mEditText2);
+ }
+
+ public void removeSecondEditor() {
+ mLayout.removeView(mEditText2);
+ }
+}
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatEditTextImeFocusTest.java b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatEditTextImeFocusTest.java
new file mode 100644
index 0000000..284cca4
--- /dev/null
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatEditTextImeFocusTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2023 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 androidx.appcompat.widget;
+
+
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+import androidx.test.filters.SdkSuppress;
+import androidx.testutils.PollingCheck;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class AppCompatEditTextImeFocusTest {
+
+ private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+ private static final int STRESS_TEST_COUNTS = 500;
+
+ @Rule
+ public final ActivityScenarioRule<AppCompatEditTextImeFocusActivity> mActivityScenarioRule =
+ new ActivityScenarioRule<>(AppCompatEditTextImeFocusActivity.class);
+
+ @Test
+ @SdkSuppress(minSdkVersion = 30)
+ public void detachServed_withDifferentNextServed_stressTest() {
+ for (int i = 0; i < STRESS_TEST_COUNTS; i++) {
+ detachServed_withDifferentNextServed();
+ }
+ }
+
+ @SdkSuppress(minSdkVersion = 30)
+ private void detachServed_withDifferentNextServed() {
+ final ActivityScenario<AppCompatEditTextImeFocusActivity> activityScenario =
+ mActivityScenarioRule.getScenario();
+
+ // Initialize activity components and add first editor into layout
+ activityScenario.onActivity(activity -> {
+ activity.initActivity();
+ activity.addFirstEditorAndRequestFocus();
+ });
+
+ // The first editor should be active for input method.
+ activityScenario.onActivity(
+ activity -> PollingCheck.waitFor(TIMEOUT, activity::isFirstEditorActive));
+
+ // Add second editor into layout and ensure it is laid out.
+ activityScenario.onActivity(AppCompatEditTextImeFocusActivity::addSecondEditor);
+ activityScenario.onActivity(
+ activity -> PollingCheck.waitFor(TIMEOUT, activity::isSecondEditorLayout));
+
+ // Second editor request focus and detach the served view(first editor).
+ // Verify second editor should be active.
+ activityScenario.onActivity(activity -> {
+ activity.switchToSecondEditor();
+ assertTrue(activity.isSecondEditorActive());
+ activity.removeSecondEditor();
+ });
+ }
+}
diff --git a/appcompat/appcompat/src/androidTest/res/layout/appcompat_edittext_ime_focus_activity.xml b/appcompat/appcompat/src/androidTest/res/layout/appcompat_edittext_ime_focus_activity.xml
new file mode 100644
index 0000000..5767e18
--- /dev/null
+++ b/appcompat/appcompat/src/androidTest/res/layout/appcompat_edittext_ime_focus_activity.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2023 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.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/ime_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+</LinearLayout>
\ No newline at end of file
diff --git a/appcompat/appcompat/src/androidTest/res/values/donottranslate-strings.xml b/appcompat/appcompat/src/androidTest/res/values/donottranslate-strings.xml
index e38440a..a73602f 100644
--- a/appcompat/appcompat/src/androidTest/res/values/donottranslate-strings.xml
+++ b/appcompat/appcompat/src/androidTest/res/values/donottranslate-strings.xml
@@ -64,6 +64,7 @@
<string name="app_compat_text_view_auto_size_activity">AppCompat text view auto-size</string>
<string name="app_compat_text_view_emoji_activity">AppCompat text view emoji</string>
<string name="app_compat_edit_text_activity">AppCompat edit text</string>
+ <string name="app_compat_edit_text_ime_focus_activity">AppCompat edit text ime focus</string>
<string name="app_compat_edit_text_receive_content_activity">
AppCompat edit text receive content activity
</string>
diff --git a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatEditText.java b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatEditText.java
index 042db5b..770f713 100644
--- a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatEditText.java
+++ b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatEditText.java
@@ -32,6 +32,7 @@
import android.view.DragEvent;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethodManager;
import android.view.textclassifier.TextClassifier;
import android.widget.EditText;
import android.widget.TextView;
@@ -301,6 +302,20 @@
super.getCustomSelectionActionModeCallback());
}
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ if (Build.VERSION.SDK_INT >= 30 && Build.VERSION.SDK_INT < 33) {
+ final InputMethodManager imm = (InputMethodManager) getContext().getSystemService(
+ Context.INPUT_METHOD_SERVICE);
+ // Calling isActive() here implied a checkFocus() call to update the active served
+ // view for input method. This is a backport for mServedView was detached, but the
+ // next served view gets mistakenly cleared as well.
+ // https://android.googlesource.com/platform/frameworks/base/+/734613a500fb
+ imm.isActive(this);
+ }
+ }
+
@UiThread
@NonNull
@RequiresApi(26)