Fix initial focus issues in dialogs and editor

Test: manually launched app on Walleye running P and verified that
keyboard is opened when the create and rename label dialogs are opened.
Also verified that the keyboard remains visible in the editor when
the screen is rotated with an input field focused.

Bug: 77244371
Bug: 77246197
Change-Id: I66638bfae9e17986773395624e3cccc75c95eb10
diff --git a/res/layout-land/contact_editor_fragment.xml b/res/layout-land/contact_editor_fragment.xml
index 7f98765..6f026aa 100644
--- a/res/layout-land/contact_editor_fragment.xml
+++ b/res/layout-land/contact_editor_fragment.xml
@@ -15,41 +15,49 @@
      limitations under the License.
 -->
 
-<com.android.contacts.editor.RawContactEditorView
-        xmlns:android="http://schemas.android.com/apk/res/android"
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/contact_editor_fragment"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@color/background_primary" >
+
+    <com.android.contacts.editor.RawContactEditorView
         android:id="@+id/raw_contacts_editor_view"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:background="@color/background_primary"
         android:orientation="horizontal"
         android:visibility="invisible">
 
-    <include layout="@layout/photo_editor_view" />
+        <include layout="@layout/photo_editor_view" />
 
-    <!-- Dummy view so the first input field is not initially focused. b/21644158 -->
-    <View
+        <!-- Dummy view so the first input field is not initially focused. b/21644158 -->
+        <View
             android:layout_width="0dp"
             android:layout_height="0dp"
             android:focusable="true"
             android:focusableInTouchMode="true"/>
 
-    <ScrollView
+        <ScrollView
+            android:id="@+id/contact_editor_scroller"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:fadingEdge="none"
             android:fillViewport="true">
 
-        <LinearLayout
+            <LinearLayout
+                android:id="@+id/editor_fields_container"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:orientation="vertical">
 
-            <include layout="@layout/editor_account_header" />
+                <include layout="@layout/editor_account_header" />
 
-            <include layout="@layout/contact_editor_fields" />
+                <include layout="@layout/contact_editor_fields" />
 
-        </LinearLayout>
+            </LinearLayout>
 
-    </ScrollView>
+        </ScrollView>
 
-</com.android.contacts.editor.RawContactEditorView>
\ No newline at end of file
+    </com.android.contacts.editor.RawContactEditorView>
+</FrameLayout>
diff --git a/res/layout/contact_editor_activity.xml b/res/layout/contact_editor_activity.xml
index 1d726fa..5843a58 100644
--- a/res/layout/contact_editor_activity.xml
+++ b/res/layout/contact_editor_activity.xml
@@ -33,4 +33,11 @@
         app:navigationIcon="@drawable/quantum_ic_close_vd_theme_24"
         app:title="@string/contact_editor_title_existing_contact" />
 
-</LinearLayout>
\ No newline at end of file
+    <fragment
+        android:id="@+id/contact_editor_fragment"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:name="com.android.contacts.editor.ContactEditorFragment"
+        android:tag="editor_fragment"/>
+
+</LinearLayout>
diff --git a/res/layout/contact_editor_fragment.xml b/res/layout/contact_editor_fragment.xml
index 690be8c..03a84e1 100644
--- a/res/layout/contact_editor_fragment.xml
+++ b/res/layout/contact_editor_fragment.xml
@@ -15,34 +15,41 @@
      limitations under the License.
 -->
 
-<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
-        android:id="@+id/contact_editor_fragment"
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/contact_editor_fragment"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@color/background_primary" >
+
+    <ScrollView 
+        android:id="@+id/contact_editor_scroller"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:background="@color/background_primary"
         android:fadingEdge="none"
         android:fillViewport="true">
 
-    <com.android.contacts.editor.RawContactEditorView
+        <com.android.contacts.editor.RawContactEditorView
             android:id="@+id/raw_contacts_editor_view"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:orientation="vertical"
             android:visibility="invisible">
 
-        <include layout="@layout/photo_editor_view" />
+            <include layout="@layout/photo_editor_view" />
 
-        <!-- Dummy view so the first input field is not initially focused. b/21644158 -->
-        <View
-            android:layout_width="0dp"
-            android:layout_height="0dp"
-            android:focusable="true"
-            android:focusableInTouchMode="true"/>
+            <!-- Dummy view so the first input field is not initially focused. b/21644158 -->
+            <View
+                android:layout_width="0dp"
+                android:layout_height="0dp"
+                android:focusable="true"
+                android:focusableInTouchMode="true"/>
 
