OSDN Git Service

EditContactActivity: Restore focus when the screen rotates.
authorMakoto Onuki <omakoto@google.com>
Mon, 2 Nov 2009 07:44:18 +0000 (16:44 +0900)
committerMakoto Onuki <omakoto@google.com>
Thu, 5 Nov 2009 04:56:19 +0000 (13:56 +0900)
Assign unique but consistent IDs to the EditText on EditContactActivity, so that focus is restored when we recreate the activity.
Also assign IDs to other views, such as GenericEditorView as well.  This will be used to restore views' visibility.

This fixes b/2151340.

Also, set focus at the new EditText when you add a new field by clicking on the "+" button.

This fixes b/2223543.

src/com/android/contacts/model/Editor.java
src/com/android/contacts/model/EntityDelta.java
src/com/android/contacts/ui/DisplayGroupsActivity.java
src/com/android/contacts/ui/EditContactActivity.java
src/com/android/contacts/ui/ViewIdGenerator.java [new file with mode: 0644]
src/com/android/contacts/ui/widget/BaseContactEditorView.java
src/com/android/contacts/ui/widget/ContactEditorView.java
src/com/android/contacts/ui/widget/GenericEditorView.java
src/com/android/contacts/ui/widget/KindSectionView.java
src/com/android/contacts/ui/widget/PhotoEditorView.java
src/com/android/contacts/ui/widget/ReadOnlyContactEditorView.java

index b3e8443..473f0d3 100644 (file)
@@ -18,6 +18,7 @@ package com.android.contacts.model;
 
 import com.android.contacts.model.ContactsSource.DataKind;
 import com.android.contacts.model.EntityDelta.ValuesDelta;
+import com.android.contacts.ui.ViewIdGenerator;
 
 import android.provider.ContactsContract.Data;
 
@@ -50,7 +51,8 @@ public interface Editor {
      * builds any needed views. Any changes performed by the user will be
      * written back to that same object.
      */
-    public void setValues(DataKind kind, ValuesDelta values, EntityDelta state, boolean readOnly);
+    public void setValues(DataKind kind, ValuesDelta values, EntityDelta state, boolean readOnly,
+            ViewIdGenerator vig);
 
     /**
      * Add a specific {@link EditorListener} to this {@link Editor}.
index ae30806..1f152e1 100644 (file)
@@ -571,14 +571,6 @@ public class EntityDelta implements Parcelable {
             return getAsLong(mIdColumn);
         }
 
-        /**
-         * Return a valid integer value suitable for {@link View#setId(int)}.
-         */
-        public int getViewId() {
-            final Long id = this.getId();
-            return (id == null) ? View.NO_ID : id.intValue();
-        }
-
         public void setIdColumn(String idColumn) {
             mIdColumn = idColumn;
         }
index ce68dcb..9ac46ce 100644 (file)
@@ -334,7 +334,15 @@ public final class DisplayGroupsActivity extends ExpandableListActivity implemen
      */
     private static Comparator<GroupDelta> sIdComparator = new Comparator<GroupDelta>() {
         public int compare(GroupDelta object1, GroupDelta object2) {
-            return object1.getViewId() - object2.getViewId();
+            final long id1 = object1.getId();
+            final long id2 = object2.getId();
+            if (id1 < id2) {
+                return -1;
+            } else if (id1 > id2) {
+                return 1;
+            } else {
+                return 0;
+            }
         }
     };
 
index 914a2e8..6172c33 100644 (file)
@@ -30,6 +30,7 @@ import com.android.contacts.model.ContactsSource.EditType;
 import com.android.contacts.model.Editor.EditorListener;
 import com.android.contacts.model.EntityDelta.ValuesDelta;
 import com.android.contacts.ui.widget.BaseContactEditorView;
+import com.android.contacts.ui.widget.ContactEditorView;
 import com.android.contacts.ui.widget.PhotoEditorView;
 import com.android.contacts.util.EmptyService;
 import com.android.contacts.util.WeakAsyncTask;
@@ -99,6 +100,7 @@ public final class EditContactActivity extends Activity
 
     private static final String KEY_EDIT_STATE = "state";
     private static final String KEY_RAW_CONTACT_ID_REQUESTING_PHOTO = "photorequester";
+    private static final String KEY_VIEW_ID_GENERATOR = "viewidgenerator";
 
     /** The result code when view activity should close after edit returns */
     public static final int RESULT_CLOSE_VIEW_ACTIVITY = 777;
@@ -124,6 +126,8 @@ public final class EditContactActivity extends Activity
 
     private ArrayList<Dialog> mManagedDialogs = Lists.newArrayList();
 
+    private ViewIdGenerator mViewIdGenerator;
+
     @Override
     protected void onCreate(Bundle icicle) {
         super.onCreate(icicle);
@@ -149,6 +153,11 @@ public final class EditContactActivity extends Activity
             // Trigger dialog to pick account type
             doAddAction();
         }
+
+        if (icicle == null) {
+            // If icicle is non-null, onRestoreInstanceState() will restore the generator.
+            mViewIdGenerator = new ViewIdGenerator();
+        }
     }
 
     private static class QueryEntitiesTask extends
