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)