-        <include layout="@layout/editor_account_header" />
+            <include layout="@layout/editor_account_header" />
 
-        <include layout="@layout/contact_editor_fields" />
+            <include layout="@layout/contact_editor_fields" />
 
-    </com.android.contacts.editor.RawContactEditorView>
+        </com.android.contacts.editor.RawContactEditorView>
 
-</ScrollView>
+    </ScrollView>
+</FrameLayout>
diff --git a/res/layout/group_name_edit_dialog.xml b/res/layout/group_name_edit_dialog.xml
index 94db3dd..333ac43 100644
--- a/res/layout/group_name_edit_dialog.xml
+++ b/res/layout/group_name_edit_dialog.xml
@@ -27,6 +27,9 @@
     app:errorEnabled="true"
     app:hintEnabled="false">
 
+    <!-- In Android P there is no initial focus by default in touch mode. See b/77244371 -->
+    <requestFocus />
+
     <android.support.design.widget.TextInputEditText
         android:id="@android:id/text1"
         android:layout_width="match_parent"
@@ -39,4 +42,4 @@
         android:minHeight="@dimen/dialog_edit_text_min_height"
         android:textAlignment="viewStart"
         android:singleLine="true"/>
-</android.support.design.widget.TextInputLayout>
\ No newline at end of file
+</android.support.design.widget.TextInputLayout>
diff --git a/src/com/android/contacts/activities/ContactEditorActivity.java b/src/com/android/contacts/activities/ContactEditorActivity.java
index 2108ba9..74a0df6 100644
--- a/src/com/android/contacts/activities/ContactEditorActivity.java
+++ b/src/com/android/contacts/activities/ContactEditorActivity.java
@@ -355,23 +355,15 @@
         // Set activity title for Talkback
         setTitle(mActionBarTitleResId);
 
-        if (savedState == null) {
-            // Create the editor and photo selection fragments
-            mFragment = new ContactEditorFragment();
-            getFragmentManager().beginTransaction()
-                    .add(R.id.fragment_container, getEditorFragment(), TAG_EDITOR_FRAGMENT)
-                    .commit();
-        } else {
+        mFragment =
+            (ContactEditor) getFragmentManager().findFragmentById(R.id.contact_editor_fragment);
+
+        if (savedState != null) {
             // Restore state
             mPhotoMode = savedState.getInt(STATE_PHOTO_MODE);
             mActionBarTitleResId = savedState.getInt(STATE_ACTION_BAR_TITLE);
             mPhotoUri = Uri.parse(savedState.getString(STATE_PHOTO_URI));
 
-            // Show/hide the editor and photo selection fragments (w/o animations)
-            mFragment = (ContactEditorFragment) getFragmentManager()
-                    .findFragmentByTag(TAG_EDITOR_FRAGMENT);
-            final FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
-            fragmentTransaction.show(getEditorFragment()).commit();
             mToolbar.setTitle(mActionBarTitleResId);
         }
 
@@ -388,16 +380,6 @@
     }
 
     @Override