@@ -223,6 +232,7 @@ public final class EditContactActivity extends Activity
         }
 
         outState.putLong(KEY_RAW_CONTACT_ID_REQUESTING_PHOTO, mRawContactIdRequestingPhoto);
+        outState.putParcelable(KEY_VIEW_ID_GENERATOR, mViewIdGenerator);
         super.onSaveInstanceState(outState);
     }
 
@@ -232,6 +242,7 @@ public final class EditContactActivity extends Activity
         mState = savedInstanceState.<EntitySet> getParcelable(KEY_EDIT_STATE);
         mRawContactIdRequestingPhoto = savedInstanceState.getLong(
                 KEY_RAW_CONTACT_ID_REQUESTING_PHOTO);
+        mViewIdGenerator = savedInstanceState.getParcelable(KEY_VIEW_ID_GENERATOR);
         bindEditors();
 
         super.onRestoreInstanceState(savedInstanceState);
@@ -357,7 +368,7 @@ public final class EditContactActivity extends Activity
                     photoEditor));
 
             mContent.addView(editor);
-            editor.setState(entity, source);
+            editor.setState(entity, source, mViewIdGenerator);
         }
 
         // Show editor now that we've loaded state
diff --git a/src/com/android/contacts/ui/ViewIdGenerator.java b/src/com/android/contacts/ui/ViewIdGenerator.java
new file mode 100644 (file)
index 0000000..f7281d4
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2009 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.contacts.ui;
+
+import com.android.contacts.model.EntityDelta;
+import com.android.contacts.model.ContactsSource.DataKind;
+import com.android.contacts.model.EntityDelta.ValuesDelta;
+import com.android.contacts.ui.widget.GenericEditorView;
+import com.android.contacts.ui.widget.KindSectionView;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A class that provides unique view ids for {@link ContentEditorView}, {@link KindSectionView},
+ * {@link GenericEditorView} and {@link EditView} on {@link EditContactActivity}.
+ * It is used to assign a unique but consistent id to each view across {@link EditContactActivity}'s
+ * lifecycle, so that we can re-construct view state (e.g. focused view) when the screen rotates.
+ *
+ * <p>This class is not thread safe.
+ */
+public final class ViewIdGenerator implements Parcelable {
+    private static final int INVALID_VIEW_ID = 0;
+    private static final int INITIAL_VIEW_ID = 1;
+
+    public static final int NO_VIEW_INDEX = -1;
+
+    private int mNextId;
+
+    /**
+     * Used as a map from the "key" of the views to actual ids.  {@link #getId()} generates keys for
+     * the views.
+     */
+    private Bundle mIdMap = new Bundle();
+
+    private static final char KEY_SEPARATOR = '*';
+
+    private final static StringBuilder sWorkStringBuilder = new StringBuilder();
+
+    public ViewIdGenerator() {
+        mNextId = INITIAL_VIEW_ID;
+    }
+
+    /** {@inheritDoc} */
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Returns an id for a view associated with specified contact field.
+     *
+     * @param entity {@link EntityDelta} associated with the view
+     * @param kind {@link DataKind} associated with the view, or null if none exists.
+     * @param values {@link ValuesDelta} associated with the view, or null if none exists.
+     * @param viewIndex index of the view in the parent {@link Editor}, if it's a leave view.
+     *     Otherwise, pass {@link #NO_VIEW_INDEX}.
+     */
+    public int getId(EntityDelta entity, DataKind kind, ValuesDelta values,
+            int viewIndex) {
+        final String k = getMapKey(entity, kind, values, viewIndex);
+
+        int id = mIdMap.getInt(k, INVALID_VIEW_ID);
+        if (id == INVALID_VIEW_ID) {
+            // Make sure the new id won't conflict with auto-generated ids by masking with 0xffff.
+            id = (mNextId++) & 0xFFFF;
+            mIdMap.putInt(k, id);
+        }
+        return id;
+    }
+
+    private static String getMapKey(EntityDelta entity, DataKind kind, ValuesDelta values,
+            int viewIndex) {
+        sWorkStringBuilder.setLength(0);
+        if (entity != null) {
+            sWorkStringBuilder.append(entity.getValues().getId());
+
+            if (kind != null) {
+                sWorkStringBuilder.append(KEY_SEPARATOR);
+                sWorkStringBuilder.append(kind.mimeType);
+
+                if (values != null) {
+                    sWorkStringBuilder.append(KEY_SEPARATOR);
+                    sWorkStringBuilder.append(values.getId());
+
+                    if (viewIndex != NO_VIEW_INDEX) {
+                        sWorkStringBuilder.append(KEY_SEPARATOR);
+                        sWorkStringBuilder.append(viewIndex);
+                    }
+                }
+            }
+        }
+        return sWorkStringBuilder.toString();
+    }
+
+    /** {@Override} */
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mNextId);
+        dest.writeBundle(mIdMap);
+    }
+
+    private void readFromParcel(Parcel src) {
+        mNextId = src.readInt();
+        mIdMap = src.readBundle();
+    }
+
+    public static final Parcelable.Creator<ViewIdGenerator> CREATOR =
+            new Parcelable.Creator<ViewIdGenerator>() {
+        public ViewIdGenerator createFromParcel(Parcel in) {
+            final ViewIdGenerator vig = new ViewIdGenerator();
+            vig.readFromParcel(in);
+            return vig;
+        }
+
+        public ViewIdGenerator[] newArray(int size) {
+            return new ViewIdGenerator[size];
+        }
+    };
+}
index 189e2d5..c2f9136 100644 (file)
@@ -22,6 +22,7 @@ import com.android.contacts.model.EntityModifier;
 import com.android.contacts.model.ContactsSource.EditType;
 import com.android.contacts.model.Editor.EditorListener;
 import com.android.contacts.model.EntityDelta.ValuesDelta;
