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;
* 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}.
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;
}
*/
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;
+ }
}
};
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;
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;
private ArrayList<Dialog> mManagedDialogs = Lists.newArrayList();
+ private ViewIdGenerator mViewIdGenerator;
+
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
// 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
}
outState.putLong(KEY_RAW_CONTACT_ID_REQUESTING_PHOTO, mRawContactIdRequestingPhoto);
+ outState.putParcelable(KEY_VIEW_ID_GENERATOR, mViewIdGenerator);
super.onSaveInstanceState(outState);
}
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);
photoEditor));
mContent.addView(editor);
- editor.setState(entity, source);
+ editor.setState(entity, source, mViewIdGenerator);
}
// Show editor now that we've loaded state
--- /dev/null
+/*
+ * 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];
+ }
+ };
+}
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;
* {@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
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;
* 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();
// 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);
// 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);
} 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 {
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);
}
}
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;
// Used only when a user tries to use custom label.
private EditType mPendingType;
+ private ViewIdGenerator mViewIdGenerator;
+
public GenericEditorView(Context context) {
super(context);
}
}
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;
// 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);
}
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;
private EntityDelta mState;
private boolean mReadOnly;
+ private ViewIdGenerator mViewIdGenerator;
+
public KindSectionView(Context context) {
super(context);
}
// 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);
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);
}
}
/** {@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();
+ }
}
}
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;
}
/** {@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);
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;
* 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();
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 {