-    protected void onPause() {
-        super.onPause();
-        final InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
-        final View currentFocus = getCurrentFocus();
-        if (imm != null && currentFocus != null) {
-            imm.hideSoftInputFromWindow(currentFocus.getWindowToken(), 0);
-        }
-    }
-
-    @Override
     protected void onNewIntent(Intent intent) {
         super.onNewIntent(intent);
 
diff --git a/src/com/android/contacts/editor/ContactEditorFragment.java b/src/com/android/contacts/editor/ContactEditorFragment.java
index dfa0eeb..a8e3cd8 100755
--- a/src/com/android/contacts/editor/ContactEditorFragment.java
+++ b/src/com/android/contacts/editor/ContactEditorFragment.java
@@ -31,6 +31,7 @@
 import android.graphics.Bitmap;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.SystemClock;
 import android.provider.ContactsContract;
 import android.provider.ContactsContract.CommonDataKinds.Email;
@@ -50,6 +51,7 @@
 import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.inputmethod.InputMethodManager;
 import android.widget.AdapterView;
 import android.widget.BaseAdapter;
 import android.widget.EditText;
@@ -103,6 +105,7 @@
 import java.util.List;
 import java.util.Locale;
 import java.util.Set;
+import javax.annotation.Nullable;
 
 /**
  * Contact editor with only the most important fields displayed initially.
@@ -121,6 +124,11 @@
     private static final int LOADER_GROUPS = 2;
     private static final int LOADER_ACCOUNTS = 3;
 
+    // How long to delay before attempting to restore focus and keyboard
+    // visibility after view state has been restored (e.g. after rotation)
+    // See b/77246197
+    private static final long RESTORE_FOCUS_DELAY_MILLIS = 100L;
+
     private static final String KEY_PHOTO_RAW_CONTACT_ID = "photo_raw_contact_id";
     private static final String KEY_UPDATED_PHOTOS = "updated_photos";
 
@@ -164,6 +172,10 @@
     private static final String KEY_READ_ONLY_DISPLAY_NAME_ID = "readOnlyDisplayNameId";
     private static final String KEY_COPY_READ_ONLY_DISPLAY_NAME = "copyReadOnlyDisplayName";
 
+    private static final String KEY_FOCUSED_VIEW_ID = "focusedViewId";
+
+    private static final String KEY_RESTORE_SOFT_INPUT = "restoreSoftInput";
+
     protected static final int REQUEST_CODE_JOIN = 0;
     protected static final int REQUEST_CODE_ACCOUNTS_CHANGED = 1;
 
@@ -445,6 +457,8 @@
     private long mPhotoRawContactId;
     private Bundle mUpdatedPhotos = new Bundle();
 
+    private InputMethodManager inputMethodManager;
+
     @Override
     public Context getContext() {
         return getActivity();
@@ -469,6 +483,9 @@
 
         super.onCreate(savedState);
 
+        inputMethodManager =
+            (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
+
         if (savedState == null) {
             mViewIdGenerator = new ViewIdGenerator();
 
@@ -576,6 +593,15 @@
         }
     }
 
+    @Override
+    public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
+        super.onViewStateRestored(savedInstanceState);
+        if (savedInstanceState == null) {
+            return;
+        }
+        maybeRestoreFocus(savedInstanceState);
+    }
+
     /**
      * Checks if the requested action is valid.
      *
@@ -629,6 +655,14 @@
 
         outState.putLong(KEY_PHOTO_RAW_CONTACT_ID, mPhotoRawContactId);
         outState.putParcelable(KEY_UPDATED_PHOTOS, mUpdatedPhotos);
+
+        // For b/77246197
+        View focusedView = getView() == null ? null : getView().findFocus();
+        if (focusedView != null) {
+            outState.putInt(KEY_FOCUSED_VIEW_ID, focusedView.getId());
+            outState.putBoolean(KEY_RESTORE_SOFT_INPUT, inputMethodManager.isActive(focusedView));
+        }
+
         super.onSaveInstanceState(outState);
     }
 
@@ -1761,4 +1795,46 @@
     private RawContactEditorView getContent() {
         return (RawContactEditorView) mContent;
     }
+
+    // TODO(b/77246197): figure out a better way to address focus being lost on rotation.
+    private void maybeRestoreFocus(Bundle savedInstanceState) {
+        int focusedViewId = savedInstanceState.getInt(KEY_FOCUSED_VIEW_ID, View.NO_ID);
+        if (focusedViewId == View.NO_ID) {
+            return;
+        }
+        boolean shouldRestoreSoftInput = savedInstanceState.getBoolean(KEY_RESTORE_SOFT_INPUT);
+        new Handler()
+            .postDelayed(
+                    () -> {
+                        if (!isResumed()) {
+                            return;
+                        }
+                        View root = getView();
+                        if (root == null) {
+                            return;
+                        }
+                        View focusedView = root.findFocus();
+                        if (focusedView != null) {
+                            return;
+                        }
+                        focusedView = getView().findViewById(focusedViewId);
+                        if (focusedView == null) {
+                            return;
+                        }
+                        boolean didFocus = focusedView.requestFocus();
+                        if (!didFocus) {
+                            Log.i(TAG, "requestFocus failed");
+                            return;
+                        }
+                        if (shouldRestoreSoftInput) {
+                            boolean didShow = inputMethodManager
+                                .showSoftInput(focusedView, InputMethodManager.SHOW_IMPLICIT);
+                            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                                Log.d(TAG, "showSoftInput -> " + didShow);
+                            }
+                        }
+                    },
+            RESTORE_FOCUS_DELAY_MILLIS);
+    }
+
 }