+import com.android.contacts.ui.ViewIdGenerator;
 
 import android.content.Context;
 import android.content.Entity;
@@ -95,7 +96,7 @@ public abstract class BaseContactEditorView extends LinearLayout {
      * {@link EntityDelta} state and the {@link ContactsSource} that
      * apply to that state.
      */
-    public abstract void setState(EntityDelta state, ContactsSource source);
+    public abstract void setState(EntityDelta state, ContactsSource source, ViewIdGenerator vig);
 
     /**
      * Sets the {@link EditorListener} on the name field
index 1720822..2de9ec6 100644 (file)
@@ -24,6 +24,7 @@ import com.android.contacts.model.ContactsSource.DataKind;
 import com.android.contacts.model.ContactsSource.EditType;
 import com.android.contacts.model.Editor.EditorListener;
 import com.android.contacts.model.EntityDelta.ValuesDelta;
+import com.android.contacts.ui.ViewIdGenerator;
 
 import android.content.Context;
 import android.content.Entity;
@@ -149,7 +150,7 @@ public class ContactEditorView extends BaseContactEditorView implements OnClickL
      * apply to that state.
      */
     @Override
-    public void setState(EntityDelta state, ContactsSource source) {
+    public void setState(EntityDelta state, ContactsSource source, ViewIdGenerator vig) {
         // Remove any existing sections
         mGeneral.removeAllViews();
         mSecondary.removeAllViews();
@@ -157,6 +158,8 @@ public class ContactEditorView extends BaseContactEditorView implements OnClickL
         // Bail if invalid state or source
         if (state == null || source == null) return;
 
+        setId(vig.getId(state, null, null, ViewIdGenerator.NO_VIEW_INDEX));
+
         // Make sure we have StructuredName
         EntityModifier.ensureKindExists(state, source, StructuredName.CONTENT_ITEM_TYPE);
 
@@ -208,7 +211,7 @@ public class ContactEditorView extends BaseContactEditorView implements OnClickL
                 // Handle special case editor for structured name
                 final ValuesDelta primary = state.getPrimaryEntry(mimeType);
                 if (!readOnly) {
-                    mName.setValues(kind, primary, state, source.readOnly);
+                    mName.setValues(kind, primary, state, source.readOnly, vig);
                 } else {
                     String displayName = primary.getAsString(StructuredName.DISPLAY_NAME);
                     mReadOnlyName.setText(displayName);
@@ -216,7 +219,7 @@ public class ContactEditorView extends BaseContactEditorView implements OnClickL
             } else if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
                 // Handle special case editor for photos
                 final ValuesDelta primary = state.getPrimaryEntry(mimeType);
-                mPhoto.setValues(kind, primary, state, source.readOnly);
+                mPhoto.setValues(kind, primary, state, source.readOnly, vig);
                 if (readOnly && !mPhoto.hasSetPhoto()) {
                     mPhotoStub.setVisibility(View.GONE);
                 } else {
@@ -228,8 +231,7 @@ public class ContactEditorView extends BaseContactEditorView implements OnClickL
                 final ViewGroup parent = kind.secondary ? mSecondary : mGeneral;
                 final KindSectionView section = (KindSectionView)mInflater.inflate(
                         R.layout.item_kind_section, parent, false);
-                section.setState(kind, state, source.readOnly);
-                section.setId(kind.weight);
+                section.setState(kind, state, source.readOnly, vig);
                 parent.addView(section);
             }
         }
index e1fdd71..c9b9c50 100644 (file)
@@ -25,6 +25,7 @@ import com.android.contacts.model.ContactsSource.DataKind;
 import com.android.contacts.model.ContactsSource.EditField;
 import com.android.contacts.model.ContactsSource.EditType;
 import com.android.contacts.model.EntityDelta.ValuesDelta;
+import com.android.contacts.ui.ViewIdGenerator;
 
 import android.app.AlertDialog;
 import android.app.Dialog;
@@ -79,6 +80,8 @@ public class GenericEditorView extends RelativeLayout implements Editor, View.On
     // Used only when a user tries to use custom label.
     private EditType mPendingType;
 
+    private ViewIdGenerator mViewIdGenerator;
+
     public GenericEditorView(Context context) {
         super(context);
     }
@@ -160,18 +163,21 @@ public class GenericEditorView extends RelativeLayout implements Editor, View.On
     }
 
     private void rebuildValues() {
-        setValues(mKind, mEntry, mState, mReadOnly);
+        setValues(mKind, mEntry, mState, mReadOnly, mViewIdGenerator);
     }
 
     /**
      * Prepare this editor using the given {@link DataKind} for defining
      * structure and {@link ValuesDelta} describing the content to edit.
      */
-    public void setValues(DataKind kind, ValuesDelta entry, EntityDelta state, boolean readOnly) {
+    public void setValues(DataKind kind, ValuesDelta entry, EntityDelta state, boolean readOnly,
+            ViewIdGenerator vig) {
         mKind = kind;
         mEntry = entry;
         mState = state;
         mReadOnly = readOnly;
+        mViewIdGenerator = vig;
+        setId(vig.getId(state, kind, entry, ViewIdGenerator.NO_VIEW_INDEX));
 
         final boolean enabled = !readOnly;
 
@@ -195,9 +201,11 @@ public class GenericEditorView extends RelativeLayout implements Editor, View.On
         // Build out set of fields
         mFields.removeAllViews();
         boolean hidePossible = false;
+        int n = 0;
         for (EditField field : kind.fieldList) {
             // Inflate field from definition
             EditText fieldView = (EditText)mInflater.inflate(RES_FIELD, mFields, false);
+            fieldView.setId(vig.getId(state, kind, entry, n++));
             if (field.titleRes > 0) {
                 fieldView.setHint(field.titleRes);
             }
index b52cfd0..e379b69 100644 (file)
@@ -23,6 +23,7 @@ import com.android.contacts.model.EntityModifier;
 import com.android.contacts.model.ContactsSource.DataKind;
 import com.android.contacts.model.Editor.EditorListener;
 import com.android.contacts.model.EntityDelta.ValuesDelta;
+import com.android.contacts.ui.ViewIdGenerator;
 
 import android.content.Context;
 import android.provider.ContactsContract.Data;
@@ -52,6 +53,8 @@ public class KindSectionView extends LinearLayout implements OnClickListener, Ed
     private EntityDelta mState;
     private boolean mReadOnly;
 
+    private ViewIdGenerator mViewIdGenerator;
+
     public KindSectionView(Context context) {
         super(context);
     }
@@ -88,10 +91,13 @@ public class KindSectionView extends LinearLayout implements OnClickListener, Ed
         // Ignore requests
     }
 
-    public void setState(DataKind kind, EntityDelta state, boolean readOnly) {
+    public void setState(DataKind kind, EntityDelta state, boolean readOnly, ViewIdGenerator vig) {
         mKind = kind;
         mState = state;
         mReadOnly = readOnly;
+        mViewIdGenerator = vig;
+
+        setId(mViewIdGenerator.getId(state, kind, null, ViewIdGenerator.NO_VIEW_INDEX));
 
         // TODO: handle resources from remote packages
         mTitle.setText(kind.titleRes);
@@ -116,9 +122,8 @@ public class KindSectionView extends LinearLayout implements OnClickListener, Ed
 
             final GenericEditorView editor = (GenericEditorView)mInflater.inflate(
                     R.layout.item_generic_editor, mEditors, false);
-            editor.setValues(mKind, entry, mState, mReadOnly);
+            editor.setValues(mKind, entry, mState, mReadOnly, mViewIdGenerator);
             editor.setEditorListener(this);
-            editor.setId(entry.getViewId());
             mEditors.addView(editor);
         }
     }
@@ -138,9 +143,16 @@ public class KindSectionView extends LinearLayout implements OnClickListener, Ed
     /** {@inheritDoc} */
     public void onClick(View v) {
         // Insert a new child and rebuild
-        EntityModifier.insertChild(mState, mKind);
+        final ValuesDelta newValues = EntityModifier.insertChild(mState, mKind);
         this.rebuildFromState();
         this.updateAddEnabled();
         this.updateEditorsVisible();
+
+        // Find the newly added EditView and set focus.
+        final int newFieldId = mViewIdGenerator.getId(mState, mKind, newValues, 0);
+        final View newField = findViewById(newFieldId);
+        if (newField != null) {
+            newField.requestFocus();
+        }
     }
 }
index f117091..eff39d0 100644 (file)
@@ -21,6 +21,7 @@ import com.android.contacts.model.EntityDelta;
 import com.android.contacts.model.ContactsSource.DataKind;
 import com.android.contacts.model.EntityDelta.ValuesDelta;
 import com.android.contacts.model.Editor;
+import com.android.contacts.ui.ViewIdGenerator;
 
 import android.content.Context;
 import android.graphics.Bitmap;
@@ -75,9 +76,13 @@ public class PhotoEditorView extends ImageView implements Editor, OnClickListene
     }
 
     /** {@inheritDoc} */
-    public void setValues(DataKind kind, ValuesDelta values, EntityDelta state, boolean readOnly) {
+    public void setValues(DataKind kind, ValuesDelta values, EntityDelta state, boolean readOnly,
+            ViewIdGenerator vig) {
         mEntry = values;
         mReadOnly = readOnly;
+
+        setId(vig.getId(state, kind, values, 0));
+
         if (values != null) {
             // Try decoding photo if actual entry
             final byte[] photoBytes = values.getAsByteArray(Photo.PHOTO);
index a5f8eb6..4635f6a 100644 (file)
@@ -24,6 +24,7 @@ import com.android.contacts.model.ContactsSource.DataKind;
 import com.android.contacts.model.ContactsSource.EditType;
 import com.android.contacts.model.Editor.EditorListener;
 import com.android.contacts.model.EntityDelta.ValuesDelta;
+import com.android.contacts.ui.ViewIdGenerator;
 
 import android.content.Context;
 import android.content.Entity;
@@ -101,7 +102,7 @@ class ReadOnlyContactEditorView extends BaseContactEditorView {
      * TODO: make this more generic using data from the source
      */
     @Override
-    public void setState(EntityDelta state, ContactsSource source) {
+    public void setState(EntityDelta state, ContactsSource source, ViewIdGenerator vig) {
         // Remove any existing sections
         mGeneral.removeAllViews();
 
@@ -135,7 +136,7 @@ class ReadOnlyContactEditorView extends BaseContactEditorView {
             EntityModifier.ensureKindExists(state, source, Photo.CONTENT_ITEM_TYPE);
             mHasPhotoEditor = (source.getKindForMimetype(Photo.CONTENT_ITEM_TYPE) != null);
             primary = state.getPrimaryEntry(Photo.CONTENT_ITEM_TYPE);
-            mPhoto.setValues(kind, primary, state, source.readOnly);
+            mPhoto.setValues(kind, primary, state, source.readOnly, vig);
             if (!mHasPhotoEditor || !mPhoto.hasSetPhoto()) {
                 mPhotoStub.setVisibility(View.GONE);
             } else {